diff --git a/.gitignore b/.gitignore
index d4ea73c8..651d8ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -319,6 +319,7 @@
 /tools/metrics/histograms/metadata/*/*.before.pretty-print.xml
 /tools/metrics/histograms/enums.before.pretty-print.xml
 /tools/page_cycler/acid3
+/tools/bluetooth
 /tools/reclient
 /tools/skia_goldctl/
 /tools/tryserver
diff --git a/DEPS b/DEPS
index f6e2f7f..5f96c95 100644
--- a/DEPS
+++ b/DEPS
@@ -297,15 +297,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'faf97e5d2c2b879cb64db6da8599262b7674b807',
+  'skia_revision': '0cc4b51ab9d5c72c052db4cf3782583df0dfa44e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '1104a81a2a08c3b25b400d2ddf283573e14eb9e5',
+  'v8_revision': 'f43f657e166b75db1f91f9f554ec8c474f87194b',
   # 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': '6f80f0f0373f9b4f014264e337443939021461b6',
+  'angle_revision': '8050079c116c993a5385737da12ad871c4f53fed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -376,7 +376,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': '82d6d5debc352225088c5d78ff2168955fbc204f',
+  'devtools_frontend_revision': '50a84ca8e5b556e27bb285477f21a99f0ccb7050',
   # 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.
@@ -412,7 +412,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': '6d5542d7036730f268bf1deb2d566ba815436caf',
+  'dawn_revision': '1eed2c2e3638a0c2fbb9494549798cea096d15e5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -440,7 +440,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'c495cf830fe2fc59de34beacfe0356f4405f9c0d',
+  'nearby_revision': '0a8f1f1c39af06dff550d8ca96c6e087994155b7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -460,7 +460,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.
-  'libunwind_revision':    'c38cbd4028118906853431635e8dbb74a90ad0a2',
+  'libunwind_revision':    'a097a1ada6ca6a585f90299b934695dffb88914c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -773,7 +773,7 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + '47ba73287a73719d6f61024b639a86c09e0ba395',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'e1675f6b5f878818b9ecc355be8186061867c170',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1693,7 +1693,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3cb199eedd80578a2c0cf432035f929ee8a04d44',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@30403bafe4dc5b14e820ae922278455053e8f7e2',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1805,7 +1805,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@635d7982b4eb830d18a166ef5a48a1d43c71d57a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fe63cb6846edc3c548739c989e6a85a6fa568bde',
     'condition': 'checkout_src_internal',
   },
 
@@ -1846,7 +1846,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'pTtXmCT6pCQDy2Z0lHJNTWGGH5n-fjbP9MSHP2JQ62YC',
+        'version': 'nn5KvAAoMD72nH1CAMYRwvmbBl3GUz-LY98pkeumiT0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 8648e65..433fa73 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -4779,7 +4779,12 @@
                                       input_api.re.MULTILINE)
     extension_re = input_api.re.compile(r'\.[a-z]+$')
     errors = []
+    config_h_file = input_api.os_path.join('build', 'build_config.h')
     for f in input_api.AffectedFiles(include_deletes=False):
+        # The build-config macros are allowed to be used in build_config.h
+        # without including itself.
+        if f.LocalPath() == config_h_file:
+            continue
         if not f.LocalPath().endswith(
             ('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
             continue
@@ -4882,8 +4887,11 @@
 def CheckForDeprecatedOSMacros(input_api, output_api):
     """Check all affected files for invalid OS macros."""
     bad_macros = []
+    # The OS_ macros are allowed to be used in build/build_config.h.
+    config_h_file = input_api.os_path.join('build', 'build_config.h')
     for f in input_api.AffectedSourceFiles(None):
-        if not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md')):
+        if not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md')) \
+                and f.LocalPath() != config_h_file:
             bad_macros.extend(_CheckForDeprecatedOSMacrosInFile(input_api, f))
 
     if not bad_macros:
diff --git a/WATCHLISTS b/WATCHLISTS
index b02a189..bd6ef14 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2571,8 +2571,7 @@
     'drive_resource_metadata': ['hashimoto+watch@chromium.org'],
     'eme': ['eme-reviews@chromium.org'],
     'enterprise_connectors': ['dpr-eng@google.com'],
-    'enterprise_reporting_private': ['domfc+watch@chromium.org',
-                                     'anthonyvd+watch@chormium.org'],
+    'enterprise_reporting_private': ['anthonyvd+watch@chormium.org'],
     'exo': ['crostini-ui+exo@chromium.org',
             'yhanada+watchexo@chromium.org'],
     'explore_sites': ['chili+watch@chromium.org',
diff --git a/android_webview/browser/gfx/output_surface_provider_webview.cc b/android_webview/browser/gfx/output_surface_provider_webview.cc
index a990ecc60..52c859cc 100644
--- a/android_webview/browser/gfx/output_surface_provider_webview.cc
+++ b/android_webview/browser/gfx/output_surface_provider_webview.cc
@@ -29,6 +29,7 @@
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace android_webview {
@@ -49,7 +50,8 @@
   if (surface && context)
     return std::make_pair(std::move(surface), std::move(context));
 
-  surface = gl::init::CreateOffscreenGLSurface(gfx::Size(1, 1));
+  surface = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                               gfx::Size(1, 1));
   DCHECK(surface);
   // Allow context and surface to be null and just fallback to
   // not having any real EGL context in that case instead of crashing.
diff --git a/android_webview/browser/gfx/test/fake_window.cc b/android_webview/browser/gfx/test/fake_window.cc
index d9acb36..f6c7c81 100644
--- a/android_webview/browser/gfx/test/fake_window.cc
+++ b/android_webview/browser/gfx/test/fake_window.cc
@@ -14,6 +14,7 @@
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace android_webview {
@@ -195,7 +196,8 @@
 
 void FakeWindow::InitializeOnRT(base::WaitableEvent* sync) {
   CheckCurrentlyOnRT();
-  surface_ = gl::init::CreateOffscreenGLSurface(surface_size_);
+  surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                surface_size_);
   DCHECK(surface_);
   DCHECK(surface_->GetHandle());
   context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
diff --git a/android_webview/browser/gfx/test/fake_window.h b/android_webview/browser/gfx/test/fake_window.h
index 7742f9d..328764a 100644
--- a/android_webview/browser/gfx/test/fake_window.h
+++ b/android_webview/browser/gfx/test/fake_window.h
@@ -95,7 +95,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> render_thread_loop_;
   scoped_refptr<gl::GLSurface> surface_;
   scoped_refptr<gl::GLContext> context_;
-  bool context_current_;
+  bool context_current_ = false;
 
   base::WeakPtrFactory<FakeWindow> weak_ptr_factory_{this};
 };
diff --git a/android_webview/browser/gfx/test/invalidate_test.cc b/android_webview/browser/gfx/test/invalidate_test.cc
index 925c0c3..96943cd 100644
--- a/android_webview/browser/gfx/test/invalidate_test.cc
+++ b/android_webview/browser/gfx/test/invalidate_test.cc
@@ -21,6 +21,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace android_webview {
@@ -279,8 +280,8 @@
     // explicitly.
     render_thread_manager_ = std::make_unique<RenderThreadManager>(
         base::ThreadTaskRunnerHandle::Get());
-
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size(100, 100));
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                  gfx::Size(100, 100));
     DCHECK(surface_);
     DCHECK(surface_->GetHandle());
     context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 5957e3f..2e0c9e9 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -110,7 +110,7 @@
 // invalidates the URL after).
 const base::Feature kWebViewSynthesizePageLoadOnlyOnInitialMainDocumentAccess{
     "WebViewSynthesizePageLoadOnlyOnInitialMainDocumentAccess",
-    base::FEATURE_DISABLED_BY_DEFAULT};
+    base::FEATURE_ENABLED_BY_DEFAULT};
 
 }  // namespace features
 }  // namespace android_webview
diff --git a/ash/app_list/test/app_list_test_helper.h b/ash/app_list/test/app_list_test_helper.h
index 8adec0c..598b1856 100644
--- a/ash/app_list/test/app_list_test_helper.h
+++ b/ash/app_list/test/app_list_test_helper.h
@@ -153,6 +153,7 @@
   std::vector<ash::AppListSearchResultCategory>* GetOrderedResultCategories();
 
   test::AppListTestModel* model() { return &model_; }
+  SearchModel* search_model() { return &search_model_; }
   TestAppListClient* app_list_client() { return app_list_client_.get(); }
 
  private:
diff --git a/ash/app_list/views/app_list_main_view_unittest.cc b/ash/app_list/views/app_list_main_view_unittest.cc
index f1bd064..4ef0c1c5 100644
--- a/ash/app_list/views/app_list_main_view_unittest.cc
+++ b/ash/app_list/views/app_list_main_view_unittest.cc
@@ -7,43 +7,35 @@
 #include <memory>
 #include <string>
 
-#include "ash/app_list/app_list_test_view_delegate.h"
+#include "ash/app_list/app_list_model_provider.h"
 #include "ash/app_list/model/app_list_test_model.h"
+#include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/apps_grid_view.h"
-#include "ash/app_list/views/apps_grid_view_test_api.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/app_list/views/page_switcher.h"
 #include "ash/app_list/views/paged_apps_grid_view.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/constants/ash_features.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
-#include "ash/public/cpp/test/test_app_list_color_provider.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/compositor/layer.h"
-#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/events/types/event_type.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/test/button_test_api.h"
-#include "ui/views/test/views_test_base.h"
 #include "ui/views/view_model.h"
-#include "ui/views/widget/widget.h"
 
 namespace ash {
-namespace {
-
-const size_t kInitialItems = 2;
-
-}  // namespace
 
 // Parameterized by ProductivityLauncher.
-class AppListMainViewTest : public views::ViewsTestBase,
+class AppListMainViewTest : public AshTestBase,
                             public testing::WithParamInterface<bool> {
  public:
   AppListMainViewTest() {
@@ -56,24 +48,22 @@
 
   // testing::Test overrides:
   void SetUp() override {
-    views::ViewsTestBase::SetUp();
-    zero_duration_mode_ =
-        std::make_unique<ui::ScopedAnimationDurationScaleMode>(
-            ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
+    AshTestBase::SetUp();
 
-    // Create, and show the app list is fullscreen apps grid state.
-    delegate_ = std::make_unique<test::AppListTestViewDelegate>();
-    app_list_view_ = new AppListView(delegate_.get());
-    app_list_view_->InitView(GetContext());
-    app_list_view_->Show(AppListViewState::kFullscreenAllApps,
-                         /*is_side_shelf=*/false);
-    EXPECT_TRUE(app_list_view_->GetWidget()->IsVisible());
+    // Create and show the app list in fullscreen apps grid state.
+    auto* helper = GetAppListTestHelper();
+    if (features::IsProductivityLauncherEnabled()) {
+      // Tablet mode uses a fullscreen AppListMainView.
+      Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+    } else {
+      helper->Show(GetPrimaryDisplay().id());
+      helper->GetAppListView()->SetState(AppListViewState::kFullscreenAllApps);
+    }
+    app_list_view_ = helper->GetAppListView();
   }
 
-  void TearDown() override {
-    app_list_view_->GetWidget()->Close();
-    zero_duration_mode_.reset();
-    views::ViewsTestBase::TearDown();
+  test::AppListTestModel* GetTestModel() {
+    return GetAppListTestHelper()->model();
   }
 
   // |point| is in |grid_view|'s coordinates.
@@ -88,29 +78,6 @@
                                  : static_cast<AppListItemView*>(iter->view);
   }
 
-  void SimulateKeyPress(ui::KeyboardCode key_code) {
-    ui::KeyEvent key_press(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE);
-    app_list_view_->GetWidget()->OnKeyEvent(&key_press);
-
-    ui::KeyEvent key_release(ui::ET_KEY_RELEASED, key_code, ui::EF_NONE);
-    app_list_view_->GetWidget()->OnKeyEvent(&key_release);
-  }
-
-  void SimulateClick(views::View* view) {
-    gfx::Point center = view->GetLocalBounds().CenterPoint();
-    views::View::ConvertPointToWidget(view, &center);
-
-    ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, center, center,
-                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
-                               ui::EF_RIGHT_MOUSE_BUTTON);
-    view->GetWidget()->OnMouseEvent(&press_event);
-
-    ui::MouseEvent release_event(
-        ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
-        ui::EF_LEFT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON);
-    view->GetWidget()->OnMouseEvent(&release_event);
-  }
-
   // |point| is in |grid_view|'s coordinates.
   AppListItemView* SimulateInitiateDrag(AppsGridView* grid_view,
                                         const gfx::Point& point) {
@@ -177,11 +144,10 @@
   AppListItemView* CreateAndOpenSingleItemFolder() {
     // Prepare single folder with a single item in it.
     AppListFolderItem* folder_item =
-        delegate_->GetTestModel()->CreateSingleItemFolder("single_item_folder",
-                                                          "single");
+        GetTestModel()->CreateSingleItemFolder("single_item_folder", "single");
     GetRootGridView()->Layout();
     EXPECT_EQ(folder_item,
-              delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
+              GetTestModel()->FindFolderItem("single_item_folder"));
     EXPECT_EQ(AppListFolderItem::kItemType, folder_item->GetItemType());
 
     EXPECT_EQ(1u, GetRootViewModel()->view_size());
@@ -191,7 +157,7 @@
 
     // Click on the folder to open it.
     EXPECT_FALSE(GetFolderView()->GetVisible());
-    SimulateClick(folder_item_view);
+    LeftClickOn(folder_item_view);
 
     EXPECT_TRUE(GetFolderView()->GetVisible());
 
@@ -229,11 +195,6 @@
     return dragged;
   }
 
-  void PressKeyInSearchBox(ui::KeyboardCode key_code) {
-    ui::KeyEvent press(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE);
-    search_box_view()->search_box()->OnKeyEvent(&press);
-  }
-
   void ClickButton(views::Button* button) {
     views::test::ButtonTestApi(button).NotifyClick(ui::MouseEvent(
         ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), base::TimeTicks(),
@@ -242,13 +203,7 @@
 
  protected:
   base::test::ScopedFeatureList feature_list_;
-  TestAppListColorProvider app_list_color_provider_;  // Needed by AppListView.
-  AshColorProvider ash_color_provider_;      // Needed by ContinueContainer.
-  AppListView* app_list_view_ = nullptr;     // Owned by native widget.
-  std::unique_ptr<test::AppListTestViewDelegate> delegate_;
-
- private:
-  std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_mode_;
+  AppListView* app_list_view_ = nullptr;  // Owned by native widget.
 };
 
 INSTANTIATE_TEST_SUITE_P(ProductivityLauncher,
@@ -257,39 +212,45 @@
 
 // Tests that the close button becomes invisible after close button is clicked.
 TEST_P(AppListMainViewTest, CloseButtonInvisibleAfterCloseButtonClicked) {
-  PressKeyInSearchBox(ui::VKEY_A);
+  PressAndReleaseKey(ui::VKEY_A);
   ClickButton(search_box_view()->close_button());
   EXPECT_FALSE(search_box_view()->close_button()->GetVisible());
 }
 
 // Tests that the search box becomes empty after close button is clicked.
 TEST_P(AppListMainViewTest, SearchBoxEmptyAfterCloseButtonClicked) {
-  PressKeyInSearchBox(ui::VKEY_A);
+  PressAndReleaseKey(ui::VKEY_A);
   ClickButton(search_box_view()->close_button());
   EXPECT_TRUE(search_box_view()->search_box()->GetText().empty());
 }
 
 // Tests that the search box is no longer active after close button is clicked.
 TEST_P(AppListMainViewTest, SearchBoxActiveAfterCloseButtonClicked) {
-  PressKeyInSearchBox(ui::VKEY_A);
+  PressAndReleaseKey(ui::VKEY_A);
   ClickButton(search_box_view()->close_button());
   EXPECT_FALSE(search_box_view()->is_search_box_active());
 }
 
 // Tests changing the AppListModel when switching profiles.
 TEST_P(AppListMainViewTest, ModelChanged) {
-  delegate_->GetTestModel()->PopulateApps(kInitialItems);
+  const size_t kInitialItems = 2;
+  GetTestModel()->PopulateApps(kInitialItems);
   EXPECT_EQ(kInitialItems, GetRootViewModel()->view_size());
 
-  // The model is owned by a profile keyed service, which is never destroyed
-  // until after profile switching.
-  std::unique_ptr<AppListModel> old_model(delegate_->ReleaseTestModel());
-  std::unique_ptr<SearchModel> old_search_model(
-      delegate_->ReleaseTestSearchModel());
+  AppListModel* old_model = GetAppListTestHelper()->model();
+  SearchModel* old_search_model = GetAppListTestHelper()->search_model();
 
+  // Simulate a profile switch (which switches the app list models).
+  auto search_model = std::make_unique<SearchModel>();
+  auto model = std::make_unique<test::AppListTestModel>();
   const size_t kReplacementItems = 5;
-  delegate_->ReplaceTestModel(kReplacementItems);
+  model->PopulateApps(kReplacementItems);
+  AppListModelProvider::Get()->SetActiveModel(model.get(), search_model.get());
   EXPECT_EQ(kReplacementItems, GetRootViewModel()->view_size());
+
+  // Replace the old model so observers on `model` are removed before test
+  // shutdown.
+  AppListModelProvider::Get()->SetActiveModel(old_model, old_search_model);
 }
 
 // Tests dragging an item out of a single item folder and dropping it onto the
@@ -301,7 +262,7 @@
   // Number of apps to populate. Should provide more than 1 page of apps (5*4 =
   // 20).
   const size_t kNumApps = 30;
-  delegate_->GetTestModel()->PopulateApps(kNumApps);
+  GetTestModel()->PopulateApps(kNumApps);
   GetRootGridView()->Layout();
 
   EXPECT_EQ(1u, GetFolderViewModel()->view_size());
@@ -321,7 +282,7 @@
   // The folder should not be destroyed.
   EXPECT_EQ(kNumApps + 1, GetRootViewModel()->view_size());
   AppListFolderItem* const folder_item =
-      delegate_->GetTestModel()->FindFolderItem("single_item_folder");
+      GetTestModel()->FindFolderItem("single_item_folder");
   ASSERT_TRUE(folder_item);
   EXPECT_EQ(1u, folder_item->item_list()->item_count());
 }
@@ -336,7 +297,7 @@
   // Now add an item to the model, not in any folder, e.g., as if by Sync.
   EXPECT_TRUE(GetRootGridView()->has_dragged_item());
   EXPECT_TRUE(GetFolderGridView()->has_dragged_item());
-  delegate_->GetTestModel()->CreateAndAddItem("Extra");
+  GetTestModel()->CreateAndAddItem("Extra");
 
   // The drag operation should get canceled.
   EXPECT_FALSE(GetRootGridView()->has_dragged_item());
@@ -358,7 +319,7 @@
   std::string folder_id = folder_item_view->item()->id();
 
   // Add another top level app.
-  delegate_->GetTestModel()->PopulateApps(1);
+  GetTestModel()->PopulateApps(1);
   gfx::Point drag_point = folder_item_view->bounds().CenterPoint();
 
   views::View::ConvertPointToTarget(GetRootGridView(), GetFolderGridView(),
@@ -375,7 +336,7 @@
   EXPECT_EQ(2u, GetRootViewModel()->view_size());
   EXPECT_EQ(folder_id, GetRootGridView()->GetItemViewAt(0)->item()->id());
   AppListFolderItem* const folder_item =
-      delegate_->GetTestModel()->FindFolderItem("single_item_folder");
+      GetTestModel()->FindFolderItem("single_item_folder");
   ASSERT_TRUE(folder_item);
   EXPECT_EQ(1u, folder_item->item_list()->item_count());
 }
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index 6ced33d9..c4b63963 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -13,7 +13,6 @@
 #include <utility>
 #include <vector>
 
-#include "ash/app_list/app_list_metrics.h"
 #include "ash/app_list/app_list_test_view_delegate.h"
 #include "ash/app_list/model/app_list_test_model.h"
 #include "ash/app_list/model/search/search_box_model.h"
@@ -36,7 +35,6 @@
 #include "ash/app_list/views/search_result_container_view.h"
 #include "ash/app_list/views/search_result_list_view.h"
 #include "ash/app_list/views/search_result_page_view.h"
-#include "ash/app_list/views/search_result_suggestion_chip_view.h"
 #include "ash/app_list/views/search_result_tile_item_list_view.h"
 #include "ash/app_list/views/search_result_tile_item_view.h"
 #include "ash/app_list/views/search_result_view.h"
@@ -44,7 +42,6 @@
 #include "ash/constants/ash_features.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/pagination/pagination_model.h"
 #include "ash/public/cpp/test/test_app_list_color_provider.h"
@@ -52,14 +49,12 @@
 #include "ash/style/ash_color_provider.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/icu_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/models/simple_menu_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/presentation_time_recorder.h"
@@ -73,9 +68,12 @@
 #include "ui/views/view_model.h"
 
 namespace ash {
-namespace test {
 namespace {
 
+using test::AppListTestModel;
+using test::AppListTestViewDelegate;
+using test::AppsGridViewTestApi;
+
 constexpr int kInitialItems = 34;
 
 constexpr int kMaxItemsPerFolderPage = AppListFolderView::kMaxFolderColumns *
@@ -786,8 +784,8 @@
     view_->SetState(state);
   }
 
-  void Show(bool is_side_shelf = false) {
-    view_->Show(AppListViewState::kPeeking, is_side_shelf);
+  void Show() {
+    view_->Show(AppListViewState::kPeeking, /*is_side_shelf=*/false);
   }
 
   SearchResultTileItemListView* GetSearchResultTileItemListView() {
@@ -840,7 +838,7 @@
     }
 
     // Adding results will schedule Update().
-    RunPendingMessages();
+    base::RunLoop().RunUntilIdle();
   }
 
   // Add search results for test on embedded Assistant UI.
@@ -867,7 +865,7 @@
     }
 
     // Adding results will schedule Update().
-    RunPendingMessages();
+    base::RunLoop().RunUntilIdle();
   }
 
   void ClearSearchResults() { GetSearchModel()->results()->DeleteAll(); }
@@ -881,7 +879,7 @@
     result->SetTitle(ASCIIToUTF16(title));
     result->set_best_match(true);
     GetSearchModel()->results()->Add(std::move(result));
-    RunPendingMessages();
+    base::RunLoop().RunUntilIdle();
   }
 
   int GetOpenFirstSearchResultCount() {
@@ -3050,7 +3048,7 @@
        RegularLandscapeScreenAtMinPreferredVerticalMargin) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
       /*row_count=*/4, /*tile_height=*/120, /*tile_margins=*/8,
-      /*large_height=*/false);
+      /*is_large_height=*/false);
   EXPECT_EQ(689, window_height);
   const gfx::Size window_size = gfx::Size(800, window_height);
   GetContext()->SetBounds(gfx::Rect(window_size));
@@ -3085,7 +3083,7 @@
        RegularLandscapeScreenWithRemovedRows) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
                                 /*row_count=*/4, /*tile_height=*/120,
-                                /*tile_margins=*/8, /*large_height=*/false) -
+                                /*tile_margins=*/8, /*is_large_height=*/false) -
                             4;
   EXPECT_EQ(685, window_height);
   const gfx::Size window_size = gfx::Size(800, window_height);
@@ -3121,7 +3119,7 @@
        RegularLandscapeScreenAtMaxPreferredVerticalMargin) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
       /*row_count=*/4, /*tile_height=*/120, /*tile_margins=*/96,
-      /*large_height=*/true);
+      /*is_large_height=*/true);
   EXPECT_EQ(1024, window_height);
   const gfx::Size window_size = gfx::Size(1100, window_height);
   GetContext()->SetBounds(gfx::Rect(window_size));
@@ -3156,7 +3154,7 @@
        RegularLandscapeScreenWithAddedRows) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
                                 /*row_count=*/4, /*tile_height=*/120,
-                                /*tile_margins=*/96, /*large_height=*/true) +
+                                /*tile_margins=*/96, /*is_large_height=*/true) +
                             6;
   EXPECT_EQ(1030, window_height);
   const gfx::Size window_size = gfx::Size(1100, window_height);
@@ -3222,7 +3220,7 @@
        RegularPortraitScreenAtMinPreferredVerticalMargin) {
   int window_height = GetExpectedScreenSizeForProductivityLauncher(
       /*row_count=*/5, /*tile_height=*/120, /*tile_margins=*/8,
-      /*large_height=*/true);
+      /*is_large_height=*/true);
   // window_height = 860;
   EXPECT_EQ(868, window_height);
   const gfx::Size window_size = gfx::Size(700, window_height);
@@ -3259,7 +3257,7 @@
   const int window_height =
       GetExpectedScreenSizeForProductivityLauncher(
           /*row_count=*/5, /*tile_height=*/120, /*tile_margins=*/8,
-          /*large_height=*/true) -
+          /*is_large_height=*/true) -
       8;
   EXPECT_EQ(860, window_height);
   const gfx::Size window_size = gfx::Size(700, window_height);
@@ -3295,7 +3293,7 @@
        RegularPortraitScreenAtMaxPreferredVerticalMargin) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
       /*row_count=*/5, /*tile_height=*/120, /*tile_margins=*/96,
-      /*large_height=*/true);
+      /*is_large_height=*/true);
   EXPECT_EQ(1270, window_height);
   const gfx::Size window_size = gfx::Size(1200, window_height);
   GetContext()->SetBounds(gfx::Rect(window_size));
@@ -3330,7 +3328,7 @@
   const int window_height =
       GetExpectedScreenSizeForProductivityLauncher(
           /*row_count=*/5, /*tile_height=*/120, /*tile_margins=*/96,
-          /*large_height=*/true) +
+          /*is_large_height=*/true) +
       4;
   EXPECT_EQ(1274, window_height);
   const gfx::Size window_size = gfx::Size(1200, window_height);
@@ -3395,7 +3393,7 @@
        DenseLandscapeScreenAtMinPreferredVerticalMargin) {
   const int window_height = GetExpectedScreenSizeForProductivityLauncher(
       /*row_count=*/4, /*tile_height=*/88, /*tile_margins=*/8,
-      /*large_height=*/false);
+      /*is_large_height=*/false);
   EXPECT_EQ(552, window_height);
   const gfx::Size window_size = gfx::Size(800, window_height);
   GetContext()->SetBounds(gfx::Rect(window_size));
@@ -4061,5 +4059,4 @@
 }
 
 }  // namespace
-}  // namespace test
 }  // namespace ash
diff --git a/ash/components/arc/net/always_on_vpn_manager.cc b/ash/components/arc/net/always_on_vpn_manager.cc
index 5a356d1..1a7abe2 100644
--- a/ash/components/arc/net/always_on_vpn_manager.cc
+++ b/ash/components/arc/net/always_on_vpn_manager.cc
@@ -33,7 +33,7 @@
       registrar_.prefs()->GetString(prefs::kAlwaysOnVpnPackage);
   bool lockdown = registrar_.prefs()->GetBoolean(prefs::kAlwaysOnVpnLockdown);
   if (lockdown && !package.empty()) {
-    chromeos::NetworkHandler::Get()
+    ash::NetworkHandler::Get()
         ->network_configuration_handler()
         ->SetManagerProperty(shill::kAlwaysOnVpnPackageProperty,
                              base::Value(std::string()));
@@ -49,7 +49,7 @@
     always_on_vpn_package =
         registrar_.prefs()->GetString(prefs::kAlwaysOnVpnPackage);
   }
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->network_configuration_handler()
       ->SetManagerProperty(shill::kAlwaysOnVpnPackageProperty,
                            base::Value(always_on_vpn_package));
diff --git a/ash/components/arc/net/arc_net_host_impl.cc b/ash/components/arc/net/arc_net_host_impl.cc
index 792dd05..bc841c9 100644
--- a/ash/components/arc/net/arc_net_host_impl.cc
+++ b/ash/components/arc/net/arc_net_host_impl.cc
@@ -66,21 +66,20 @@
   return !inet_ntop(family, data.data(), buf, sizeof(buf)) ? "" : buf;
 }
 
-chromeos::NetworkStateHandler* GetStateHandler() {
-  return chromeos::NetworkHandler::Get()->network_state_handler();
+ash::NetworkStateHandler* GetStateHandler() {
+  return ash::NetworkHandler::Get()->network_state_handler();
 }
 
 ash::ManagedNetworkConfigurationHandler* GetManagedConfigurationHandler() {
-  return chromeos::NetworkHandler::Get()
-      ->managed_network_configuration_handler();
+  return ash::NetworkHandler::Get()->managed_network_configuration_handler();
 }
 
 ash::NetworkConnectionHandler* GetNetworkConnectionHandler() {
-  return chromeos::NetworkHandler::Get()->network_connection_handler();
+  return ash::NetworkHandler::Get()->network_connection_handler();
 }
 
 ash::NetworkProfileHandler* GetNetworkProfileHandler() {
-  return chromeos::NetworkHandler::Get()->network_profile_handler();
+  return ash::NetworkHandler::Get()->network_profile_handler();
 }
 
 const ash::NetworkProfile* GetNetworkProfile() {
@@ -407,7 +406,7 @@
 // vector of mojo NetworkConfiguration objects.
 std::vector<arc::mojom::NetworkConfigurationPtr> TranslateNetworkStates(
     const std::string& arc_vpn_path,
-    const chromeos::NetworkStateHandler::NetworkStateList& network_states,
+    const ash::NetworkStateHandler::NetworkStateList& network_states,
     const std::map<std::string, base::Value>& shill_network_properties,
     const std::vector<patchpanel::NetworkDevice>& devices) {
   // Move the devices vector to a map keyed by its physical interface name in
@@ -602,7 +601,7 @@
 void ArcNetHostImpl::OnConnectionReady() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (chromeos::NetworkHandler::IsInitialized()) {
+  if (ash::NetworkHandler::IsInitialized()) {
     GetStateHandler()->AddObserver(this, FROM_HERE);
     GetNetworkConnectionHandler()->AddObserver(this);
     observing_network_state_ = true;
@@ -657,7 +656,7 @@
   ash::NetworkTypePattern network_pattern =
       ash::onc::NetworkTypePatternFromOncType(onc::network_type::kWiFi);
 
-  chromeos::NetworkStateHandler::NetworkStateList network_states;
+  ash::NetworkStateHandler::NetworkStateList network_states;
   GetStateHandler()->GetNetworkListByType(
       network_pattern, configured_only, !configured_only /* visible_only */,
       kGetNetworksListLimit, &network_states);
@@ -673,7 +672,7 @@
     GetNetworksCallback callback,
     const std::vector<patchpanel::NetworkDevice>& devices) {
   // Retrieve list of currently active networks.
-  chromeos::NetworkStateHandler::NetworkStateList network_states;
+  ash::NetworkStateHandler::NetworkStateList network_states;
   GetStateHandler()->GetActiveNetworkListByType(
       ash::NetworkTypePattern::Default(), &network_states);
 
@@ -845,9 +844,9 @@
   auto state =
       GetStateHandler()->GetTechnologyState(ash::NetworkTypePattern::WiFi());
   // WiFi can't be enabled or disabled in these states.
-  if ((state == chromeos::NetworkStateHandler::TECHNOLOGY_PROHIBITED) ||
-      (state == chromeos::NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) ||
-      (state == chromeos::NetworkStateHandler::TECHNOLOGY_UNAVAILABLE)) {
+  if ((state == ash::NetworkStateHandler::TECHNOLOGY_PROHIBITED) ||
+      (state == ash::NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) ||
+      (state == ash::NetworkStateHandler::TECHNOLOGY_UNAVAILABLE)) {
     NET_LOG(ERROR) << "SetWifiEnabledState failed due to WiFi state: " << state;
     std::move(callback).Run(false);
     return;
@@ -885,7 +884,7 @@
 }
 
 std::string ArcNetHostImpl::LookupArcVpnServicePath() {
-  chromeos::NetworkStateHandler::NetworkStateList state_list;
+  ash::NetworkStateHandler::NetworkStateList state_list;
   GetStateHandler()->GetNetworkListByType(
       ash::NetworkTypePattern::VPN(), true /* configured_only */,
       false /* visible_only */, kGetNetworksListLimit, &state_list);
@@ -1305,7 +1304,7 @@
   if (!IsActiveNetworkState(network))
     return;
 
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->network_configuration_handler()
       ->GetShillProperties(
           network->path(),
diff --git a/ash/components/arc/net/arc_net_host_impl.h b/ash/components/arc/net/arc_net_host_impl.h
index 03351e6..7eb77ea 100644
--- a/ash/components/arc/net/arc_net_host_impl.h
+++ b/ash/components/arc/net/arc_net_host_impl.h
@@ -213,7 +213,7 @@
       base::OnceCallback<void(const std::string&)> callback,
       const std::string& error_name);
 
-  // Callback for chromeos::NetworkHandler::GetShillProperties
+  // Callback for ash::NetworkHandler::GetShillProperties
   void ReceiveShillProperties(const std::string& service_path,
                               absl::optional<base::Value> shill_properties);
 
diff --git a/ash/components/audio/cros_audio_config_impl.cc b/ash/components/audio/cros_audio_config_impl.cc
index 93809b76..9ded779 100644
--- a/ash/components/audio/cros_audio_config_impl.cc
+++ b/ash/components/audio/cros_audio_config_impl.cc
@@ -96,6 +96,17 @@
   }
 };
 
+void CrosAudioConfigImpl::SetOutputVolumePercent(int8_t volume) {
+  CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
+  audio_handler->SetOutputVolumePercent(volume);
+
+  // If the volume is above certain level and it's muted, it should be unmuted.
+  if (audio_handler->IsOutputMuted() &&
+      volume > audio_handler->GetOutputDefaultVolumeMuteThreshold()) {
+    audio_handler->SetOutputMute(false);
+  }
+}
+
 void CrosAudioConfigImpl::OnOutputNodeVolumeChanged(uint64_t node_id,
                                                     int volume) {
   NotifyObserversAudioSystemPropertiesChanged();
diff --git a/ash/components/audio/cros_audio_config_impl.h b/ash/components/audio/cros_audio_config_impl.h
index 06f9319..3c0eb340 100644
--- a/ash/components/audio/cros_audio_config_impl.h
+++ b/ash/components/audio/cros_audio_config_impl.h
@@ -25,6 +25,7 @@
   mojom::MuteState GetOutputMuteState() const override;
   void GetAudioDevices(
       std::vector<mojom::AudioDevicePtr>* output_devices_out) const override;
+  void SetOutputVolumePercent(int8_t volume) override;
 
   // CrasAudioHandler::AudioObserver:
   void OnOutputNodeVolumeChanged(uint64_t node_id, int volume) override;
diff --git a/ash/components/audio/cros_audio_config_impl_unittest.cc b/ash/components/audio/cros_audio_config_impl_unittest.cc
index a9776c1..a7b6827f 100644
--- a/ash/components/audio/cros_audio_config_impl_unittest.cc
+++ b/ash/components/audio/cros_audio_config_impl_unittest.cc
@@ -21,6 +21,9 @@
 namespace ash::audio_config {
 
 const uint8_t kTestOutputVolumePercent = 80u;
+const uint8_t kTestUnderMuteThreshholdVolumePercent = 0u;
+const uint8_t kTestOverMaxOutputVolumePercent = 105u;
+const int8_t kTestUnderMinOutputVolumePercent = -5;
 
 const int8_t kDefaultOutputVolumePercent =
     AudioDevicesPrefHandler::kDefaultOutputVolumePercent;
@@ -101,7 +104,7 @@
   }
 
   void SetOutputVolumePercent(uint8_t volume_percent) {
-    cras_audio_handler_->SetOutputVolumePercent(volume_percent);
+    remote_->SetOutputVolumePercent(volume_percent);
     base::RunLoop().RunUntilIdle();
   }
 
@@ -170,7 +173,7 @@
   FakeCrasAudioClient* fake_cras_audio_client_;
 };
 
-TEST_F(CrosAudioConfigImplTest, GetOutputVolumePercent) {
+TEST_F(CrosAudioConfigImplTest, GetSetOutputVolumePercent) {
   std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
   // |fake_observer| count is first incremented in Observe() method.
   ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
@@ -186,6 +189,75 @@
                 ->output_volume_percent);
 }
 
+TEST_F(CrosAudioConfigImplTest, GetSetOutputVolumePercentMuteThresholdTest) {
+  std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
+
+  // |fake_observer| count is first incremented in Observe() method.
+  ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
+  ASSERT_TRUE(fake_observer->last_audio_system_properties_.has_value());
+  ASSERT_EQ(kDefaultOutputVolumePercent,
+            fake_observer->last_audio_system_properties_.value()
+                ->output_volume_percent);
+
+  // Test setting volume over mute threshold when muted.
+  SetOutputMuteState(mojom::MuteState::kMutedByUser);
+  ASSERT_EQ(2u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(
+      mojom::MuteState::kMutedByUser,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+
+  SetOutputVolumePercent(kDefaultOutputVolumePercent);
+
+  // |fake_observer| should be notified twice due to mute state changing when
+  // setting volume over the mute threshold.
+  ASSERT_EQ(4u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(
+      mojom::MuteState::kNotMuted,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+  EXPECT_EQ(kDefaultOutputVolumePercent,
+            fake_observer->last_audio_system_properties_.value()
+                ->output_volume_percent);
+
+  // Test setting volume under mute threshold when muted.
+  SetOutputMuteState(mojom::MuteState::kMutedByUser);
+  ASSERT_EQ(5u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(
+      mojom::MuteState::kMutedByUser,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+
+  SetOutputVolumePercent(kTestUnderMuteThreshholdVolumePercent);
+  ASSERT_EQ(6u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(
+      mojom::MuteState::kMutedByUser,
+      fake_observer->last_audio_system_properties_.value()->output_mute_state);
+  EXPECT_EQ(kTestUnderMuteThreshholdVolumePercent,
+            fake_observer->last_audio_system_properties_.value()
+                ->output_volume_percent);
+}
+
+TEST_F(CrosAudioConfigImplTest, GetSetOutputVolumePercentVolumeBoundariesTest) {
+  std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
+
+  // |fake_observer| count is first incremented in Observe() method.
+  ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
+  ASSERT_TRUE(fake_observer->last_audio_system_properties_.has_value());
+  ASSERT_EQ(kDefaultOutputVolumePercent,
+            fake_observer->last_audio_system_properties_.value()
+                ->output_volume_percent);
+
+  // Test setting volume over max volume.
+  SetOutputVolumePercent(kTestOverMaxOutputVolumePercent);
+  ASSERT_EQ(2u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(100u, fake_observer->last_audio_system_properties_.value()
+                      ->output_volume_percent);
+
+  // Test setting volume under min volume.
+  SetOutputVolumePercent(kTestUnderMinOutputVolumePercent);
+  ASSERT_EQ(3u, fake_observer->num_properties_updated_calls_);
+  EXPECT_EQ(0u, fake_observer->last_audio_system_properties_.value()
+                    ->output_volume_percent);
+}
+
 TEST_F(CrosAudioConfigImplTest, GetOutputMuteState) {
   std::unique_ptr<FakeAudioSystemPropertiesObserver> fake_observer = Observe();
   ASSERT_EQ(1u, fake_observer->num_properties_updated_calls_);
diff --git a/ash/components/audio/public/mojom/cros_audio_config.mojom b/ash/components/audio/public/mojom/cros_audio_config.mojom
index e39bd11..f5cd72e 100644
--- a/ash/components/audio/public/mojom/cros_audio_config.mojom
+++ b/ash/components/audio/public/mojom/cros_audio_config.mojom
@@ -87,4 +87,8 @@
   // To stop observing, disconnect |observer|.
   ObserveAudioSystemProperties(
     pending_remote<AudioSystemPropertiesObserver> observer);
+
+  // Sets the volume of the active output device. If |volume| is above a
+  // threshold and the device is muted, it's unmuted.
+  SetOutputVolumePercent(int8 volume);
 };
\ No newline at end of file
diff --git a/ash/components/device_activity/device_activity_controller.cc b/ash/components/device_activity/device_activity_controller.cc
index 18ca7d2..f376c3e 100644
--- a/ash/components/device_activity/device_activity_controller.cc
+++ b/ash/components/device_activity/device_activity_controller.cc
@@ -243,8 +243,8 @@
       psm_device_active_secret, chrome_passed_device_params_, local_state));
 
   da_client_network_ = std::make_unique<DeviceActivityClient>(
-      chromeos::NetworkHandler::Get()->network_state_handler(),
-      url_loader_factory, std::make_unique<PsmDelegateImpl>(),
+      NetworkHandler::Get()->network_state_handler(), url_loader_factory,
+      std::make_unique<PsmDelegateImpl>(),
       std::make_unique<base::RepeatingTimer>(), kFresnelBaseUrl,
       google_apis::GetFresnelAPIKey(), std::move(use_cases));
 }
diff --git a/ash/components/hid_detection/bluetooth_hid_detector_impl.cc b/ash/components/hid_detection/bluetooth_hid_detector_impl.cc
index 5a3248b3..8f90bc1 100644
--- a/ash/components/hid_detection/bluetooth_hid_detector_impl.cc
+++ b/ash/components/hid_detection/bluetooth_hid_detector_impl.cc
@@ -6,6 +6,7 @@
 #include "ash/public/cpp/bluetooth_config_service.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/device_event_log/device_event_log.h"
+#include "hid_detection_utils.h"
 
 namespace ash::hid_detection {
 namespace {
@@ -116,6 +117,7 @@
                  << input_devices_status.keyboard_is_missing;
   input_devices_status_ = input_devices_status;
   state_ = kStarting;
+  num_pairing_attempts_ = 0;
   GetBluetoothConfigService(
       cros_bluetooth_config_remote_.BindNewPipeAndPassReceiver());
   cros_bluetooth_config_remote_->ObserveSystemProperties(
@@ -129,6 +131,7 @@
       << "HID detection is inactive.";
   HID_LOG(EVENT) << "Stopping Bluetooth HID detection, |is_using_bluetooth|: "
                  << is_using_bluetooth;
+  hid_detection::RecordBluetoothPairingAttempts(num_pairing_attempts_);
   state_ = kNotStarted;
   cros_bluetooth_config_remote_->SetBluetoothHidDetectionInactive(
       is_using_bluetooth);
@@ -350,6 +353,7 @@
 
   HID_LOG(EVENT) << "Pairing with device with id: "
                  << current_pairing_device_.value()->id;
+  ++num_pairing_attempts_;
   device_pairing_handler_remote_->PairDevice(
       current_pairing_device_.value()->id,
       device_pairing_delegate_receiver_.BindNewPipeAndPassRemote(),
diff --git a/ash/components/hid_detection/bluetooth_hid_detector_impl.h b/ash/components/hid_detection/bluetooth_hid_detector_impl.h
index c41183a..63efb3b 100644
--- a/ash/components/hid_detection/bluetooth_hid_detector_impl.h
+++ b/ash/components/hid_detection/bluetooth_hid_detector_impl.h
@@ -135,6 +135,11 @@
   InputDevicesStatus input_devices_status_;
   State state_ = kNotStarted;
 
+  // This is a counter used to emit a count of the number of pairing attempts
+  // that occur while HID detection is active. The count is reset to zero each
+  // time a HID detection session is started.
+  size_t num_pairing_attempts_ = 0;
+
   mojo::Remote<chromeos::bluetooth_config::mojom::CrosBluetoothConfig>
       cros_bluetooth_config_remote_;
   mojo::Receiver<chromeos::bluetooth_config::mojom::SystemPropertiesObserver>
diff --git a/ash/components/hid_detection/bluetooth_hid_detector_impl_unittest.cc b/ash/components/hid_detection/bluetooth_hid_detector_impl_unittest.cc
index e5fd156..648b8e19 100644
--- a/ash/components/hid_detection/bluetooth_hid_detector_impl_unittest.cc
+++ b/ash/components/hid_detection/bluetooth_hid_detector_impl_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chromeos/services/bluetooth_config/fake_adapter_state_controller.h"
@@ -201,6 +202,15 @@
     }
   }
 
+  void AssertBluetoothPairingAttemptsCount(int bucket,
+                                           int count,
+                                           int total_count) {
+    histogram_tester_.ExpectBucketCount(
+        "OOBE.HidDetectionScreen.BluetoothPairingAttempts", bucket, count);
+    histogram_tester_.ExpectTotalCount(
+        "OOBE.HidDetectionScreen.BluetoothPairingAttempts", total_count);
+  }
+
  private:
   void UpdateDiscoveredDevicesProviderDevices() {
     std::vector<BluetoothDevicePropertiesPtr> unpaired_devices;
@@ -222,6 +232,7 @@
 
   base::test::TaskEnvironment task_environment_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  base::HistogramTester histogram_tester_;
 
   std::vector<BluetoothDevicePropertiesPtr> unpaired_devices_;
   size_t num_devices_created_ = 0u;
@@ -261,6 +272,8 @@
   StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
   EXPECT_FALSE(IsDiscoverySessionActive());
   EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/0, /*count=*/1,
+                                      /*total_count=*/1);
 
   // Trigger an OnPropertiesUpdated() call. Nothing should happen.
   TriggerOnPropertiesUpdatedCall();
@@ -403,6 +416,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevices_TypeNotMissing) {
@@ -424,6 +441,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevices_NoTypeMissing) {
@@ -459,6 +480,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest,
@@ -511,6 +536,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevices_BatchAfterStartingDetection) {
@@ -557,6 +586,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboardPointerCombo),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest,
@@ -610,6 +643,10 @@
   AssertBluetoothHidDetectionStatus(
       /*current_pairing_device=*/absl::nullopt,
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, DisconnectDevice) {
@@ -654,6 +691,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, ConnectDeviceTypeDuringPairing) {
@@ -704,6 +745,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest,
@@ -746,6 +791,10 @@
   AssertBluetoothHidDetectionStatus(
       /*current_pairing_device=*/absl::nullopt,
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AdapterDisablesDuringPairing) {
@@ -804,6 +853,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
       BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, DetectionStopsStartsDuringPairing) {
@@ -835,6 +888,8 @@
 
   // Stop detection.
   StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
   EXPECT_FALSE(IsDiscoverySessionActive());
   EXPECT_EQ(3u, delegate1->num_bluetooth_hid_status_changed_calls());
   AssertBluetoothHidDetectionStatus(
@@ -861,6 +916,10 @@
   AssertBluetoothHidDetectionStatus(
       BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
       BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/2,
+                                      /*total_count=*/2);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevices_UnsupportedAuthorizations) {
@@ -914,6 +973,10 @@
   AssertBluetoothHidDetectionStatus(
       /*current_pairing_device=*/absl::nullopt,
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/3, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevice_AuthorizePairingAuth) {
@@ -954,6 +1017,10 @@
   AssertBluetoothHidDetectionStatus(
       /*current_pairing_device=*/absl::nullopt,
       /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 TEST_F(BluetoothHidDetectorImplTest, AddDevice_DisplayCodeAuths) {
@@ -1049,6 +1116,10 @@
   EXPECT_EQ(18u, delegate->num_bluetooth_hid_status_changed_calls());
   AssertBluetoothHidDetectionStatus(/*current_pairing_device=*/absl::nullopt,
                                     /*pairing_state=*/absl::nullopt);
+
+  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
+  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
+                                      /*total_count=*/1);
 }
 
 }  // namespace ash::hid_detection
diff --git a/ash/components/phonehub/tether_controller_impl_unittest.cc b/ash/components/phonehub/tether_controller_impl_unittest.cc
index 287d58cc..9fc386e 100644
--- a/ash/components/phonehub/tether_controller_impl_unittest.cc
+++ b/ash/components/phonehub/tether_controller_impl_unittest.cc
@@ -183,7 +183,7 @@
 
   void TearDown() override {
     controller_->RemoveObserver(&fake_observer_);
-    chromeos::NetworkHandler::Shutdown();
+    NetworkHandler::Shutdown();
     testing::Test::TearDown();
   }
 
diff --git a/ash/components/tether/active_host_network_state_updater.h b/ash/components/tether/active_host_network_state_updater.h
index ecd3279..cfd248d 100644
--- a/ash/components/tether/active_host_network_state_updater.h
+++ b/ash/components/tether/active_host_network_state_updater.h
@@ -7,11 +7,11 @@
 
 #include "ash/components/tether/active_host.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 // Observes changes to the status of the active host, and relays these updates
diff --git a/ash/components/tether/asynchronous_shutdown_object_container_impl.h b/ash/components/tether/asynchronous_shutdown_object_container_impl.h
index a87c08e..1bda3554 100644
--- a/ash/components/tether/asynchronous_shutdown_object_container_impl.h
+++ b/ash/components/tether/asynchronous_shutdown_object_container_impl.h
@@ -13,8 +13,6 @@
 #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 class PrefService;
 
@@ -26,6 +24,7 @@
 
 class ManagedNetworkConfigurationHandler;
 class NetworkConnectionHandler;
+class NetworkStateHandler;
 
 namespace tether {
 
diff --git a/ash/components/tether/connection_preserver_impl.h b/ash/components/tether/connection_preserver_impl.h
index ff292e3..f4aaf58 100644
--- a/ash/components/tether/connection_preserver_impl.h
+++ b/ash/components/tether/connection_preserver_impl.h
@@ -17,11 +17,11 @@
 #include "ash/services/secure_channel/public/mojom/secure_channel.mojom.h"
 #include "base/timer/timer.h"
 #include "base/unguessable_token.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class SecureChannelClient;
diff --git a/ash/components/tether/crash_recovery_manager_impl.h b/ash/components/tether/crash_recovery_manager_impl.h
index b76cdc4..1cad66c 100644
--- a/ash/components/tether/crash_recovery_manager_impl.h
+++ b/ash/components/tether/crash_recovery_manager_impl.h
@@ -10,12 +10,12 @@
 #include "ash/components/tether/active_host.h"
 #include "ash/components/tether/crash_recovery_manager.h"
 #include "base/callback.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class HostScanCache;
diff --git a/ash/components/tether/host_scan_scheduler_impl.h b/ash/components/tether/host_scan_scheduler_impl.h
index a3c6d6a..53cffa0d 100644
--- a/ash/components/tether/host_scan_scheduler_impl.h
+++ b/ash/components/tether/host_scan_scheduler_impl.h
@@ -14,8 +14,6 @@
 #include "base/time/clock.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 #include "components/session_manager/core/session_manager_observer.h"
 
@@ -29,6 +27,7 @@
 
 namespace ash {
 
+class NetworkStateHandler;
 class NetworkTypePattern;
 
 namespace tether {
diff --git a/ash/components/tether/network_host_scan_cache.h b/ash/components/tether/network_host_scan_cache.h
index 979471d..d47afb28 100644
--- a/ash/components/tether/network_host_scan_cache.h
+++ b/ash/components/tether/network_host_scan_cache.h
@@ -12,11 +12,11 @@
 #include "ash/components/tether/host_scan_cache.h"
 #include "ash/components/tether/tether_host_response_recorder.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class DeviceIdTetherNetworkGuidMap;
diff --git a/ash/components/tether/notification_remover.h b/ash/components/tether/notification_remover.h
index bb3c4f3..2cf531a7 100644
--- a/ash/components/tether/notification_remover.h
+++ b/ash/components/tether/notification_remover.h
@@ -7,12 +7,12 @@
 
 #include "ash/components/tether/active_host.h"
 #include "ash/components/tether/host_scan_cache.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class NotificationPresenter;
diff --git a/ash/components/tether/synchronous_shutdown_object_container_impl.h b/ash/components/tether/synchronous_shutdown_object_container_impl.h
index 71f4067..798f878 100644
--- a/ash/components/tether/synchronous_shutdown_object_container_impl.h
+++ b/ash/components/tether/synchronous_shutdown_object_container_impl.h
@@ -10,8 +10,6 @@
 #include "ash/components/tether/synchronous_shutdown_object_container.h"
 // TODO(https://crbug.com/1164001): move to forward declaration
 #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 class PrefService;
 
@@ -27,6 +25,7 @@
 
 class NetworkConnect;
 class NetworkConnectionHandler;
+class NetworkStateHandler;
 
 namespace tether {
 
diff --git a/ash/components/tether/tether_component_impl.h b/ash/components/tether/tether_component_impl.h
index 07659792..9d14a17 100644
--- a/ash/components/tether/tether_component_impl.h
+++ b/ash/components/tether/tether_component_impl.h
@@ -12,8 +12,6 @@
 #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 
@@ -36,6 +34,7 @@
 class ManagedNetworkConfigurationHandler;
 class NetworkConnect;
 class NetworkConnectionHandler;
+class NetworkStateHandler;
 
 namespace tether {
 
diff --git a/ash/components/tether/tether_connector_impl.h b/ash/components/tether/tether_connector_impl.h
index 13fb0c98..f4574a0 100644
--- a/ash/components/tether/tether_connector_impl.h
+++ b/ash/components/tether/tether_connector_impl.h
@@ -13,8 +13,6 @@
 #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/ash/components/network/network_connection_handler.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
@@ -23,6 +21,8 @@
 class DeviceSyncClient;
 }
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class ActiveHost;
diff --git a/ash/components/tether/tether_network_disconnection_handler.h b/ash/components/tether/tether_network_disconnection_handler.h
index 7e2a6a1..e085a24 100644
--- a/ash/components/tether/tether_network_disconnection_handler.h
+++ b/ash/components/tether/tether_network_disconnection_handler.h
@@ -7,8 +7,6 @@
 
 #include "ash/components/tether/active_host.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 
 namespace base {
@@ -17,6 +15,8 @@
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace tether {
 
 class ActiveHost;
diff --git a/ash/components/tether/wifi_hotspot_connector.h b/ash/components/tether/wifi_hotspot_connector.h
index 9926a06..6a02353 100644
--- a/ash/components/tether/wifi_hotspot_connector.h
+++ b/ash/components/tether/wifi_hotspot_connector.h
@@ -13,8 +13,6 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "base/values.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 
 namespace base {
@@ -25,6 +23,7 @@
 
 class NetworkConnect;
 class NetworkState;
+class NetworkStateHandler;
 
 namespace tether {
 
diff --git a/ash/components/tether/wifi_hotspot_disconnector_impl.h b/ash/components/tether/wifi_hotspot_disconnector_impl.h
index 656055fa..484f50f 100644
--- a/ash/components/tether/wifi_hotspot_disconnector_impl.h
+++ b/ash/components/tether/wifi_hotspot_disconnector_impl.h
@@ -7,8 +7,6 @@
 
 #include "ash/components/tether/wifi_hotspot_disconnector.h"
 #include "base/memory/weak_ptr.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 
 class PrefRegistrySimple;
 class PrefService;
@@ -16,6 +14,7 @@
 namespace ash {
 
 class NetworkConnectionHandler;
+class NetworkStateHandler;
 
 namespace tether {
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index aae51979..1658ca5 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1596,6 +1596,10 @@
 const base::Feature kVirtualKeyboardBorderedKey{
     "VirtualKeyboardBorderedKey", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enable or disable multitouch for virtual keyboard on ChromeOS.
+const base::Feature kVirtualKeyboardMultitouch{
+    "VirtualKeyboardMultitouch", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable or disable round corners for virtual keyboard on ChromeOS.
 const base::Feature kVirtualKeyboardRoundCorners{
     "VirtualKeyboardRoundCorners", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index c68186f..afeb36b 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -645,6 +645,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardBorderedKey;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kVirtualKeyboardMultitouch;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardRoundCorners;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kWakeOnWifiAllowed;
diff --git a/ash/public/cpp/system/toast_data.h b/ash/public/cpp/system/toast_data.h
index 13d4623..485a29e2 100644
--- a/ash/public/cpp/system/toast_data.h
+++ b/ash/public/cpp/system/toast_data.h
@@ -56,6 +56,7 @@
   base::RepeatingClosure dismiss_callback;
   base::RepeatingClosure expired_callback;
   base::TimeTicks time_created;
+  base::TimeTicks time_shown;
 };
 
 }  // namespace ash
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl.cc b/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl.cc
index 7e71c9b..c7de878 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl.cc
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl.cc
@@ -92,13 +92,12 @@
       found_callback_(std::move(found_callback)),
       lost_callback_(std::move(lost_callback)) {
   observation_.Observe(scanner_.get());
-  chromeos::NetworkHandler::Get()->network_state_handler()->AddObserver(
-      this, FROM_HERE);
+  NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
 }
 
 FastPairDiscoverableScannerImpl::~FastPairDiscoverableScannerImpl() {
-  chromeos::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
-      this, FROM_HERE);
+  NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+                                                                 FROM_HERE);
 }
 
 void FastPairDiscoverableScannerImpl::OnDeviceFound(
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl_unittest.cc b/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl_unittest.cc
index 0e7cbd9f..b5ecbbbc 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl_unittest.cc
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl_unittest.cc
@@ -98,7 +98,7 @@
 class FastPairDiscoverableScannerImplTest : public testing::Test {
  public:
   void SetUp() override {
-    chromeos::NetworkHandler::Initialize();
+    NetworkHandler::Initialize();
     repository_ = std::make_unique<FakeFastPairRepository>();
 
     nearby::fastpair::Device metadata;
@@ -131,7 +131,7 @@
     process_manager_.reset();
     testing::Test::TearDown();
     discoverable_scanner_.reset();
-    chromeos::NetworkHandler::Shutdown();
+    NetworkHandler::Shutdown();
   }
 
   MockQuickPairProcessManager* mock_process_manager() {
diff --git a/ash/services/cellular_setup/esim_manager.h b/ash/services/cellular_setup/esim_manager.h
index 372efa1a..1732ff39 100644
--- a/ash/services/cellular_setup/esim_manager.h
+++ b/ash/services/cellular_setup/esim_manager.h
@@ -11,8 +11,6 @@
 #include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
 #include "chromeos/ash/components/dbus/hermes/hermes_profile_client.h"
 #include "chromeos/ash/components/network/cellular_esim_profile_handler.h"
-// TODO(https://crbug.com/1164001): move to forward declaration.
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
@@ -28,6 +26,7 @@
 class CellularESimUninstallHandler;
 class CellularInhibitor;
 class NetworkConnectionHandler;
+class NetworkStateHandler;
 
 namespace cellular_setup {
 
diff --git a/ash/services/cellular_setup/esim_test_base.h b/ash/services/cellular_setup/esim_test_base.h
index de3cb38..7d0966e 100644
--- a/ash/services/cellular_setup/esim_test_base.h
+++ b/ash/services/cellular_setup/esim_test_base.h
@@ -8,8 +8,6 @@
 #include "ash/services/cellular_setup/public/cpp/esim_manager_test_observer.h"
 #include "ash/services/cellular_setup/public/mojom/esim_manager.mojom.h"
 #include "base/test/task_environment.h"
-// TODO(https://crbug.com/1164001): move to forward declaration.
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -22,6 +20,7 @@
 class NetworkConfigurationHandler;
 class NetworkDeviceHandler;
 class NetworkProfileHandler;
+class NetworkStateHandler;
 class FakeNetworkConnectionHandler;
 class TestCellularESimProfileHandler;
 
diff --git a/ash/services/cellular_setup/ota_activator_impl.h b/ash/services/cellular_setup/ota_activator_impl.h
index 618dedaa..2a29319 100644
--- a/ash/services/cellular_setup/ota_activator_impl.h
+++ b/ash/services/cellular_setup/ota_activator_impl.h
@@ -15,8 +15,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-// TODO(https://crbug.com/1164001): move to forward declaration.
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -24,6 +22,7 @@
 namespace ash {
 
 class NetworkState;
+class NetworkStateHandler;
 class NetworkActivationHandler;
 class NetworkConnectionHandler;
 
diff --git a/ash/services/device_sync/cryptauth_scheduler_impl.h b/ash/services/device_sync/cryptauth_scheduler_impl.h
index 0ee2cd9..2799465 100644
--- a/ash/services/device_sync/cryptauth_scheduler_impl.h
+++ b/ash/services/device_sync/cryptauth_scheduler_impl.h
@@ -18,8 +18,6 @@
 #include "base/time/default_clock.h"
 #include "base/timer/timer.h"
 #include "chromeos/ash/components/network/network_handler.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -28,6 +26,8 @@
 
 namespace ash {
 
+class NetworkStateHandler;
+
 namespace device_sync {
 
 // CryptAuthScheduler implementation which stores scheduling metadata
diff --git a/ash/system/accessibility/tray_accessibility.cc b/ash/system/accessibility/tray_accessibility.cc
index b8fcafd..7a83079 100644
--- a/ash/system/accessibility/tray_accessibility.cc
+++ b/ash/system/accessibility/tray_accessibility.cc
@@ -626,8 +626,9 @@
   MaybeShowSodaMessage(SodaFeature::kLiveCaption, language_code, message);
 }
 
-void AccessibilityDetailedView::OnSodaError(
-    speech::LanguageCode language_code) {
+void AccessibilityDetailedView::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   std::u16string message = l10n_util::GetStringUTF16(
       IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR);
   MaybeShowSodaMessage(SodaFeature::kDictation, language_code, message);
diff --git a/ash/system/accessibility/tray_accessibility.h b/ash/system/accessibility/tray_accessibility.h
index 1f02066..30abb8872 100644
--- a/ash/system/accessibility/tray_accessibility.h
+++ b/ash/system/accessibility/tray_accessibility.h
@@ -79,7 +79,8 @@
 
   // SodaInstaller::Observer:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int combined_progress) override;
 
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index be585e89..8612503 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -358,7 +358,9 @@
   MaybeShowSodaMessage(language_code, message);
 }
 
-void AudioDetailedView::OnSodaError(speech::LanguageCode language_code) {
+void AudioDetailedView::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   std::u16string message = l10n_util::GetStringUTF16(
       IDS_ASH_ACCESSIBILITY_SETTING_SUBTITLE_SODA_DOWNLOAD_ERROR);
   MaybeShowSodaMessage(language_code, message);
diff --git a/ash/system/audio/audio_detailed_view.h b/ash/system/audio/audio_detailed_view.h
index 6370206..6127f6c 100644
--- a/ash/system/audio/audio_detailed_view.h
+++ b/ash/system/audio/audio_detailed_view.h
@@ -74,7 +74,8 @@
 
   // SodaInstaller::Observer:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int combined_progress) override;
 
diff --git a/ash/system/network/active_network_icon_unittest.cc b/ash/system/network/active_network_icon_unittest.cc
index ad805bfc..a64bdf6 100644
--- a/ash/system/network/active_network_icon_unittest.cc
+++ b/ash/system/network/active_network_icon_unittest.cc
@@ -150,7 +150,7 @@
   NetworkStateTestHelper& network_state_helper() {
     return network_config_helper_.network_state_helper();
   }
-  chromeos::NetworkStateHandler* network_state_handler() {
+  NetworkStateHandler* network_state_handler() {
     return network_state_helper().network_state_handler();
   }
   ActiveNetworkIcon* active_network_icon() {
diff --git a/ash/system/network/auto_connect_notifier.cc b/ash/system/network/auto_connect_notifier.cc
index db7c8df9..a754d1eb 100644
--- a/ash/system/network/auto_connect_notifier.cc
+++ b/ash/system/network/auto_connect_notifier.cc
@@ -21,8 +21,6 @@
 #include "chromeos/ash/components/network/network_type_pattern.h"
 #include "ui/base/l10n/l10n_util.h"
 
-using chromeos::NetworkHandler;
-
 namespace ash {
 
 namespace {
diff --git a/ash/system/network/auto_connect_notifier_unittest.cc b/ash/system/network/auto_connect_notifier_unittest.cc
index 6ee0ffb..4ea99ba 100644
--- a/ash/system/network/auto_connect_notifier_unittest.cc
+++ b/ash/system/network/auto_connect_notifier_unittest.cc
@@ -50,7 +50,7 @@
     NetworkCertLoader::Initialize();
     NetworkCertLoader::ForceAvailableForNetworkAuthForTesting();
     network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
-    CHECK(chromeos::NetworkHandler::Get()->auto_connect_handler());
+    CHECK(NetworkHandler::Get()->auto_connect_handler());
     network_config_helper_ = std::make_unique<
         chromeos::network_config::CrosNetworkConfigTestHelper>();
 
@@ -119,7 +119,7 @@
 };
 
 TEST_F(AutoConnectNotifierTest, NoExplicitConnectionRequested) {
-  chromeos::NetworkHandler::Get()
+  NetworkHandler::Get()
       ->auto_connect_handler()
       ->NotifyAutoConnectInitiatedForTest(
           AutoConnectHandler::AUTO_CONNECT_REASON_POLICY_APPLIED);
@@ -131,7 +131,7 @@
 
 TEST_F(AutoConnectNotifierTest, AutoConnectDueToLoginOnly) {
   NotifyConnectToNetworkRequested();
-  chromeos::NetworkHandler::Get()
+  NetworkHandler::Get()
       ->auto_connect_handler()
       ->NotifyAutoConnectInitiatedForTest(
           AutoConnectHandler::AUTO_CONNECT_REASON_LOGGED_IN);
@@ -143,7 +143,7 @@
 
 TEST_F(AutoConnectNotifierTest, NoConnectionBeforeTimerExpires) {
   NotifyConnectToNetworkRequested();
-  chromeos::NetworkHandler::Get()
+  NetworkHandler::Get()
       ->auto_connect_handler()
       ->NotifyAutoConnectInitiatedForTest(
           AutoConnectHandler::AUTO_CONNECT_REASON_POLICY_APPLIED);
@@ -164,7 +164,7 @@
   SuccessfullyJoinWifiNetwork();
 
   NotifyConnectToNetworkRequested();
-  chromeos::NetworkHandler::Get()
+  NetworkHandler::Get()
       ->auto_connect_handler()
       ->NotifyAutoConnectInitiatedForTest(
           AutoConnectHandler::AUTO_CONNECT_REASON_POLICY_APPLIED);
@@ -176,7 +176,7 @@
 
 TEST_F(AutoConnectNotifierTest, ToastDisplayed) {
   NotifyConnectToNetworkRequested();
-  chromeos::NetworkHandler::Get()
+  NetworkHandler::Get()
       ->auto_connect_handler()
       ->NotifyAutoConnectInitiatedForTest(
           AutoConnectHandler::AUTO_CONNECT_REASON_POLICY_APPLIED);
diff --git a/ash/system/network/cellular_setup_notifier_unittest.cc b/ash/system/network/cellular_setup_notifier_unittest.cc
index 7d54995..ed92205 100644
--- a/ash/system/network/cellular_setup_notifier_unittest.cc
+++ b/ash/system/network/cellular_setup_notifier_unittest.cc
@@ -47,7 +47,7 @@
     NetworkCertLoader::Initialize();
     chromeos::shill_clients::InitializeFakes();
     hermes_clients::InitializeFakes();
-    chromeos::NetworkHandler::Initialize();
+    NetworkHandler::Initialize();
     network_config_helper_ = std::make_unique<
         chromeos::network_config::CrosNetworkConfigTestHelper>();
 
@@ -66,7 +66,7 @@
   void TearDown() override {
     AshTestBase::TearDown();
     network_config_helper_.reset();
-    chromeos::NetworkHandler::Shutdown();
+    NetworkHandler::Shutdown();
     hermes_clients::Shutdown();
     chromeos::shill_clients::Shutdown();
     NetworkCertLoader::Shutdown();
diff --git a/ash/system/network/network_detailed_view_controller_unittest.cc b/ash/system/network/network_detailed_view_controller_unittest.cc
index 8b026ae..f128233 100644
--- a/ash/system/network/network_detailed_view_controller_unittest.cc
+++ b/ash/system/network/network_detailed_view_controller_unittest.cc
@@ -91,7 +91,7 @@
     network_config_helper_ = std::make_unique<
         chromeos::network_config::CrosNetworkConfigTestHelper>();
 
-    chromeos::NetworkHandler::Initialize();
+    NetworkHandler::Initialize();
     base::RunLoop().RunUntilIdle();
 
     // Creating a service here, since we would be testing that wifi,
@@ -120,7 +120,7 @@
     network_detailed_view_controller_.reset();
     AshTestBase::TearDown();
     NetworkConnect::Shutdown();
-    chromeos::NetworkHandler::Shutdown();
+    NetworkHandler::Shutdown();
     network_connect_delegate_.reset();
   }
 
@@ -187,13 +187,12 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  chromeos::NetworkStateHandler::TechnologyState GetTechnologyState(
+  NetworkStateHandler::TechnologyState GetTechnologyState(
       const NetworkTypePattern& network) {
     return network_state_handler()->GetTechnologyState(network);
   }
 
-  void SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState state) {
+  void SetTetherTechnologyState(NetworkStateHandler::TechnologyState state) {
     network_state_handler()->SetTetherTechnologyState(state);
     base::RunLoop().RunUntilIdle();
   }
@@ -231,7 +230,7 @@
   // hotspot, and associates the two networks.
   void AddTetherDevice() {
     network_state_handler()->SetTetherTechnologyState(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+        NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
     network_state_handler()->AddTetherNetworkState(
         kTetherGuid, kTetherName, kTetherCarrier, /*battery_percentage=*/100,
         kSignalStrength, /*has_connected_to_host=*/false);
@@ -255,7 +254,7 @@
   }
 
  private:
-  chromeos::NetworkStateHandler* network_state_handler() {
+  NetworkStateHandler* network_state_handler() {
     return network_state_helper()->network_state_handler();
   }
 
@@ -502,7 +501,7 @@
 
 TEST_F(NetworkDetailedViewControllerTest, WifiStateChange) {
   // By default ash test instantiates WiFi networks and enables them.
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::WiFi()));
   CheckNetworkTypeToggledHistogramBuckets(
       /*network_type=*/kNetworkTechnologyWiFi,
@@ -516,9 +515,8 @@
       /*network_type=*/kNetworkTechnologyWiFi,
       /*new_state=*/false, /*count=*/1u,
       /*total_count=*/1u);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      GetTechnologyState(NetworkTypePattern::WiFi()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            GetTechnologyState(NetworkTypePattern::WiFi()));
 
   // Renable wifi.
   ToggleWifiState(/*new_state=*/true);
@@ -527,7 +525,7 @@
       /*network_type=*/kNetworkTechnologyWiFi,
       /*new_state=*/true, /*count=*/1u,
       /*total_count=*/2u);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::WiFi()));
 }
 
@@ -538,7 +536,7 @@
       /*network_type=*/kNetworkTechnologyMobile,
       /*new_state=*/false, /*count=*/0u,
       /*total_count=*/0u);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::Cellular()));
 
   ToggleMobileState(/*new_state=*/false);
@@ -547,9 +545,8 @@
       /*network_type=*/kNetworkTechnologyMobile,
       /*new_state=*/false, /*count=*/1u,
       /*total_count=*/1u);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      GetTechnologyState(NetworkTypePattern::Cellular()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            GetTechnologyState(NetworkTypePattern::Cellular()));
   EXPECT_EQ(0, GetSystemTrayClient()->show_sim_unlock_settings_count());
 
   // When SIM is locked and new state is being toggled on show SIM unlock
@@ -557,9 +554,8 @@
   SetCellularSimLockStatus(shill::kSIMLockPin, /*sim_locked=*/true);
   ToggleMobileState(/*new_state=*/true);
   EXPECT_EQ(1, GetSystemTrayClient()->show_sim_unlock_settings_count());
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      GetTechnologyState(NetworkTypePattern::Cellular()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            GetTechnologyState(NetworkTypePattern::Cellular()));
   CheckNetworkTypeToggledHistogramBuckets(
       /*network_type=*/kNetworkTechnologyMobile,
       /*new_state=*/true, /*count=*/1u,
@@ -568,20 +564,19 @@
   // When Cellular and Tether are both available toggle should control cellular.
   AddTetherDevice();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::Tether()));
 
   // Set Tether to available and check toggle updates Cellular.
   SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
   SetCellularSimLockStatus(/*lock_type=*/"", /*sim_locked=*/false);
 
   ToggleMobileState(/*new_state=*/true);
   EXPECT_EQ(1, GetSystemTrayClient()->show_sim_unlock_settings_count());
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      GetTechnologyState(NetworkTypePattern::Tether()));
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            GetTechnologyState(NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::Cellular()));
   CheckNetworkTypeToggledHistogramBuckets(
       /*network_type=*/kNetworkTechnologyMobile,
@@ -592,14 +587,13 @@
   AddTetherDevice();
 
   // Toggle now controls Tether since there are no Cellular devices.
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::Tether()));
 
   ToggleMobileState(/*new_state=*/false);
   EXPECT_EQ(1, GetSystemTrayClient()->show_sim_unlock_settings_count());
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      GetTechnologyState(NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            GetTechnologyState(NetworkTypePattern::Tether()));
   CheckNetworkTypeToggledHistogramBuckets(
       /*network_type=*/kNetworkTechnologyMobile,
       /*new_state=*/false, /*count=*/2u,
@@ -608,15 +602,14 @@
   // When Tether is uninitialized and Bluetooth is disabled, toggling Mobile on
   // should enable Bluetooth.
   SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
   SetBluetoothAdapterState(BluetoothSystemState::kDisabled);
 
   ToggleMobileState(/*new_state=*/true);
   EXPECT_EQ(BluetoothSystemState::kEnabling, GetBluetoothAdapterState());
   EXPECT_EQ(1, GetSystemTrayClient()->show_sim_unlock_settings_count());
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
-      GetTechnologyState(NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
+            GetTechnologyState(NetworkTypePattern::Tether()));
   CheckNetworkTypeToggledHistogramBuckets(
       /*network_type=*/kNetworkTechnologyMobile,
       /*new_state=*/true, /*count=*/3u,
@@ -627,11 +620,11 @@
   // adapter state. Enabling Bluetooth will also change Tether state to
   // available.
   SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
   SetBluetoothAdapterState(BluetoothSystemState::kEnabled);
 
   EXPECT_EQ(BluetoothSystemState::kEnabled, GetBluetoothAdapterState());
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             GetTechnologyState(NetworkTypePattern::Tether()));
 }
 
diff --git a/ash/system/network/network_feature_pod_controller_unittest.cc b/ash/system/network/network_feature_pod_controller_unittest.cc
index 385594b..b654d70 100644
--- a/ash/system/network/network_feature_pod_controller_unittest.cc
+++ b/ash/system/network/network_feature_pod_controller_unittest.cc
@@ -106,7 +106,7 @@
                                       shill::kTypeEthernet, "stub_eth_device");
 
     network_state_handler()->SetTetherTechnologyState(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+        NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
 
     base::RunLoop().RunUntilIdle();
   }
@@ -248,7 +248,7 @@
     return &network_config_helper_.network_state_helper();
   }
 
-  chromeos::NetworkStateHandler* network_state_handler() {
+  NetworkStateHandler* network_state_handler() {
     return network_state_helper()->network_state_handler();
   }
 
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index e245ed76..0627bbb 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -139,10 +139,9 @@
 
     base::RunLoop().RunUntilIdle();
 
-    ASSERT_EQ(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-        helper().network_state_handler()->GetTechnologyState(
-            NetworkTypePattern::Cellular()));
+    ASSERT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+              helper().network_state_handler()->GetTechnologyState(
+                  NetworkTypePattern::Cellular()));
   }
 
   NetworkStateTestHelper& helper() {
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc
index 763338c..37e2abd 100644
--- a/ash/system/network/network_list_view_controller_unittest.cc
+++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -433,7 +433,7 @@
   // hotspot, and associates the two networks.
   void AddTetherNetworkState() {
     network_state_handler()->SetTetherTechnologyState(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+        NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
     network_state_handler()->AddTetherNetworkState(
         kTetherGuid, kTetherName, kTetherCarrier, /*battery_percentage=*/100,
         kSignalStrength, /*has_connected_to_host=*/false);
@@ -528,7 +528,7 @@
         .IsRunning();
   }
 
-  chromeos::NetworkStateHandler* network_state_handler() {
+  NetworkStateHandler* network_state_handler() {
     return network_state_helper()->network_state_handler();
   }
 
@@ -606,7 +606,7 @@
 
   // Tether device is prohibited.
   network_state_handler()->SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(nullptr, GetMobileSubHeader());
   histogram_tester.ExpectBucketCount("ChromeOS.SystemTray.Network.SectionShown",
@@ -614,7 +614,7 @@
 
   // Tether device is uninitialized but is primary user.
   network_state_handler()->SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
   base::RunLoop().RunUntilIdle();
   EXPECT_NE(nullptr, GetMobileSubHeader());
   histogram_tester.ExpectBucketCount("ChromeOS.SystemTray.Network.SectionShown",
@@ -1004,7 +1004,7 @@
 
   // Tether is enabled but no devices are added.
   network_state_handler()->SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_NE(nullptr, GetMobileStatusMessage());
@@ -1017,7 +1017,7 @@
 
   // Tether network is uninitialized and Bluetooth state enabling.
   network_state_handler()->SetTetherTechnologyState(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
   base::RunLoop().RunUntilIdle();
 
   SetBluetoothAdapterState(BluetoothSystemState::kEnabling);
diff --git a/ash/system/network/sms_observer.cc b/ash/system/network/sms_observer.cc
index a4e04849..5ae8666 100644
--- a/ash/system/network/sms_observer.cc
+++ b/ash/system/network/sms_observer.cc
@@ -18,8 +18,6 @@
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/message_center/message_center.h"
 
-using chromeos::NetworkHandler;
-
 namespace ash {
 
 const char SmsObserver::kNotificationPrefix[] = "chrome://network/sms";
diff --git a/ash/system/status_area_widget_unittest.cc b/ash/system/status_area_widget_unittest.cc
index 6417d07..cc46cac 100644
--- a/ash/system/status_area_widget_unittest.cc
+++ b/ash/system/status_area_widget_unittest.cc
@@ -314,7 +314,7 @@
 
   void TearDown() override {
     // This roughly matches production shutdown order.
-    chromeos::NetworkHandler::Get()->ShutdownPrefServices();
+    NetworkHandler::Get()->ShutdownPrefServices();
     AshTestBase::TearDown();
   }
 
diff --git a/ash/system/toast/toast_manager_impl.cc b/ash/system/toast/toast_manager_impl.cc
index 6d3d74c..972776e 100644
--- a/ash/system/toast/toast_manager_impl.cc
+++ b/ash/system/toast/toast_manager_impl.cc
@@ -11,11 +11,30 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 
 namespace ash {
 
+namespace {
+
+constexpr char NotifierFrameworkToastHistogram[] =
+    "Ash.NotifierFramework.Toast";
+
+// Used in histogram names.
+std::string GetToastDismissedTimeRange(const base::TimeDelta& time) {
+  if (time <= base::Seconds(2))
+    return "Within2s";
+  // Toast default duration is 6s, but with animation it's usually
+  // around ~6.2s, so recording 7s as the default case.
+  if (time <= base::Seconds(7))
+    return "Within7s";
+  return "After7s";
+}
+
+}  // namespace
+
 ToastManagerImpl::ToastManagerImpl()
     : locked_(Shell::Get()->session_controller()->IsScreenLocked()) {}
 
@@ -82,6 +101,14 @@
 }
 
 void ToastManagerImpl::OnClosed() {
+  const base::TimeDelta user_journey_time =
+      base::TimeTicks::Now() - current_toast_data_->time_shown;
+  const std::string time_range = GetToastDismissedTimeRange(user_journey_time);
+  base::UmaHistogramEnumeration(
+      base::StringPrintf("%s.Dismissed.%s", NotifierFrameworkToastHistogram,
+                         time_range.c_str()),
+      current_toast_data_->catalog_name);
+
   overlay_.reset();
   current_toast_data_.reset();
 
@@ -124,6 +151,7 @@
         current_toast_data_->duration);
   }
 
+  current_toast_data_->time_shown = base::TimeTicks::Now();
   base::UmaHistogramEnumeration("Ash.NotifierFramework.Toast.ShownCount",
                                 current_toast_data_->catalog_name);
   base::UmaHistogramMediumTimes(
diff --git a/ash/system/toast/toast_manager_unittest.cc b/ash/system/toast/toast_manager_unittest.cc
index c69d1b9..a4c59c4 100644
--- a/ash/system/toast/toast_manager_unittest.cc
+++ b/ash/system/toast/toast_manager_unittest.cc
@@ -34,6 +34,25 @@
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/widget/widget.h"
 
+namespace {
+
+constexpr char kToastShownCountHistogramName[] =
+    "Ash.NotifierFramework.Toast.ShownCount";
+
+constexpr char kToastTimeInQueueHistogramName[] =
+    "Ash.NotifierFramework.Toast.TimeInQueue";
+
+constexpr char kToastDismissedWithin2s[] =
+    "Ash.NotifierFramework.Toast.Dismissed.Within2s";
+
+constexpr char kToastDismissedWithin7s[] =
+    "Ash.NotifierFramework.Toast.Dismissed.Within7s";
+
+constexpr char kToastDismissedAfter7s[] =
+    "Ash.NotifierFramework.Toast.Dismissed.After7s";
+
+}  // namespace
+
 namespace ash {
 
 class ToastManagerImplTest : public AshTestBase {
@@ -660,56 +679,105 @@
             GetCurrentDismissText());
 }
 
-TEST_F(ToastManagerImplTest, NotifierFrameworkMetrics) {
+TEST_F(ToastManagerImplTest, ShownCountMetric) {
   base::HistogramTester histogram_tester;
 
-  constexpr char kToastShownCountHistogramName[] =
-      "Ash.NotifierFramework.Toast.ShownCount";
-  constexpr char kToastTimeInQueueHistogramName[] =
-      "Ash.NotifierFramework.Toast.TimeInQueue";
   const ToastCatalogName catalog_name_1 = static_cast<ToastCatalogName>(1);
   const ToastCatalogName catalog_name_2 = static_cast<ToastCatalogName>(2);
-  const base::TimeDelta duration = base::Seconds(3);
+  const base::TimeDelta duration = base::Seconds(2);
+  constexpr char text[] = "sample text";
 
   // Show Toast with catalog_name_1.
-  std::string id1 = ShowToast("TEXT1", duration,
+  std::string id1 = ShowToast(text, duration,
                               /*visible_on_lock_screen=*/false, catalog_name_1);
   histogram_tester.ExpectBucketCount(kToastShownCountHistogramName,
                                      catalog_name_1, 1);
 
-  // Expect "TimeInQueue" metric to record zero since there were no toasts in
-  // the queue.
-  histogram_tester.ExpectTimeBucketCount(kToastTimeInQueueHistogramName,
-                                         base::Seconds(0), 1);
-
   // Replace existing toast a couple of times.
-  ReplaceToast(id1, "TEXT1_UPDATED", duration,
+  ReplaceToast(id1, text, duration,
                /*visible_on_lock_screen=*/false, catalog_name_1);
-  ReplaceToast(id1, "TEXT1_UPDATED", duration,
+  ReplaceToast(id1, text, duration,
                /*visible_on_lock_screen=*/false, catalog_name_1);
   histogram_tester.ExpectBucketCount(kToastShownCountHistogramName,
                                      catalog_name_1, 3);
 
-  // Expect "TimeInQueue" metric to record zero since the same toast was shown,
-  // so it wasn't queued.
-  histogram_tester.ExpectTimeBucketCount(kToastTimeInQueueHistogramName,
-                                         base::Seconds(0), 3);
-
   // Try to show toast with catalog_name_2 right after last toast was shown.
-  ShowToast("TEXT2", duration, /*visible_on_lock_screen=*/false,
-            catalog_name_2);
+  ShowToast(text, duration, /*visible_on_lock_screen=*/false, catalog_name_2);
 
   // Fast forward the toast's duration so the queued toast is shown.
   task_environment()->FastForwardBy(duration);
   histogram_tester.ExpectBucketCount(kToastShownCountHistogramName,
                                      catalog_name_2, 1);
+}
 
-  // Expect "TimeInQueue" metric to record the toast's duration since the second
-  // toast was queued right after the first one was shown.
+TEST_F(ToastManagerImplTest, TimeInQueueMetric) {
+  base::HistogramTester histogram_tester;
+
+  const ToastCatalogName catalog_name_1 = static_cast<ToastCatalogName>(1);
+  const ToastCatalogName catalog_name_2 = static_cast<ToastCatalogName>(2);
+  const base::TimeDelta duration = base::Seconds(2);
+  constexpr char text[] = "sample text";
+
+  // Show Toast with catalog_name_1.
+  std::string id1 = ShowToast(text, duration, /*visible_on_lock_screen=*/false,
+                              catalog_name_1);
+
+  // 'TimeInQueue' is zero since there were no toasts in the queue.
+  histogram_tester.ExpectTimeBucketCount(kToastTimeInQueueHistogramName,
+                                         base::Seconds(0), 1);
+
+  // Replace existing toast a couple of times.
+  ReplaceToast(id1, text, duration,
+               /*visible_on_lock_screen=*/false, catalog_name_1);
+  ReplaceToast(id1, text, duration,
+               /*visible_on_lock_screen=*/false, catalog_name_1);
+
+  // 'TimeInQueue' is zero since the same toast was replaced.
+  histogram_tester.ExpectTimeBucketCount(kToastTimeInQueueHistogramName,
+                                         base::Seconds(0), 3);
+
+  // Try to show toast with catalog_name_2 right after last toast was shown.
+  ShowToast(text, duration, /*visible_on_lock_screen=*/false, catalog_name_2);
+
+  // Fast forward the toast's duration so the queued toast is shown.
+  task_environment()->FastForwardBy(duration);
+
+  // 'TimeInQueue' records the toast's duration since the second toast was
+  // queued right after the first one was shown.
   histogram_tester.ExpectTimeBucketCount(kToastTimeInQueueHistogramName,
                                          duration, 1);
 }
 
+TEST_F(ToastManagerImplTest, UserJourneyTimeMetric) {
+  base::HistogramTester histogram_tester;
+
+  const ToastCatalogName catalog_name = ToastCatalogName::kToastManagerUnittest;
+  const base::TimeDelta duration = base::Seconds(6);
+  constexpr char text[] = "sample text";
+
+  // Show Toast and wait for it to dismiss by time-out.
+  ShowToast(text, duration);
+  task_environment()->FastForwardBy(duration);
+  histogram_tester.ExpectBucketCount(kToastDismissedWithin7s, catalog_name, 1);
+
+  // Show toast and replace it right after.
+  std::string id = ShowToast(text, duration);
+  ReplaceToast(id, text, duration);
+  task_environment()->FastForwardBy(duration);
+
+  // Replaced toast was dismissed within 2s.
+  histogram_tester.ExpectBucketCount(kToastDismissedWithin2s, catalog_name, 1);
+  histogram_tester.ExpectBucketCount(kToastDismissedWithin7s, catalog_name, 2);
+
+  // Show a toast with infinite duration.
+  ShowToastWithDismiss(text, ToastData::kInfiniteDuration);
+  task_environment()->FastForwardBy(duration + base::Seconds(2));
+  ClickDismissButton();
+
+  // Toast with dismiss button was dismissed after 7s.
+  histogram_tester.ExpectBucketCount(kToastDismissedAfter7s, catalog_name, 1);
+}
+
 // Table-driven test that checks that a toast's expired callback is run when a
 // toast is closed when the toast manager cancels the toast, when the toast
 // duration cancels the toast, and when the dismiss button is pressed.
diff --git a/ash/webui/os_feedback_ui/resources/file_attachment.js b/ash/webui/os_feedback_ui/resources/file_attachment.js
index b0f9d14..bffed1b 100644
--- a/ash/webui/os_feedback_ui/resources/file_attachment.js
+++ b/ash/webui/os_feedback_ui/resources/file_attachment.js
@@ -6,7 +6,7 @@
 import './os_feedback_shared_css.js';
 import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import 'chrome://resources/cr_elements/icons.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.js b/ash/webui/os_feedback_ui/resources/share_data_page.js
index 9b58f96..822dc74 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.js
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.js
@@ -6,7 +6,7 @@
 import './file_attachment.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/ash/webui/scanning/resources/multi_page_checkbox.js b/ash/webui/scanning/resources/multi_page_checkbox.js
index 1a7e18b..9bc851df 100644
--- a/ash/webui/scanning/resources/multi_page_checkbox.js
+++ b/ash/webui/scanning/resources/multi_page_checkbox.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 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import './scan_settings_section.js';
 
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index 039fda04..ae6b5f4 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -54,8 +54,8 @@
 // Metered networks are excluded for RMA to avoid any cost to the owner who
 // does not have control of the device during RMA.
 bool HaveAllowedNetworkConnection() {
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* network_state_handler =
+      NetworkHandler::Get()->network_state_handler();
   const NetworkState* network = network_state_handler->DefaultNetwork();
   // TODO(gavindodd): Confirm that metered networks should be excluded. This
   // should only be true for cellular networks which are already blocked.
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
index ec201be..79537823 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
@@ -295,7 +295,7 @@
     return cros_network_config_test_helper_->network_state_helper();
   }
 
-  chromeos::NetworkStateHandler* network_state_handler() {
+  NetworkStateHandler* network_state_handler() {
     return network_state_helper().network_state_handler();
   }
 
diff --git a/ash/webui/shimless_rma/backend/version_updater.cc b/ash/webui/shimless_rma/backend/version_updater.cc
index 1a44a15..705c83d 100644
--- a/ash/webui/shimless_rma/backend/version_updater.cc
+++ b/ash/webui/shimless_rma/backend/version_updater.cc
@@ -42,8 +42,8 @@
 // the appropriate status. |interactive| indicates whether the user is actively
 // checking for updates.
 bool IsUpdateAllowed() {
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* network_state_handler =
+      NetworkHandler::Get()->network_state_handler();
   const NetworkState* network = network_state_handler->DefaultNetwork();
   // Don't allow an update if device is currently offline or connected
   // to a network for which data is metered.
diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.js b/ash/webui/shimless_rma/resources/calibration_component_chip.js
index 8bcc280..dd5e0c5 100644
--- a/ash/webui/shimless_rma/resources/calibration_component_chip.js
+++ b/ash/webui/shimless_rma/resources/calibration_component_chip.js
@@ -58,12 +58,27 @@
         value: false,
         observer: 'onIsFirstClickableComponentChanged_',
       },
+
+      /** @type {number} */
+      uniqueId: {
+        reflectToAttribute: true,
+        type: Number,
+        value: '',
+      },
     };
   }
 
   /** @protected */
   onComponentButtonClicked_() {
     this.checked = !this.checked;
+
+    // Notify the page that the component chip was clicked, so that the page can
+    // put the focus on it.
+    this.dispatchEvent(new CustomEvent('click-calibration-component-button', {
+      bubbles: true,
+      composed: true,
+      detail: this.uniqueId,
+    }));
   }
 
   click() {
diff --git a/ash/webui/shimless_rma/resources/fake_data.js b/ash/webui/shimless_rma/resources/fake_data.js
index 668d6f3..80585a4 100644
--- a/ash/webui/shimless_rma/resources/fake_data.js
+++ b/ash/webui/shimless_rma/resources/fake_data.js
@@ -248,13 +248,13 @@
     progress: 1.0,
   },
   {
-    component: ComponentType.kBaseAccelerometer,
-    status: CalibrationStatus.kCalibrationInProgress,
+    component: ComponentType.kLidAccelerometer,
+    status: CalibrationStatus.kCalibrationFailed,
     progress: 1.0,
   },
   {
-    component: ComponentType.kLidAccelerometer,
-    status: CalibrationStatus.kCalibrationFailed,
+    component: ComponentType.kBaseAccelerometer,
+    status: CalibrationStatus.kCalibrationInProgress,
     progress: 1.0,
   },
   {
@@ -262,6 +262,16 @@
     status: CalibrationStatus.kCalibrationSkip,
     progress: 0.0,
   },
+  {
+    component: ComponentType.kScreen,
+    status: CalibrationStatus.kCalibrationFailed,
+    progress: 1.0,
+  },
+  {
+    component: ComponentType.kScreen,
+    status: CalibrationStatus.kCalibrationFailed,
+    progress: 1.0,
+  },
 ];
 
 /** @type {!Array<!CalibrationComponentStatus>} */
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
index e391f73d..4a89fed 100644
--- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html
@@ -10,9 +10,10 @@
   </div>
   <div slot="right-pane">
     <div class="scroll-container">
-      <div class="component-grid calibration">
+      <div id="componentList" class="component-grid calibration">
         <template is="dom-repeat" items="{{componentCheckboxes_}}" as="component">
           <calibration-component-chip id="[[component.id]]"
+            unique-id="[[component.uniqueId]]"
             checked="{{component.checked}}"
             failed="[[component.failed]]"
             component-name="[[component.name]]"
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js
index 3ca6b5c..fe2eb0cf 100644
--- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js
+++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js
@@ -12,7 +12,7 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ComponentTypeToId} from './data.js';
 import {getShimlessRmaService} from './mojo_interface_provider.js';
@@ -30,6 +30,7 @@
 /**
  * @typedef {{
  *   component: !ComponentType,
+ *   uniqueId: number,
  *   id: string,
  *   name: string,
  *   checked: boolean,
@@ -38,6 +39,8 @@
  */
 let ComponentCheckbox;
 
+const NUM_COLUMNS = 1;
+
 /**
  * @constructor
  * @extends {PolymerElement}
@@ -70,6 +73,16 @@
         type: Array,
         value: () => [],
       },
+
+      /**
+       * The index into componentCheckboxes_ for keyboard navigation between
+       * components.
+       * @private
+       */
+      focusedComponentIndex_: {
+        type: Number,
+        value: -1,
+      },
     };
   }
 
@@ -86,6 +99,92 @@
     this.shimlessRmaService_ = getShimlessRmaService();
 
     /**
+     * The componentClickedCallback_ callback is used to capture events when
+     * components are clicked, so that the page can put the focus on the
+     * component that was clicked.
+     * @private {?Function}
+     */
+    this.componentClicked_ = (event) => {
+      const componentIndex = this.componentCheckboxes_.findIndex(
+          component => component.uniqueId === event.detail);
+
+      if (componentIndex === -1 ||
+          this.componentCheckboxes_[componentIndex].disabled) {
+        return;
+      }
+
+      this.focusedComponentIndex_ = componentIndex;
+      this.focusOnCurrentComponent_();
+    };
+
+    /**
+     * Handles keyboard navigation over the list of components.
+     * TODO(240717594): Find a way to avoid duplication of this code in the
+     * repair components page.
+     * @private {?Function}
+     */
+    this.HandleKeyDownEvent = (event) => {
+      if (event.key !== 'ArrowRight' && event.key !== 'ArrowDown' &&
+          event.key !== 'ArrowLeft' && event.key !== 'ArrowUp') {
+        return;
+      }
+
+      // If there are no selectable components, do nothing.
+      if (this.focusedComponentIndex_ === -1) {
+        return;
+      }
+
+      // Don't use keyboard navigation if the user tabbed out of the
+      // component list.
+      if (!this.shadowRoot.activeElement ||
+          this.shadowRoot.activeElement.tagName !==
+              'CALIBRATION-COMPONENT-CHIP') {
+        return;
+      }
+
+      if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
+        // The Down button should send you down the column, so we go forward
+        // by two components, which is the size of the row.
+        let step = 1;
+        if (event.key === 'ArrowDown') {
+          step = NUM_COLUMNS;
+        }
+
+        let newIndex = this.focusedComponentIndex_ + step;
+        // Keep skipping disabled components until we encounter one that is
+        // not disabled.
+        while (newIndex < this.componentCheckboxes_.length &&
+               this.componentCheckboxes_[newIndex].disabled) {
+          newIndex += step;
+        }
+        // Check that we haven't ended up outside of the array before
+        // applying the changes.
+        if (newIndex < this.componentCheckboxes_.length) {
+          this.focusedComponentIndex_ = newIndex;
+        }
+      }
+
+      // The left and up arrows work similarly to down and right, but go
+      // backwards.
+      if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
+        let step = 1;
+        if (event.key === 'ArrowUp') {
+          step = NUM_COLUMNS;
+        }
+
+        let newIndex = this.focusedComponentIndex_ - step;
+        while (newIndex >= 0 && this.componentCheckboxes_[newIndex].disabled) {
+          newIndex -= step;
+        }
+        if (newIndex >= 0) {
+          this.focusedComponentIndex_ = newIndex;
+        }
+      }
+
+      this.focusOnCurrentComponent_();
+    };
+
+    /**
      * The "Skip calibration" button on this page is styled and positioned like
      * a exit button. So we use the common exit button from shimless_rma.js
      * This function needs to be public, because it's invoked by
@@ -130,9 +229,10 @@
         return;
       }
 
-      this.componentCheckboxes_ = result.components.map(item => {
+      this.componentCheckboxes_ = result.components.map((item, index) => {
         return {
           component: item.component,
+          uniqueId: index,
           id: ComponentTypeToId[item.component],
           name: this.i18n(ComponentTypeToId[item.component]),
           checked: false,
@@ -142,9 +242,45 @@
           disabled: item.status !== CalibrationStatus.kCalibrationFailed,
         };
       });
+
+      // Focus on the first clickable component at the beginning.
+      this.focusedComponentIndex_ =
+          this.componentCheckboxes_.findIndex(component => !component.disabled);
+      afterNextRender(this, () => {
+        this.focusOnCurrentComponent_();
+      });
     });
   }
 
+  /** @override */
+  connectedCallback() {
+    super.connectedCallback();
+    window.addEventListener('keydown', this.HandleKeyDownEvent);
+    window.addEventListener(
+        'click-calibration-component-button', this.componentClicked_);
+  }
+
+  /** @override */
+  disconnectedCallback() {
+    super.disconnectedCallback();
+    window.removeEventListener('keydown', this.HandleKeyDownEvent);
+    window.removeEventListener(
+        'click-calibration-component-button', this.componentClicked_);
+  }
+
+  /**
+   * Make the page focus on the component at focusedComponentIndex_.
+   * @private
+   */
+  focusOnCurrentComponent_() {
+    if (this.focusedComponentIndex_ != -1) {
+      const componentChip = this.shadowRoot.querySelector(`[unique-id="${
+          this.componentCheckboxes_[this.focusedComponentIndex_].uniqueId}"]`);
+      componentChip.shadowRoot.querySelector('#componentButton').focus();
+    }
+  }
+
+
   /**
    * @return {!Array<!CalibrationComponentStatus>}
    * @private
diff --git a/ash/wm/desks/OWNERS b/ash/wm/desks/OWNERS
index 111b3ba..dbaf9f1b8 100644
--- a/ash/wm/desks/OWNERS
+++ b/ash/wm/desks/OWNERS
@@ -1 +1,2 @@
 afakhry@chromium.org
+dandersson@chromium.org
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index 75cdf6a8..615b9c5e 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -592,12 +592,12 @@
           },
           weak_ptr_factory_.GetWeakPtr()))
       .Once()
-      .SetOpacity(std::move(layer_to_show), 0.0f)
-      .SetOpacity(std::move(layer_to_hide), 1.0f)
+      .SetOpacity(layer_to_show, 0.0f)
+      .SetOpacity(layer_to_hide, 1.0f)
       .Then()
       .SetDuration(base::Milliseconds(kFadeDurationMs))
-      .SetOpacity(std::move(layer_to_show), 1.0f)
-      .SetOpacity(std::move(layer_to_hide), 0.0f);
+      .SetOpacity(layer_to_show, 1.0f)
+      .SetOpacity(layer_to_hide, 0.0f);
 }
 
 void SavedDeskItemView::ContentsChanged(views::Textfield* sender,
diff --git a/base/BUILD.gn b/base/BUILD.gn
index de7ba75..a707f0b 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -737,6 +737,8 @@
     "task/sequence_manager/enqueue_order_generator.h",
     "task/sequence_manager/fence.cc",
     "task/sequence_manager/fence.h",
+    "task/sequence_manager/hierarchical_timing_wheel.cc",
+    "task/sequence_manager/hierarchical_timing_wheel.h",
     "task/sequence_manager/lazily_deallocated_deque.h",
     "task/sequence_manager/sequence_manager.cc",
     "task/sequence_manager/sequence_manager.h",
@@ -3293,6 +3295,7 @@
     "task/post_job_unittest.cc",
     "task/scoped_set_task_priority_for_current_thread_unittest.cc",
     "task/sequence_manager/atomic_flag_set_unittest.cc",
+    "task/sequence_manager/hierarchical_timing_wheel_unittest.cc",
     "task/sequence_manager/lazily_deallocated_deque_unittest.cc",
     "task/sequence_manager/sequence_manager_impl_unittest.cc",
     "task/sequence_manager/task_order_unittest.cc",
diff --git a/base/feature_list.cc b/base/feature_list.cc
index 0885a9a..857322c 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -28,6 +28,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/task/sequence_manager/work_queue.h"
 #include "build/build_config.h"
 
 namespace base {
@@ -508,6 +509,8 @@
   ConfigureRandBytesFieldTrial();
 #endif
 
+  base::sequence_manager::internal::WorkQueue::ConfigureCapacityFieldTrial();
+
 #if BUILDFLAG(DCHECK_IS_CONFIGURABLE)
   // Update the behaviour of LOGGING_DCHECK to match the Feature configuration.
   // DCHECK is also forced to be FATAL if we are running a death-test.
diff --git a/base/logging.cc b/base/logging.cc
index 80dab7d..412084c 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -25,6 +25,11 @@
 #include "base/trace_event/base_tracing.h"
 #include "build/build_config.h"
 
+#if !BUILDFLAG(IS_NACL)
+#include "base/auto_reset.h"
+#include "base/debug/crash_logging.h"
+#endif  // !BUILDFLAG(IS_NACL)
+
 #if defined(LEAK_SANITIZER) && !BUILDFLAG(IS_NACL)
 #include "base/debug/leak_annotations.h"
 #endif  // defined(LEAK_SANITIZER) && !BUILDFLAG(IS_NACL)
@@ -456,6 +461,39 @@
   }
 }
 
+void SetLogFatalCrashKey(LogMessage* log_message) {
+#if !BUILDFLAG(IS_NACL)
+  // In case of an out-of-memory condition, this code could be reentered when
+  // constructing and storing the key. Using a static is not thread-safe, but if
+  // multiple threads are in the process of a fatal crash at the same time, this
+  // should work.
+  static bool guarded = false;
+  if (guarded)
+    return;
+
+  base::AutoReset<bool> guard(&guarded, true);
+
+  static auto* const crash_key = base::debug::AllocateCrashKeyString(
+      "LOG_FATAL", base::debug::CrashKeySize::Size1024);
+  base::debug::SetCrashKeyString(crash_key, log_message->BuildCrashString());
+
+#endif  // !BUILDFLAG(IS_NACL)
+}
+
+std::string BuildCrashString(const char* file,
+                             int line,
+                             const char* message_without_prefix) {
+  // Only log last path component.
+  if (file) {
+    const char* slash = strrchr(file, '/');
+    if (slash) {
+      file = slash + 1;
+    }
+  }
+
+  return base::StringPrintf("%s:%d: %s", file, line, message_without_prefix);
+}
+
 }  // namespace
 
 #if BUILDFLAG(DCHECK_IS_CONFIGURABLE)
@@ -685,6 +723,9 @@
   TRACE_LOG_MESSAGE(
       file_, base::StringPiece(str_newline).substr(message_start_), line_);
 
+  if (severity_ == LOGGING_FATAL)
+    SetLogFatalCrashKey(this);
+
   // Give any log message handler first dibs on the message.
   if (g_log_message_handler &&
       g_log_message_handler(severity_, file_, line_, message_start_,
@@ -907,21 +948,8 @@
 }
 
 std::string LogMessage::BuildCrashString() const {
-  return BuildCrashString(file(), line(), str().c_str() + message_start_);
-}
-
-std::string LogMessage::BuildCrashString(const char* file,
-                                         int line,
-                                         const char* message_without_prefix) {
-  // Only log last path component.
-  if (file) {
-    const char* slash = strrchr(file, '/');
-    if (slash) {
-      file = slash + 1;
-    }
-  }
-
-  return base::StringPrintf("%s:%d: %s", file, line, message_without_prefix);
+  return logging::BuildCrashString(file(), line(),
+                                   str().c_str() + message_start_);
 }
 
 // writes the common header info to the stream
diff --git a/base/logging.h b/base/logging.h
index acef975e..9cef63f2 100644
--- a/base/logging.h
+++ b/base/logging.h
@@ -653,9 +653,6 @@
 
   // Gets file:line: message in a format suitable for crash reporting.
   std::string BuildCrashString() const;
-  static std::string BuildCrashString(const char* file,
-                                      int line,
-                                      const char* message_without_prefix);
 
  private:
   void Init(const char* file, int line);
diff --git a/base/native_library_fuchsia.cc b/base/native_library_fuchsia.cc
index a58c0f2..a1ccb86 100644
--- a/base/native_library_fuchsia.cc
+++ b/base/native_library_fuchsia.cc
@@ -26,6 +26,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
+#include "base_paths.h"
 
 namespace base {
 
@@ -45,13 +46,13 @@
   }
 
   FilePath computed_path;
-  base::PathService::Get(DIR_SOURCE_ROOT, &computed_path);
+  base::PathService::Get(DIR_ASSETS, &computed_path);
   computed_path = computed_path.AppendASCII("lib").Append(components[0]);
 
   // Use fdio_open_fd (a Fuchsia-specific API) here so we can pass the
   // appropriate FS rights flags to request executability.
-  // TODO(1018538): Teach base::File about FLAG_WIN_EXECUTE on Fuchsia, and then
-  // use it here instead of using fdio_open_fd() directly.
+  // TODO(crbug.com/1018538): Teach base::File about FLAG_WIN_EXECUTE on
+  // Fuchsia, and then use it here instead of using fdio_open_fd() directly.
   base::ScopedFD fd;
   zx_status_t status = fdio_open_fd(
       computed_path.value().c_str(),
diff --git a/base/task/sequence_manager/hierarchical_timing_wheel.cc b/base/task/sequence_manager/hierarchical_timing_wheel.cc
new file mode 100644
index 0000000..0b429229
--- /dev/null
+++ b/base/task/sequence_manager/hierarchical_timing_wheel.cc
@@ -0,0 +1,83 @@
+// 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 "base/task/sequence_manager/hierarchical_timing_wheel.h"
+
+namespace base::sequence_manager {
+
+////////////////////////////////////////////////////////////////////////////////
+// HierarchicalTimingWheelHandle
+
+HierarchicalTimingWheelHandle::HierarchicalTimingWheelHandle() = default;
+
+HierarchicalTimingWheelHandle::HierarchicalTimingWheelHandle(
+    HierarchicalTimingWheelHandle&& other) noexcept
+    : timing_wheel_handle_(std::move(other.timing_wheel_handle_)),
+      heap_handle_(std::move(other.heap_handle_)),
+      hierarchy_index_(std::exchange(other.hierarchy_index_, kInvalidIndex)) {}
+
+HierarchicalTimingWheelHandle& HierarchicalTimingWheelHandle::operator=(
+    HierarchicalTimingWheelHandle&& other) noexcept {
+  timing_wheel_handle_ = std::move(other.timing_wheel_handle_);
+  heap_handle_ = std::move(other.heap_handle_);
+  hierarchy_index_ = std::exchange(other.hierarchy_index_, kInvalidIndex);
+  return *this;
+}
+
+HierarchicalTimingWheelHandle::~HierarchicalTimingWheelHandle() = default;
+
+internal::TimingWheelHandle
+HierarchicalTimingWheelHandle::GetTimingWheelHandle() const {
+  return timing_wheel_handle_;
+}
+
+void HierarchicalTimingWheelHandle::SetTimingWheelHandle(
+    internal::TimingWheelHandle timing_wheel_handle) {
+  DCHECK(timing_wheel_handle.IsValid());
+  DCHECK(!heap_handle_.IsValid());
+  timing_wheel_handle_ = timing_wheel_handle;
+}
+
+void HierarchicalTimingWheelHandle::ClearTimingWheelHandle() {
+  timing_wheel_handle_.Reset();
+}
+
+HeapHandle HierarchicalTimingWheelHandle::GetHeapHandle() {
+  return heap_handle_;
+}
+
+void HierarchicalTimingWheelHandle::SetHeapHandle(HeapHandle heap_handle) {
+  DCHECK(heap_handle.IsValid());
+  DCHECK(!timing_wheel_handle_.IsValid());
+  heap_handle_ = heap_handle;
+}
+
+void HierarchicalTimingWheelHandle::ClearHeapHandle() {
+  heap_handle_.reset();
+}
+
+size_t HierarchicalTimingWheelHandle::GetHierarchyIndex() const {
+  return hierarchy_index_;
+}
+
+void HierarchicalTimingWheelHandle::SetHierarchyIndex(size_t hierarchy_index) {
+  DCHECK(hierarchy_index != kInvalidIndex);
+  hierarchy_index_ = hierarchy_index;
+}
+
+void HierarchicalTimingWheelHandle::ClearHierarchyIndex() {
+  hierarchy_index_ = kInvalidIndex;
+}
+
+// static
+HierarchicalTimingWheelHandle HierarchicalTimingWheelHandle::Invalid() {
+  return HierarchicalTimingWheelHandle();
+}
+
+bool HierarchicalTimingWheelHandle::IsValid() const {
+  return (timing_wheel_handle_.IsValid() || heap_handle_.IsValid()) &&
+         hierarchy_index_ != kInvalidIndex;
+}
+
+}  // namespace base::sequence_manager
diff --git a/base/task/sequence_manager/hierarchical_timing_wheel.h b/base/task/sequence_manager/hierarchical_timing_wheel.h
new file mode 100644
index 0000000..d8a52e9
--- /dev/null
+++ b/base/task/sequence_manager/hierarchical_timing_wheel.h
@@ -0,0 +1,433 @@
+// 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 BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
+#define BASE_TASK_SEQUENCE_MANAGER_HIERARCHICAL_TIMING_WHEEL_H_
+
+#include <algorithm>
+#include <array>
+#include <numeric>
+#include <vector>
+
+#include "base/containers/intrusive_heap.h"
+#include "base/task/sequence_manager/timing_wheel.h"
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base::sequence_manager {
+
+// A union of |TimingWheelHandle| and |HeapHandle|. At any given
+// time it holds a value of one of its alternative types. It can only
+// have either. This class is maintained by the hierarchical timing
+// wheel as the object moves around within it. It can be used to subsequently
+// remove the element.
+class BASE_EXPORT HierarchicalTimingWheelHandle {
+ public:
+  enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
+
+  HierarchicalTimingWheelHandle();
+
+  HierarchicalTimingWheelHandle(const HierarchicalTimingWheelHandle& other) =
+      default;
+  HierarchicalTimingWheelHandle(HierarchicalTimingWheelHandle&& other) noexcept;
+
+  HierarchicalTimingWheelHandle& operator=(
+      const HierarchicalTimingWheelHandle& other) = default;
+  HierarchicalTimingWheelHandle& operator=(
+      HierarchicalTimingWheelHandle&& other) noexcept;
+
+  ~HierarchicalTimingWheelHandle();
+
+  // TimingWheel contract
+  internal::TimingWheelHandle GetTimingWheelHandle() const;
+  void SetTimingWheelHandle(internal::TimingWheelHandle timing_wheel_handle);
+  void ClearTimingWheelHandle();
+
+  // IntrusiveHeap contract
+  HeapHandle GetHeapHandle();
+  void SetHeapHandle(HeapHandle handle);
+  void ClearHeapHandle();
+
+  size_t GetHierarchyIndex() const;
+  void SetHierarchyIndex(size_t hierarchy_index);
+  void ClearHierarchyIndex();
+
+  // Gets a default constructed HierarchicalTimingWheelHandle.
+  static HierarchicalTimingWheelHandle Invalid();
+
+  bool IsValid() const;
+
+ private:
+  // The handle of the timing wheel in the hierarchical timing wheel where the
+  // element is in.
+  internal::TimingWheelHandle timing_wheel_handle_;
+
+  // The handle of the heap in the hierarchical timing wheel where the element
+  // is in.
+  HeapHandle heap_handle_;
+
+  // The index in the hierarchy of timing wheels and heaps, this handle belongs
+  // to.
+  size_t hierarchy_index_ = kInvalidIndex;
+};
+
+// The default HierarchicalTimingWheelHandleAccessor, which simply forwards
+// calls to the underlying type. It assumes |T| provides
+// HierarchicalTimingWheelHandle storage and will simply forward calls to
+// equivalent member function.
+template <typename T>
+struct DefaultHierarchicalTimingWheelHandleAccessor {
+  void SetTimingWheelHandle(T* element,
+                            internal::TimingWheelHandle handle) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->SetTimingWheelHandle(handle);
+  }
+
+  void ClearTimingWheelHandle(T* element) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->ClearTimingWheelHandle();
+  }
+
+  HeapHandle GetHeapHandle(const T* element) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    return htw_handle->GetHeapHandle();
+  }
+
+  void SetHeapHandle(T* element, HeapHandle handle) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->SetHeapHandle(handle);
+  }
+
+  void ClearHeapHandle(T* element) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->ClearHeapHandle();
+  }
+
+  void SetHierarchyIndex(T* element, size_t hierarchy_index) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->SetHierarchyIndex(hierarchy_index);
+  }
+
+  void ClearHierarchyIndex(T* element) const {
+    HierarchicalTimingWheelHandle* htw_handle = element->handle();
+    htw_handle->ClearHierarchyIndex();
+  }
+};
+
+// Gets the delayed run time of the |element|. Assumes the |element| has a
+// public |delayed_run_time| member variable.
+template <typename T>
+struct GetDelayedRunTime {
+  TimeTicks operator()(const T& element) { return element.delayed_run_time; }
+};
+
+// Used for ordering elements in the IntrusiveHeap in the hierarchy.
+template <typename T>
+struct Compare {
+  bool operator()(const T& lhs, const T& rhs) const {
+    return lhs.delayed_run_time > rhs.delayed_run_time;
+  }
+};
+
+// This class is made to optimize the data structure IntrusiveHeap. Timers are
+// implemented by scheduling the user task using TaskRunner::PostDelayedTask().
+// The elements are then inserted in an InstrusiveHeap. It suffers from its time
+// complexity of O(LgN) removal and insertion.
+//
+// This class is an implementation of timing wheel technique. It contains a
+// hierarchy which is a sequence of timing wheels and heaps with different
+// granularities used to span a greater range of intervals. There are two heaps
+// in the hierarchy, each placed on the two ends of the sequence of timing
+// wheels.
+//
+// |T| is a typename for the intervals that are inserted in this class.
+// |TotalWheels| is the number of timing wheels to be constructed in the
+// hierarchy. |WheelSize| is the number of buckets in each of the timing wheel.
+// |SmallestBucketDeltaInMicroseconds| corresponds to the time delta per
+// bucket for the smallest timing wheel in the hierarchy. The time delta per
+// bucket for the following timing wheels are WheelSize *
+// |time_delta_per_bucket| of previous timing wheel.
+// |HierarchicalTimingWheelHandleAccessor| is the type of the object which under
+// the hood manages the HierarchicalTimingWheelHandle. |GetDelayedRunTime| is a
+// function which returns the time when the element is due at.
+//
+// Example:
+// Note: The number enclosing in the curly brackets "{}" are the data
+// structure's hierarchy number. It exists to understand their order in the
+// hierarchy.
+//
+// TotalWheels = 4
+// WheelSize = 100
+// SmallestBucketDeltaInMicroseconds = 500 microseconds
+//
+// Heap{0} - all elements with delays below 500 microseconds
+//
+// Wheel{1} - each bucket of 500microseconds = 0.5ms.
+//          bucket0 contains 0 <= delta < 0.5ms
+//          bucket1 contains 0.5 <= delta < 1ms
+//          Wheel1 contains 0.5 <= delta < 50ms
+//
+// Wheel{2} - each bucket of 50ms.
+//          bucket0 contains 0 <= delta < 50ms
+//          bucket1 contains 50ms <= delta < 100ms
+//          Wheel1 contains 50ms <= delta < 5s
+//
+// Wheel{3} - each bucket of 5s.
+//          bucket0 contains 0 <= delta < 5s
+//          bucket1 contains 5s <= delta < 10s
+//          Wheel1 contains 5s <= delta < 500s
+//
+// Wheel{4} - each bucket of 500s.
+//          bucket0 contains 0 <= delta < 500s
+//          bucket1 contains 500s <= delta < 1000s
+//          Wheel1 contains 500s <= delta < 50000s
+//
+// Heap{5} - all elements with delay above or equals to 500microseconds *
+// (100^4)
+//
+// This class takes O(1) time to insert and cancel timers. However, if a element
+// has a very small or big timer interval, then it's placed in a heap. This
+// means, the removal and insertion won't be as efficient. However, the
+// expectation is that such elements with very small or very big intervals would
+// be very few.
+
+template <typename T,
+          size_t TotalWheels,
+          size_t WheelSize,
+          size_t SmallestBucketDeltaInMicroseconds,
+          typename HierarchicalTimingWheelHandleAccessor =
+              DefaultHierarchicalTimingWheelHandleAccessor<T>,
+          typename GetDelayedRunTime = GetDelayedRunTime<T>,
+          typename Compare = Compare<T>>
+class HierarchicalTimingWheel {
+ public:
+  // Construct a HierarchicalTimingWheel instance where |last_wakeup|
+  // corresponds to the last time it was updated.
+  explicit HierarchicalTimingWheel(
+      TimeTicks last_wakeup,
+      const HierarchicalTimingWheelHandleAccessor&
+          hierarchical_timing_wheel_handle_accessor =
+              HierarchicalTimingWheelHandleAccessor(),
+      const GetDelayedRunTime& get_delayed_run_time = GetDelayedRunTime(),
+      const Compare compare = Compare())
+      : small_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
+        large_delay_heap_(compare, hierarchical_timing_wheel_handle_accessor),
+        last_wakeup_(last_wakeup),
+        hierarchical_timing_wheel_handle_accessor_(
+            hierarchical_timing_wheel_handle_accessor),
+        get_delayed_run_time_(get_delayed_run_time) {}
+
+  HierarchicalTimingWheel(HierarchicalTimingWheel&&) = delete;
+  HierarchicalTimingWheel& operator=(HierarchicalTimingWheel&&) = delete;
+
+  HierarchicalTimingWheel(const HierarchicalTimingWheel&) = delete;
+  HierarchicalTimingWheel& operator=(const HierarchicalTimingWheel&) = delete;
+
+  ~HierarchicalTimingWheel() = default;
+
+  size_t Size() {
+    return small_delay_heap_.size() + large_delay_heap_.size() +
+           std::accumulate(std::begin(wheels_), std::end(wheels_), 0,
+                           [](size_t i, auto& wheel) {
+                             return wheel.total_elements() + i;
+                           });
+  }
+
+  // Inserts the |element| based on its delayed run time into one of the
+  // |wheels_|.
+  typename std::vector<T>::const_iterator Insert(T element) {
+    DCHECK(get_delayed_run_time_(element) > last_wakeup_);
+
+    const TimeDelta delay = get_delayed_run_time_(element) - last_wakeup_;
+    const size_t hierarchy_index = FindHierarchyIndex(delay);
+
+    if (IsHeap(hierarchy_index)) {
+      auto& heap = GetHeapForHierarchyIndex(hierarchy_index);
+      hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
+          &element, hierarchy_index);
+      auto it = heap.insert(std::move(element));
+      return it;
+    } else {
+      auto& wheel = GetTimingWheelForHierarchyIndex(hierarchy_index);
+      hierarchical_timing_wheel_handle_accessor_.SetHierarchyIndex(
+          &element, hierarchy_index);
+      auto it = wheel.Insert(std::move(element), delay);
+      return it;
+    }
+  }
+
+  // Updates the hierarchy and reassigns the elements that need to be
+  // placed in a different timing wheel or heap to reflect their respective
+  // delay. It returns the elements that are expired.
+  std::vector<T> Update(TimeTicks now) {
+    DCHECK(now >= last_wakeup_);
+    std::vector<T> expired_elements;
+
+    // Check for expired elements in the small delay heap.
+    while (!small_delay_heap_.empty() &&
+           get_delayed_run_time_(small_delay_heap_.top()) <= now) {
+      T element = small_delay_heap_.take_top();
+
+      // Clear the hierarchy index since the |element| will be returned.
+      hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(&element);
+
+      expired_elements.push_back(std::move(element));
+    }
+
+    // Look into the timing wheels for elements which have either expired or
+    // need to be moved down the hierarchy.
+    std::vector<T> elements;
+    const TimeDelta time_delta = now - last_wakeup_;
+    const size_t timing_wheels_delay_upperbound =
+        SmallestBucketDeltaInMicroseconds * Pow(WheelSize, TotalWheels);
+    const TimeTicks timing_wheels_maximum_delayed_run_time =
+        now + Milliseconds(timing_wheels_delay_upperbound);
+    last_wakeup_ = now;
+
+    for (size_t wheel_index = 0; wheel_index < TotalWheels; wheel_index++) {
+      wheels_[wheel_index].AdvanceTimeAndRemoveExpiredElements(time_delta,
+                                                               elements);
+    }
+
+    // Keep on removing the top elements from the |large_delay_heap_| which
+    // could be either moved down the hierarchy or are expired.
+    while (!large_delay_heap_.empty() &&
+           get_delayed_run_time_(large_delay_heap_.top()) <
+               timing_wheels_maximum_delayed_run_time) {
+      elements.push_back(std::move(large_delay_heap_.take_top()));
+    }
+
+    // Re-insert elements which haven't expired yet.
+    for (auto& element : elements) {
+      if (now >= get_delayed_run_time_(element)) {
+        hierarchical_timing_wheel_handle_accessor_.ClearHierarchyIndex(
+            &element);
+        expired_elements.emplace_back(std::move(element));
+      } else {
+        // Doesn't clear hierarchy index since the element will have their
+        // hierarchy index overwritten when re-inserted.
+        Insert(std::move(element));
+      }
+    }
+
+    return expired_elements;
+  }
+
+  // Removes the |element|. This is considered as the element getting cancelled
+  // and will never be run.
+  void Remove(HierarchicalTimingWheelHandle& handle) {
+    DCHECK(handle.IsValid());
+    if (handle.GetTimingWheelHandle().IsValid()) {
+      auto& wheel = GetTimingWheelForHierarchyIndex(handle.GetHierarchyIndex());
+      wheel.Remove(handle.GetTimingWheelHandle());
+    } else {
+      auto& heap = GetHeapForHierarchyIndex(handle.GetHierarchyIndex());
+      heap.erase(handle.GetHeapHandle());
+    }
+  }
+
+  // Returns the earliest due element in all of the hierarchy. This method
+  // should only called when the HierarchicalTimingWheel is not empty.
+  typename std::vector<T>::const_reference Top() {
+    DCHECK_NE(Size(), 0u);
+
+    // Check for smallest elements heap first.
+    if (!small_delay_heap_.empty()) {
+      return small_delay_heap_.top();
+    }
+
+    // Iterate from smallest to biggest element wheel.
+    for (size_t i = 0; i < TotalWheels; i++) {
+      if (wheels_[i].total_elements() != 0) {
+        return wheels_[i].Top();
+      }
+    }
+
+    // The result must be in the biggest elements heap.
+    return large_delay_heap_.top();
+  }
+
+ private:
+  bool IsHeap(size_t hierarchy_index) {
+    return hierarchy_index == 0 or hierarchy_index == TotalWheels + 1;
+  }
+
+  auto& GetHeapForHierarchyIndex(size_t hierarchy_index) {
+    DCHECK(hierarchy_index == 0 || hierarchy_index == TotalWheels + 1);
+    return hierarchy_index == 0 ? small_delay_heap_ : large_delay_heap_;
+  }
+
+  auto& GetTimingWheelForHierarchyIndex(size_t hierarchy_index) {
+    DCHECK(hierarchy_index > 0);
+    DCHECK(hierarchy_index < TotalWheels + 1);
+    return wheels_[hierarchy_index - 1];
+  }
+
+  // Calculates the hierarchy index at which a element with |delay| should be
+  // appended in.
+  size_t FindHierarchyIndex(TimeDelta delay) {
+    DCHECK(!delay.is_zero());
+
+    if (delay < Microseconds(SmallestBucketDeltaInMicroseconds))
+      return 0;
+
+    for (size_t i = 0; i < TotalWheels; i++) {
+      if (delay < (wheels_[i].time_delta_per_bucket() * WheelSize)) {
+        return i + 1;
+      }
+    }
+
+    // Return the index of the heap placed at the end of the hierarchy.
+    return TotalWheels + 1;
+  }
+
+  // Computes |a| to the power of |b| at compile time. This is used to compute
+  // the parameter for |TimingWheel| when generating |wheels_| at compile
+  // time.
+  constexpr static std::size_t Pow(size_t a, size_t b) {
+    size_t res = 1;
+    for (size_t i = 0; i < b; i++) {
+      res *= a;
+    }
+    return res;
+  }
+
+  using Wheel =
+      typename internal::TimingWheel<T,
+                                     WheelSize,
+                                     HierarchicalTimingWheelHandleAccessor,
+                                     GetDelayedRunTime>;
+
+  // Generates |wheels_| at compile time.
+  template <size_t... I>
+  static std::array<Wheel, TotalWheels> MakeWheels(std::index_sequence<I...>) {
+    return {(Wheel(Microseconds(SmallestBucketDeltaInMicroseconds *
+                                Pow(WheelSize, I))))...};
+  }
+
+  // The timing wheels where the elements are added according to their delay.
+  std::array<Wheel, TotalWheels> wheels_ =
+      MakeWheels(std::make_index_sequence<TotalWheels>{});
+
+  // There are two heaps enclosing the sequence of timing wheels. The first one
+  // contains elements whose delay is too small to enter a timing wheel. The
+  // second one contains elements whose delay is too big to enter a timing
+  // wheel.
+  IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
+      small_delay_heap_;
+  IntrusiveHeap<T, Compare, HierarchicalTimingWheelHandleAccessor>
+      large_delay_heap_;
+
+  // The last time when the timing wheels were updated.
+  TimeTicks last_wakeup_;
+
+  HierarchicalTimingWheelHandleAccessor
+      hierarchical_timing_wheel_handle_accessor_;
+
+  GetDelayedRunTime get_delayed_run_time_;
+};
+
+}  // namespace base::sequence_manager
+
+#endif
diff --git a/base/task/sequence_manager/hierarchical_timing_wheel_unittest.cc b/base/task/sequence_manager/hierarchical_timing_wheel_unittest.cc
new file mode 100644
index 0000000..e22eb21
--- /dev/null
+++ b/base/task/sequence_manager/hierarchical_timing_wheel_unittest.cc
@@ -0,0 +1,302 @@
+// 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 "base/task/sequence_manager/hierarchical_timing_wheel.h"
+
+#include <math.h>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base::sequence_manager {
+
+namespace {
+
+// Custom comparator for testing.
+template <typename T>
+struct CustomCompare {
+  bool operator()(const T& lhs, const T& rhs) const {
+    return std::tie(lhs.delayed_run_time, lhs.name) >
+           std::tie(rhs.delayed_run_time, rhs.name);
+  }
+};
+
+class Task {
+ public:
+  enum : size_t { kInvalidIndex = std::numeric_limits<size_t>::max() };
+
+  explicit Task(TimeTicks delayed_run_time, std::string name = std::string())
+      : delayed_run_time(delayed_run_time),
+        name(name),
+        handle_(std::make_unique<HierarchicalTimingWheelHandle>()) {}
+
+  HierarchicalTimingWheelHandle* handle() const { return handle_.get(); }
+
+  TimeTicks delayed_run_time;
+
+  // Used as a second comparator key to test the custom comparator
+  // functionality.
+  std::string name;
+
+ private:
+  std::unique_ptr<HierarchicalTimingWheelHandle> handle_;
+};
+
+}  // namespace
+
+// Tests the construction of the object.
+TEST(HierarchicalTimingWheelTest, SimpleTest) {
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u);
+
+  auto* handle =
+      hierarchical_timing_wheel.Insert(Task{baseline + Microseconds(100)})
+          ->handle();
+  EXPECT_EQ(hierarchical_timing_wheel.Size(), 1u);
+
+  hierarchical_timing_wheel.Remove(*handle);
+  EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u);
+}
+
+// Tests whether an element can be added in all the places in the hierarchy.
+TEST(HierarchicalTimingWheelTest, InsertAllDistinctElements) {
+  const size_t kHierarchyCount = 6;
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in each of the hierarchy. The element's
+  // index represents the hierarchy index in which it will be inserted. The
+  // delays are chosen as per the example given in the class's header file.
+  const TimeDelta kDelay[kHierarchyCount] = {
+      Microseconds(100), Microseconds(500), Milliseconds(50),
+      Seconds(5),        Seconds(500),      Seconds(50000)};
+
+  HierarchicalTimingWheelHandle* handles[kHierarchyCount];
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+  }
+
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    EXPECT_EQ(handles[i]->GetHierarchyIndex(), i);
+
+    const bool is_heap_handle = i == 0 || i == kHierarchyCount - 1;
+    EXPECT_EQ(handles[i]->GetHeapHandle().IsValid(), is_heap_handle);
+    EXPECT_EQ(handles[i]->GetTimingWheelHandle().IsValid(), !is_heap_handle);
+  }
+}
+
+// Tests whether multiple elements can be added in the same place in the
+// hierarchy.
+TEST(HierarchicalTimingWheelTest, InsertSimilarElements) {
+  const size_t kTotalElements = 3;
+  const size_t kExpectedHierarchyIndex = 1;
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in the second hierarchy.
+  const TimeDelta kDelay[kTotalElements] = {Microseconds(500), Milliseconds(21),
+                                            Milliseconds(49)};
+
+  HierarchicalTimingWheelHandle* handles[kTotalElements];
+  for (size_t i = 0; i < kTotalElements; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+  }
+
+  for (auto* handle : handles) {
+    EXPECT_EQ(handle->GetHierarchyIndex(), kExpectedHierarchyIndex);
+    EXPECT_EQ(handle->GetHeapHandle().IsValid(), false);
+    EXPECT_EQ(handle->GetTimingWheelHandle().IsValid(), true);
+  }
+}
+
+// Tests whether the hierarchy can be updated and cascading take place from one
+// hierarchy to another for an element.
+TEST(HierarchicalTimingWheelTest, UpdateOneElement) {
+  const size_t kHierarchyCount = 6;
+  TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // An array of deltas which cascades an element from the biggest
+  // hierarchy to the smallest sequentially, and then finally expiring the
+  // element.
+  const TimeDelta kTimeDelta[] = {Seconds(50000) - Seconds(500),
+                                  Seconds(500) - Seconds(5),
+                                  Seconds(5) - Milliseconds(50),
+                                  Milliseconds(50) - Microseconds(500),
+                                  Microseconds(500) - Microseconds(100),
+                                  Microseconds(100)};
+
+  // Create time delta to insert a task at the end of the hierarchy.
+  const TimeTicks delayed_run_time = baseline + Seconds(50000);
+
+  HierarchicalTimingWheelHandle* handle =
+      hierarchical_timing_wheel.Insert(Task{delayed_run_time})->handle();
+
+  std::vector<Task> expired_tasks;
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    const size_t expected_hierarchy_index = kHierarchyCount - i - 1;
+    EXPECT_EQ(handle->GetHierarchyIndex(), expected_hierarchy_index);
+    baseline += kTimeDelta[i];
+    expired_tasks = hierarchical_timing_wheel.Update(baseline);
+
+    // An element will be returned as expired on the last Update.
+    const bool expired = i == kHierarchyCount - 1;
+
+    // We expect one element to be returned, once.
+    EXPECT_EQ(expired_tasks.size() == 0, !expired);
+    EXPECT_EQ(expired_tasks.size() == 1, expired);
+  }
+}
+
+// Tests whether the hierarchy can be updated and cascading take place of
+// multiple existing elements in the hierarchy.
+TEST(HierarchicalTimingWheelTest, UpdateMultipleElements) {
+  const size_t kHierarchyCount = 6;
+  TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in each of the hierarchy. The element's
+  // index represents the hierarchy index in which it will be inserted.
+  const TimeDelta kDelay[kHierarchyCount] = {
+      Microseconds(100), Microseconds(500), Milliseconds(50),
+      Seconds(5),        Seconds(500),      Seconds(50000)};
+
+  HierarchicalTimingWheelHandle* handles[kHierarchyCount];
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+  }
+
+  // This update expires all the inserted elements except the last two.
+  baseline += Seconds(499);
+  std::vector<Task> expired_tasks = hierarchical_timing_wheel.Update(baseline);
+  EXPECT_EQ(expired_tasks.size(), 4u);
+  EXPECT_EQ(handles[kHierarchyCount - 2]->GetHierarchyIndex(), 2u);
+  EXPECT_EQ(handles[kHierarchyCount - 1]->GetHierarchyIndex(), 4u);
+
+  // Expires the last two elements by updating much more than latest delay
+  // element.
+  baseline += Seconds(100000);
+  expired_tasks = hierarchical_timing_wheel.Update(baseline);
+  EXPECT_EQ(expired_tasks.size(), 2u);
+}
+
+// Tests whether an element can be removed from each hierarchy.
+TEST(HierarchicalTimingWheelTest, RemoveElements) {
+  const size_t kHierarchyCount = 6;
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in each of the hierarchy. The element's
+  // index represents the hierarchy index in which it will be inserted.
+  const TimeDelta kDelay[kHierarchyCount] = {
+      Microseconds(100), Microseconds(500), Milliseconds(50),
+      Seconds(5),        Seconds(500),      Seconds(50000)};
+
+  HierarchicalTimingWheelHandle* handles[kHierarchyCount];
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+  }
+
+  for (auto* handle : handles) {
+    hierarchical_timing_wheel.Remove(*handle);
+  }
+
+  // The biggest delay was Seconds(50000). Hence, this would remove any leftover
+  // element, which there aren't supposed to be.
+  std::vector<Task> expired_tasks =
+      hierarchical_timing_wheel.Update(baseline + Seconds(50000));
+  EXPECT_EQ(expired_tasks.empty(), true);
+}
+
+// Tests whether the top element of the hierarchy returned is correct when all
+// distinct elements exist in the hierarchy.
+TEST(HierarchicalTimingWheelTest, TopDifferentElements) {
+  const size_t kHierarchyCount = 6;
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in each of the hierarchy. The element's
+  // index represents the hierarchy index in which it will be inserted.
+  const TimeDelta kDelay[kHierarchyCount] = {
+      Microseconds(100), Microseconds(500), Milliseconds(50),
+      Seconds(5),        Seconds(500),      Seconds(50000)};
+
+  HierarchicalTimingWheelHandle* handles[kHierarchyCount];
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+    const Task& task = hierarchical_timing_wheel.Top();
+    EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]);
+  }
+
+  for (size_t i = 0; i < kHierarchyCount; i++) {
+    const Task& task = hierarchical_timing_wheel.Top();
+    EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]);
+    hierarchical_timing_wheel.Remove(*handles[i]);
+  }
+}
+
+// Tests whether the top element of the hierarchy returned is correct when
+// multiple similar elements are in the hierarchy.
+TEST(HierarchicalTimingWheelTest, TopSimilarElements) {
+  const size_t kTotalElements = 3;
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500> hierarchical_timing_wheel{
+      baseline};
+
+  // Create time delta to insert a task in the first hierarchy.
+  const TimeDelta kDelay[kTotalElements] = {
+      Microseconds(100), Microseconds(200), Microseconds(300)};
+
+  HierarchicalTimingWheelHandle* handles[kTotalElements];
+  for (size_t i = 0; i < kTotalElements; i++) {
+    handles[i] =
+        hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle();
+    const Task& task = hierarchical_timing_wheel.Top();
+    EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]);
+  }
+
+  for (size_t i = 0; i < kTotalElements; i++) {
+    const Task& task = hierarchical_timing_wheel.Top();
+    EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]);
+    hierarchical_timing_wheel.Remove(*handles[i]);
+  }
+}
+
+// Tests whether the |Compare| functor is correctly used.
+TEST(HierarchicalTimingWheelTest, CustomComparator) {
+  const std::string expectedTopTaskName = "a";
+  const TimeTicks baseline = TimeTicks::Now();
+  HierarchicalTimingWheel<Task, 4, 100, 500,
+                          DefaultHierarchicalTimingWheelHandleAccessor<Task>,
+                          GetDelayedRunTime<Task>, CustomCompare<Task>>
+      hierarchical_timing_wheel{baseline};
+
+  // Create time delta to insert a task in the first hierarchy.
+  const TimeDelta kDelay = Microseconds(100);
+
+  // Inserts two elements in the same bucket.
+  hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "z"});
+  hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "a"});
+  const Task& task = hierarchical_timing_wheel.Top();
+
+  // The custom comparator orders by the name's lexicographical order,
+  // since both the elements have the same delayed run time.
+  EXPECT_EQ(task.name, expectedTopTaskName);
+}
+
+}  // namespace base::sequence_manager
diff --git a/base/task/sequence_manager/work_queue.cc b/base/task/sequence_manager/work_queue.cc
index 1bb4ebb..9c8512b6 100644
--- a/base/task/sequence_manager/work_queue.cc
+++ b/base/task/sequence_manager/work_queue.cc
@@ -4,8 +4,11 @@
 
 #include "base/task/sequence_manager/work_queue.h"
 
+#include <atomic>
+
 #include "base/containers/stack_container.h"
 #include "base/debug/alias.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/task/sequence_manager/fence.h"
 #include "base/task/sequence_manager/sequence_manager_impl.h"
 #include "base/task/sequence_manager/task_order.h"
@@ -17,6 +20,39 @@
 namespace sequence_manager {
 namespace internal {
 
+namespace {
+
+const Feature kDifferentWorkQueueCapacities{"DifferentWorkQueueCapacities",
+                                            FEATURE_DISABLED_BY_DEFAULT};
+
+std::atomic<bool> g_different_work_queue_capacities_feature_enabled(false);
+
+}  // namespace
+
+// static
+void WorkQueue::ConfigureCapacityFieldTrial() {
+  g_different_work_queue_capacities_feature_enabled.store(
+      FeatureList::IsEnabled(kDifferentWorkQueueCapacities),
+      std::memory_order_relaxed);
+}
+
+// static
+bool WorkQueue::IsDifferentWorkQueueCapacitiesEnabled() {
+  return g_different_work_queue_capacities_feature_enabled.load(
+      std::memory_order_relaxed);
+}
+
+// static
+size_t WorkQueue::GetStackCapacityChoice() {
+  static const size_t kStackCapacityChoice =
+      IsDifferentWorkQueueCapacitiesEnabled()
+          ? static_cast<size_t>(GetFieldTrialParamByFeatureAsInt(
+                kDifferentWorkQueueCapacities, "StackCapacity",
+                StackCapacity::kDefault))
+          : StackCapacity::kDefault;
+  return kStackCapacityChoice;
+}
+
 WorkQueue::WorkQueue(TaskQueueImpl* task_queue,
                      const char* name,
                      QueueType queue_type)
@@ -219,14 +255,12 @@
   return pending_task;
 }
 
-bool WorkQueue::RemoveAllCanceledTasksFromFront() {
-  if (!work_queue_sets_)
-    return false;
-
+template <size_t stack_capacity>
+bool WorkQueue::RemoveAllCancelledTasksFromFrontImpl() {
   // Since task destructors could have a side-effect of deleting this task queue
   // we move cancelled tasks into a temporary container which can be emptied
   // without accessing |this|.
-  StackVector<Task, 8> tasks_to_delete;
+  StackVector<Task, stack_capacity> tasks_to_delete;
 
   while (!tasks_.empty()) {
     const auto& pending_task = tasks_.front();
@@ -256,6 +290,31 @@
   return !tasks_to_delete->empty();
 }
 
+template bool WorkQueue::RemoveAllCancelledTasksFromFrontImpl<
+    WorkQueue::StackCapacity::kSmall>();
+template bool WorkQueue::RemoveAllCancelledTasksFromFrontImpl<
+    WorkQueue::StackCapacity::kMedium>();
+template bool WorkQueue::RemoveAllCancelledTasksFromFrontImpl<
+    WorkQueue::StackCapacity::kLarge>();
+template bool WorkQueue::RemoveAllCancelledTasksFromFrontImpl<
+    WorkQueue::StackCapacity::kDefault>();
+
+bool WorkQueue::RemoveAllCanceledTasksFromFront() {
+  if (!work_queue_sets_)
+    return false;
+
+  switch (GetStackCapacityChoice()) {
+    case StackCapacity::kSmall:
+      return RemoveAllCancelledTasksFromFrontImpl<StackCapacity::kSmall>();
+    case StackCapacity::kMedium:
+      return RemoveAllCancelledTasksFromFrontImpl<StackCapacity::kMedium>();
+    case StackCapacity::kLarge:
+      return RemoveAllCancelledTasksFromFrontImpl<StackCapacity::kLarge>();
+    default:
+      return RemoveAllCancelledTasksFromFrontImpl<StackCapacity::kDefault>();
+  }
+}
+
 void WorkQueue::AssignToWorkQueueSets(WorkQueueSets* work_queue_sets) {
   work_queue_sets_ = work_queue_sets;
 }
diff --git a/base/task/sequence_manager/work_queue.h b/base/task/sequence_manager/work_queue.h
index d236666..082cb1a10 100644
--- a/base/task/sequence_manager/work_queue.h
+++ b/base/task/sequence_manager/work_queue.h
@@ -163,7 +163,29 @@
   void CollectTasksOlderThan(TaskOrder reference,
                              std::vector<const Task*>* result) const;
 
+  // This is for an experiment where we try different WorkQueue capacities
+  // when deleting tasks in RemoveAllCanceledTasksFromFront() (see
+  // crbug.com/1347892). Tests spawn threads around the time FeatureList is
+  // initialized, which creates race conditions as WorkQueue is trying to read
+  // the Feature while threads spawn. To solve this, we store the value of the
+  // Feature in a std::atomic<bool> and initialize it once in SetInstance(), and
+  // read this atomic value instead of calling IsEnabled().
+  static void ConfigureCapacityFieldTrial();
+  static bool IsDifferentWorkQueueCapacitiesEnabled();
+
  private:
+  enum StackCapacity : size_t {
+    kSmall = 4,
+    kMedium = 16,
+    kLarge = 24,
+    kDefault = 8,
+  };
+
+  static size_t GetStackCapacityChoice();
+
+  template <size_t stack_capacity>
+  bool RemoveAllCancelledTasksFromFrontImpl();
+
   bool InsertFenceImpl(Fence fence);
 
   TaskQueueImpl::TaskDeque tasks_;
diff --git a/base/test/launcher/test_launcher_unittest.cc b/base/test/launcher/test_launcher_unittest.cc
index 3c9bc2ad..46b98f9 100644
--- a/base/test/launcher/test_launcher_unittest.cc
+++ b/base/test/launcher/test_launcher_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/base64.h"
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -809,7 +810,8 @@
 
 // Validate delegate produces correct command line.
 TEST_F(UnitTestLauncherDelegateTester, GetCommandLine) {
-  UnitTestLauncherDelegate launcher_delegate(&defaultPlatform, 10u, true);
+  UnitTestLauncherDelegate launcher_delegate(&defaultPlatform, 10u, true,
+                                             DoNothing());
   TestLauncherDelegate* delegate_ptr = &launcher_delegate;
 
   std::vector<std::string> test_names(5, "Tests");
@@ -1049,7 +1051,8 @@
 
 // Validate delegate sets batch size correctly.
 TEST_F(UnitTestLauncherDelegateTester, BatchSize) {
-  UnitTestLauncherDelegate launcher_delegate(&defaultPlatform, 15u, true);
+  UnitTestLauncherDelegate launcher_delegate(&defaultPlatform, 15u, true,
+                                             DoNothing());
   TestLauncherDelegate* delegate_ptr = &launcher_delegate;
   EXPECT_EQ(delegate_ptr->GetBatchSize(), 15u);
 }
diff --git a/base/test/launcher/unit_test_launcher.cc b/base/test/launcher/unit_test_launcher.cc
index f58381d..58f05f20 100644
--- a/base/test/launcher/unit_test_launcher.cc
+++ b/base/test/launcher/unit_test_launcher.cc
@@ -151,6 +151,7 @@
                             int default_batch_limit,
                             size_t retry_limit,
                             bool use_job_objects,
+                            RepeatingClosure timeout_callback,
                             OnceClosure gtest_init) {
   base::test::AllowCheckIsTestToBeCalled();
 
@@ -218,7 +219,7 @@
 
   DefaultUnitTestPlatformDelegate platform_delegate;
   UnitTestLauncherDelegate delegate(&platform_delegate, batch_limit,
-                                    use_job_objects);
+                                    use_job_objects, timeout_callback);
   TestLauncher launcher(&delegate, parallel_jobs, retry_limit);
   bool success = launcher.Run();
 
@@ -277,6 +278,7 @@
   }
   return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
                                  kDefaultTestBatchLimit, retry_limit, true,
+                                 DoNothing(),
                                  BindOnce(&InitGoogleTestChar, &argc, argv));
 }
 
@@ -285,7 +287,7 @@
                             RunTestSuiteCallback run_test_suite) {
   CommandLine::Init(argc, argv);
   return LaunchUnitTestsInternal(std::move(run_test_suite), 1U,
-                                 kDefaultTestBatchLimit, 1U, true,
+                                 kDefaultTestBatchLimit, 1U, true, DoNothing(),
                                  BindOnce(&InitGoogleTestChar, &argc, argv));
 }
 
@@ -294,10 +296,12 @@
                                size_t parallel_jobs,
                                int default_batch_limit,
                                bool use_job_objects,
+                               RepeatingClosure timeout_callback,
                                RunTestSuiteCallback run_test_suite) {
   CommandLine::Init(argc, argv);
   return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
                                  default_batch_limit, 1U, use_job_objects,
+                                 timeout_callback,
                                  BindOnce(&InitGoogleTestChar, &argc, argv));
 }
 
@@ -314,6 +318,7 @@
   }
   return LaunchUnitTestsInternal(std::move(run_test_suite), parallel_jobs,
                                  kDefaultTestBatchLimit, 1U, use_job_objects,
+                                 DoNothing(),
                                  BindOnce(&InitGoogleTestWChar, &argc, argv));
 }
 #endif  // BUILDFLAG(IS_WIN)
@@ -369,10 +374,12 @@
 UnitTestLauncherDelegate::UnitTestLauncherDelegate(
     UnitTestPlatformDelegate* platform_delegate,
     size_t batch_limit,
-    bool use_job_objects)
+    bool use_job_objects,
+    RepeatingClosure timeout_callback)
     : platform_delegate_(platform_delegate),
       batch_limit_(batch_limit),
-      use_job_objects_(use_job_objects) {}
+      use_job_objects_(use_job_objects),
+      timeout_callback_(timeout_callback) {}
 
 UnitTestLauncherDelegate::~UnitTestLauncherDelegate() {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -416,4 +423,8 @@
   return batch_limit_;
 }
 
+void UnitTestLauncherDelegate::OnTestTimedOut(const CommandLine& cmd_line) {
+  timeout_callback_.Run();
+}
+
 }  // namespace base
diff --git a/base/test/launcher/unit_test_launcher.h b/base/test/launcher/unit_test_launcher.h
index 75a6c8a..4e867e6 100644
--- a/base/test/launcher/unit_test_launcher.h
+++ b/base/test/launcher/unit_test_launcher.h
@@ -40,11 +40,15 @@
 // |default_batch_limit| is the default size of test batch
 // (use 0 to disable batching).
 // |use_job_objects| determines whether to use job objects.
+// |timeout_callback| is called each time a test batch times out. It can be used
+// as a cue to print additional debugging information about the test system,
+// such as log files or the names of running processes.
 int LaunchUnitTestsWithOptions(int argc,
                                char** argv,
                                size_t parallel_jobs,
                                int default_batch_limit,
                                bool use_job_objects,
+                               RepeatingClosure timeout_callback,
                                RunTestSuiteCallback run_test_suite);
 
 #if BUILDFLAG(IS_WIN)
@@ -128,7 +132,8 @@
  public:
   UnitTestLauncherDelegate(UnitTestPlatformDelegate* delegate,
                            size_t batch_limit,
-                           bool use_job_objects);
+                           bool use_job_objects,
+                           RepeatingClosure timeout_callback);
 
   UnitTestLauncherDelegate(const UnitTestLauncherDelegate&) = delete;
   UnitTestLauncherDelegate& operator=(const UnitTestLauncherDelegate&) = delete;
@@ -151,6 +156,8 @@
 
   size_t GetBatchSize() override;
 
+  void OnTestTimedOut(const CommandLine& cmd_line) override;
+
   ThreadChecker thread_checker_;
 
   raw_ptr<UnitTestPlatformDelegate> platform_delegate_;
@@ -160,6 +167,9 @@
 
   // Determines whether we use job objects on Windows.
   bool use_job_objects_;
+
+  // Callback to invoke when a test process times out.
+  RepeatingClosure timeout_callback_;
 };
 
 // We want to stop throwing away duplicate test filter file flags, but we're
diff --git a/base/values.cc b/base/values.cc
index 9dc7ff6..c6f102d 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -12,6 +12,7 @@
 
 #include "base/as_const.h"
 #include "base/bit_cast.h"
+#include "base/check.h"
 #include "base/check_op.h"
 #include "base/containers/checked_iterators.h"
 #include "base/containers/cxx20_erase_vector.h"
@@ -345,10 +346,12 @@
 }
 
 bool Value::GetBool() const {
+  DCHECK(is_bool());
   return absl::get<bool>(data_);
 }
 
 int Value::GetInt() const {
+  DCHECK(is_int());
   return absl::get<int>(data_);
 }
 
@@ -362,30 +365,37 @@
 }
 
 const std::string& Value::GetString() const {
+  DCHECK(is_string());
   return absl::get<std::string>(data_);
 }
 
 std::string& Value::GetString() {
+  DCHECK(is_string());
   return absl::get<std::string>(data_);
 }
 
 const Value::BlobStorage& Value::GetBlob() const {
+  DCHECK(is_blob());
   return absl::get<BlobStorage>(data_);
 }
 
 const Value::Dict& Value::GetDict() const {
+  DCHECK(is_dict());
   return absl::get<Dict>(data_);
 }
 
 Value::Dict& Value::GetDict() {
+  DCHECK(is_dict());
   return absl::get<Dict>(data_);
 }
 
 const Value::List& Value::GetList() const {
+  DCHECK(is_list());
   return absl::get<List>(data_);
 }
 
 Value::List& Value::GetList() {
+  DCHECK(is_list());
   return absl::get<List>(data_);
 }
 
diff --git a/build/sanitizers/tsan_suppressions.cc b/build/sanitizers/tsan_suppressions.cc
index 5cabfb3f..e4b64ff 100644
--- a/build/sanitizers/tsan_suppressions.cc
+++ b/build/sanitizers/tsan_suppressions.cc
@@ -104,6 +104,7 @@
 
     // https://crbug.com/794920
     "race:base::debug::SetCrashKeyString\n"
+    "race:crash_reporter::internal::CrashKeyStringImpl::Clear\n"
     "race:crash_reporter::internal::CrashKeyStringImpl::Set\n"
 
     // http://crbug.com/927330
diff --git a/build/util/version.gni b/build/util/version.gni
deleted file mode 100644
index eec226e..0000000
--- a/build/util/version.gni
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# TODO(crbug.com/1347803): Delete this file.
-import("//chrome/version.gni")
diff --git a/cc/paint/image_transfer_cache_entry_unittest.cc b/cc/paint/image_transfer_cache_entry_unittest.cc
index 817d311..b18eb55 100644
--- a/cc/paint/image_transfer_cache_entry_unittest.cc
+++ b/cc/paint/image_transfer_cache_entry_unittest.cc
@@ -36,6 +36,7 @@
 #include "ui/gl/gl_context_egl.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/create_gr_gl_interface.h"
 #include "ui/gl/init/gl_factory.h"
 
@@ -86,7 +87,8 @@
  public:
   void SetUp() override {
     // Initialize a GL GrContext for Skia.
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                  gfx::Size());
     ASSERT_TRUE(surface_);
     share_group_ = base::MakeRefCounted<gl::GLShareGroup>();
     gl_context_ = base::MakeRefCounted<gl::GLContextEGL>(share_group_.get());
@@ -94,9 +96,9 @@
     ASSERT_TRUE(
         gl_context_->Initialize(surface_.get(), gl::GLContextAttribs()));
     ASSERT_TRUE(gl_context_->MakeCurrent(surface_.get()));
-    sk_sp<GrGLInterface> interface(gl::init::CreateGrGLInterface(
+    sk_sp<GrGLInterface> gl_interface(gl::init::CreateGrGLInterface(
         *gl_context_->GetVersionInfo(), false /* use_version_es2 */));
-    gr_context_ = GrDirectContext::MakeGL(std::move(interface));
+    gr_context_ = GrDirectContext::MakeGL(std::move(gl_interface));
     ASSERT_TRUE(gr_context_);
   }
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 3867c5f8..d6d99fc 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1770,6 +1770,7 @@
     "//services/device/public/java:geolocation_java",
     "//services/device/public/java:geolocation_java_test_support",
     "//services/device/public/mojom:mojom_java",
+    "//services/media_session/public/mojom:mojom_java",
     "//services/network/public/mojom:mojom_java",
     "//services/network/public/mojom:mojom_proxy_config_java",
     "//services/network/public/mojom:url_loader_base_java",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 7e3ed53..631f2f3 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -504,6 +504,7 @@
   "java/res/layout/custom_tabs_control_container.xml",
   "java/res/layout/custom_tabs_handle_view.xml",
   "java/res/layout/custom_tabs_navigation_bar.xml",
+  "java/res/layout/custom_tabs_toast_branding_layout.xml",
   "java/res/layout/custom_tabs_toolbar.xml",
   "java/res/layout/custom_tabs_toolbar_button.xml",
   "java/res/layout/custom_tabs_topbar.xml",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index d764e78..9681207 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -402,6 +402,10 @@
         mMediator.softCleanup();
     }
 
+    void hardCleanup() {
+        mMediator.hardCleanup();
+    }
+
     void prepareTabSwitcherView() {
         if (mGlobalLayoutListener != null) {
             mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
@@ -409,6 +413,7 @@
         registerLayoutChangeListener();
         mRecyclerView.prepareTabSwitcherView();
         mMediator.prepareTabSwitcherView();
+        mMediator.registerOnScrolledListener(mRecyclerView);
     }
 
     private void registerLayoutChangeListener() {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index cbb6eb1..08ca85c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -33,6 +33,8 @@
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
 
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
@@ -99,8 +101,10 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Mediator for business logic for the tab grid. This class should be initialized with a list of
@@ -362,6 +366,7 @@
 
     private static final String TAG = "TabListMediator";
     private static Map<Integer, Integer> sTabClosedFromMapTabClosedFromMap = new HashMap<>();
+    private static Set<Integer> sViewedTabIds = new HashSet<>();
 
     private final Context mContext;
     private final TabListModel mModel;
@@ -384,6 +389,10 @@
     private @UiType int mUiType;
     private int mSearchChipIconDrawableId;
     private GridLayoutManager mGridLayoutManager;
+    // mRecyclerView and mOnScrollListener are null, unless the the price drop IPH or badge is
+    // enabled.
+    private @Nullable RecyclerView mRecyclerView;
+    private @Nullable OnScrollListener mOnScrollListener;
 
     private final TabActionListener mTabSelectedListener = new TabActionListener() {
         @Override
@@ -1231,8 +1240,20 @@
         return false;
     }
 
+    /**
+     * Add the tab id of a {@Tab} that has been viewed to the sViewedTabIds set.
+     * @param tabIndex  The tab index of a {@Tab} the user has viewed.
+     */
+    private void addViewedTabId(int tabIndex) {
+        assert !mTabModelSelector.getCurrentModel().isIncognito();
+        int tabId = mModel.get(tabIndex).model.get(TabProperties.TAB_ID);
+        assert TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId) != null;
+        sViewedTabIds.add(tabId);
+    }
+
     void postHiding() {
         mVisible = false;
+        unregisterOnScrolledListener();
     }
 
     private boolean isSelectedTab(PseudoTab tab, int tabModelSelectedTabId) {
@@ -1256,6 +1277,33 @@
         }
     }
 
+    void hardCleanup() {
+        assert !mVisible;
+        if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
+                && (PriceTrackingFeatures.isPriceDropIphEnabled()
+                        || PriceTrackingFeatures.isPriceDropBadgeEnabled())) {
+            saveSeenPriceDrops();
+        }
+        sViewedTabIds.clear();
+    }
+
+    /**
+     * While leaving the tab switcher grid this update whether a tab's current price drop has or has
+     * not been seen.
+     */
+    private void saveSeenPriceDrops() {
+        for (Integer tabId : sViewedTabIds) {
+            Tab tab = TabModelUtils.getTabById(mTabModelSelector.getModel(false), tabId);
+            if (tab != null && isUngroupedTab(tab.getId())) {
+                ShoppingPersistedTabData.from(tab, (sptd) -> {
+                    if (sptd != null && sptd.getPriceDrop() != null) {
+                        sptd.setIsCurrentPriceDropSeen(true);
+                    }
+                });
+            }
+        }
+    }
+
     private void updateTab(int index, PseudoTab pseudoTab, boolean isSelected, boolean isUpdatingId,
             boolean quickMode) {
         if (index < 0 || index >= mModel.size()) return;
@@ -1329,6 +1377,11 @@
         return getRelatedTabsForId(tabId).size() == 1;
     }
 
+    @VisibleForTesting
+    public Set<Integer> getViewedTabIdsForTesting() {
+        return sViewedTabIds;
+    }
+
     /**
      * @return The callback that hosts the logic for swipe and drag related actions.
      */
@@ -1380,6 +1433,40 @@
     }
 
     /**
+     * Adds an on scroll listener to {@link TabListRecyclerView} that determines whether a tab
+     * thumbnail is within view after a scroll is completed.
+     * @param recyclerView the {@link TabListRecyclerView} to add the listener too.
+     */
+    void registerOnScrolledListener(RecyclerView recyclerView) {
+        if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
+                && (PriceTrackingFeatures.isPriceDropIphEnabled()
+                        || PriceTrackingFeatures.isPriceDropBadgeEnabled())) {
+            mRecyclerView = recyclerView;
+            mOnScrollListener = new OnScrollListener() {
+                @Override
+                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                    if (!mTabModelSelector.isIncognitoSelected()) {
+                        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+                            if (mRecyclerView.getLayoutManager().isViewPartiallyVisible(
+                                        mRecyclerView.getChildAt(i), false, true)) {
+                                addViewedTabId(i);
+                            }
+                        }
+                    }
+                }
+            };
+            mRecyclerView.addOnScrollListener(mOnScrollListener);
+        }
+    }
+
+    private void unregisterOnScrolledListener() {
+        if (mRecyclerView != null && mOnScrollListener != null) {
+            mRecyclerView.removeOnScrollListener(mOnScrollListener);
+            mOnScrollListener = null;
+        }
+    }
+
+    /**
      * Span count is computed based on screen width for tablets and orientation for phones.
      * When in multi-window mode on phone, the span count is fixed to 2 to keep tab card size
      * reasonable.
@@ -1478,6 +1565,7 @@
         if (mTemplateUrlObserver != null) {
             TemplateUrlServiceFactory.get().removeObserver(mTemplateUrlObserver);
         }
+        unregisterOnScrolledListener();
     }
 
     private void addTabInfoToModel(final PseudoTab pseudoTab, int index, boolean isSelected) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index eec795c9..2bd0f054 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -805,6 +805,11 @@
         mTabListCoordinator.softCleanup();
     }
 
+    @Override
+    public void hardCleanup() {
+        mTabListCoordinator.hardCleanup();
+    }
+
     // ResetHandler implementation.
     @Override
     public void onDestroy() {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
index 7e271d6..8052cc0f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediator.java
@@ -188,6 +188,13 @@
          * Release the thumbnail {@link Bitmap} but keep the {@link TabGridView}.
          */
         void softCleanup();
+
+        /**
+         * Check to see if there are any not viewed price drops when the user leaves the tab
+         * switcher. This is done only before the coordinator is destroyed to reduce the amount of
+         * calls to ShoppingPersistedTabData.
+         */
+        void hardCleanup();
     }
 
     /**
@@ -434,8 +441,10 @@
         mContainerView = containerView;
 
         mSoftClearTabListRunnable = mResetHandler::softCleanup;
-        mClearTabListRunnable =
-                () -> mResetHandler.resetWithTabList(null, false, mShowTabsInMruOrder);
+        mClearTabListRunnable = () -> {
+            mResetHandler.hardCleanup();
+            mResetHandler.resetWithTabList(null, false, mShowTabsInMruOrder);
+        };
         mHandler = new Handler();
         mTabContentManager = tabContentManager;
 
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index 9faf173..ff9d13c 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -169,6 +169,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 /**
  * Tests for {@link TabListMediator}.
  */
@@ -253,6 +254,8 @@
     @Mock
     RecyclerView mRecyclerView;
     @Mock
+    TabListRecyclerView mTabListRecyclerView;
+    @Mock
     RecyclerView.Adapter mAdapter;
     @Mock
     TabGroupModelFilter mTabGroupModelFilter;
@@ -305,6 +308,8 @@
     ArgumentCaptor<ComponentCallbacks> mComponentCallbacksCaptor;
     @Captor
     ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver> mTemplateUrlServiceObserver;
+    @Captor
+    ArgumentCaptor<RecyclerView.OnScrollListener> mOnScrollListenerCaptor;
     @Mock
     EndpointFetcher.Natives mEndpointFetcherJniMock;
     @Mock
@@ -3145,6 +3150,31 @@
                         -1));
     }
 
+    @Test
+    public void testPriceDropSeen() throws TimeoutException {
+        setPriceTrackingEnabledForTesting(true);
+        PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
+        PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
+                PriceTrackingUtilities.TRACK_PRICES_ON_TABS, true);
+
+        doReturn(false).when(mTab1).isIncognito();
+        doReturn(false).when(mTab2).isIncognito();
+
+        List<Tab> tabs = new ArrayList<>();
+        tabs.add(mTabModel.getTabAt(0));
+        tabs.add(mTabModel.getTabAt(1));
+
+        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs),
+                /*quickMode =*/false, /*mruMode =*/false);
+
+        prepareRecyclerViewForScroll();
+        mMediator.registerOnScrolledListener(mRecyclerView);
+        verify(mRecyclerView).addOnScrollListener(mOnScrollListenerCaptor.capture());
+        mOnScrollListenerCaptor.getValue().onScrolled(
+                mRecyclerView, /*dx =*/mTabModel.getCount(), /*dy =*/0);
+        assertEquals(2, mMediator.getViewedTabIdsForTesting().size());
+    }
+
     private void setUpCloseButtonDescriptionString(boolean isGroup) {
         if (isGroup) {
             doAnswer(invocation -> {
@@ -3400,11 +3430,23 @@
         doReturn(mPriceDrop).when(mShoppingPersistedTabData).getPriceDrop();
     }
 
+    private void prepareRecyclerViewForScroll() {
+        View seenView = mock(View.class);
+        for (int i = 0; i < mTabModel.getCount(); i++) {
+            when(mRecyclerView.getChildAt(i)).thenReturn(seenView);
+        }
+
+        doReturn(true).when(mGridLayoutManager).isViewPartiallyVisible(seenView, false, true);
+        doReturn(mTabModel.getCount()).when(mRecyclerView).getChildCount();
+    }
+
     private static void setPriceTrackingEnabledForTesting(boolean value) {
         FeatureList.TestValues testValues = new FeatureList.TestValues();
         testValues.addFeatureFlagOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING, true);
         testValues.addFieldTrialParamOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING,
                 PriceTrackingFeatures.PRICE_TRACKING_PARAM, String.valueOf(value));
+        testValues.addFieldTrialParamOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING,
+                PriceTrackingFeatures.PRICE_DROP_IPH_ENABLED_PARAM, String.valueOf(value));
         FeatureList.setTestValues(testValues);
     }
 }
diff --git a/chrome/android/java/res/layout/custom_tabs_toast_branding_layout.xml b/chrome/android/java/res/layout/custom_tabs_toast_branding_layout.xml
new file mode 100644
index 0000000..3e600b3
--- /dev/null
+++ b/chrome/android/java/res/layout/custom_tabs_toast_branding_layout.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="44dp"
+    android:gravity="center"
+    android:maxLines="1"
+    android:drawableStart="@mipmap/app_icon"
+    android:drawablePadding="@dimen/custom_tabs_menu_footer_margin_horizontal"
+    android:background="@drawable/custom_toast_background"
+    app:drawableWidth="@dimen/chip_icon_size"
+    app:drawableHeight="@dimen/chip_icon_size"
+    style="@style/TextAppearance.TextSmall.Primary.Baseline" />
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/OWNERS
index e31c9547..ab4c0db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/OWNERS
@@ -3,3 +3,4 @@
 
 per-file ContextualSearchSceneLayer.java=donnd@chromium.org
 per-file ContextualSearchSceneLayer.java=twellington@chromium.org
+per-file TabStripSceneLayer.java=skavuluru@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingChecker.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingChecker.java
index 170155e..137b740 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingChecker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingChecker.java
@@ -109,9 +109,9 @@
 
     private @BrandingDecision int makeBrandingDecisionFromLaunchTime(
             long startTime, long lastBrandingShowTime) {
-        // TODO(crrev.com/c/3769601): Support toast branding.
-        if (lastBrandingShowTime == BRANDING_TIME_NOT_FOUND
-                || startTime - lastBrandingShowTime >= mBrandingCadence) {
+        if (lastBrandingShowTime == BRANDING_TIME_NOT_FOUND) {
+            return BrandingDecision.TOAST;
+        } else if (startTime - lastBrandingShowTime >= mBrandingCadence) {
             return BrandingDecision.TOOLBAR;
         } else {
             return BrandingDecision.NONE;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
index 9528cfa..8dbcce8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
@@ -6,17 +6,22 @@
 
 import android.content.Context;
 import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.CallbackController;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.ui.widget.Toast;
 
 import java.util.concurrent.TimeUnit;
 
@@ -53,8 +58,10 @@
     private final @BrandingDecision OneshotSupplierImpl<Integer> mBrandingDecision =
             new OneshotSupplierImpl<>();
     private final BrandingChecker mBrandingChecker;
+    private final Context mContext;
 
     private ToolbarBrandingDelegate mToolbarBrandingDelegate;
+    private @Nullable Toast mToast;
     private long mToolbarInitializedTime;
     private boolean mIsBrandingShowing;
 
@@ -64,12 +71,13 @@
      * @param packageName The package name for the embedded app.
      */
     public BrandingController(Context context, String packageName) {
+        mContext = context;
         mBrandingDecision.onAvailable((decision) -> maybeMakeBrandingDecision());
 
         // TODO(https://crbug.com/1350661): Start branding checker during CCT warm up.
         mBrandingChecker = new BrandingChecker(context, packageName,
                 SharedPreferencesBrandingTimeStorage.getInstance(), mBrandingDecision::set,
-                BRANDING_CADENCE_MS.getValue(), BrandingDecision.TOOLBAR);
+                BRANDING_CADENCE_MS.getValue(), BrandingDecision.TOAST);
         mBrandingChecker.executeWithTaskTraits(TaskTraits.USER_VISIBLE_MAY_BLOCK);
     }
 
@@ -105,13 +113,20 @@
 
         @BrandingDecision
         int brandingDecision = mBrandingDecision.get();
-        if (BrandingDecision.NONE == brandingDecision) {
-            mToolbarBrandingDelegate.showRegularToolbar();
-            return;
+        switch (brandingDecision) {
+            case BrandingDecision.NONE:
+                mToolbarBrandingDelegate.showRegularToolbar();
+                break;
+            case BrandingDecision.TOOLBAR:
+                showToolbarBranding(remainingBrandingTime);
+                break;
+            case BrandingDecision.TOAST:
+                mToolbarBrandingDelegate.showRegularToolbar();
+                showToastBranding(remainingBrandingTime);
+                break;
+            default:
+                assert false : "Unreachable state!";
         }
-
-        // TODO(wenyufu): Support toast branding.
-        showToolbarBranding(remainingBrandingTime);
     }
 
     private void showToolbarBranding(long durationMs) {
@@ -126,9 +141,24 @@
                 mCallbackController.makeCancelable(hideToolbarBranding), durationMs);
     }
 
+    private void showToastBranding(long durationMs) {
+        String appName = mContext.getResources().getString(R.string.app_name);
+        String toastText =
+                mContext.getResources().getString(R.string.twa_running_in_chrome_template, appName);
+        TextView runInChromeTextView = (TextView) LayoutInflater.from(mContext).inflate(
+                R.layout.custom_tabs_toast_branding_layout, null, false);
+        runInChromeTextView.setText(toastText);
+        mToast = new Toast(mContext, /*toastView*/ runInChromeTextView);
+        mToast.setDuration((int) durationMs);
+        mToast.show();
+    }
+
     /** Destroy this instance an cancel all scheduled callbacks */
     public void destroy() {
         mCallbackController.destroy();
+        if (mToast != null) {
+            mToast.cancel();
+        }
     }
 
     @VisibleForTesting
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingDecision.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingDecision.java
index 91379ac3..1b6d509 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingDecision.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingDecision.java
@@ -13,13 +13,9 @@
  * Class used to indicate what branding decision needs to make for the embedded app.
  */
 @Retention(RetentionPolicy.SOURCE)
-@IntDef({
-        BrandingDecision.NONE, BrandingDecision.TOOLBAR,
-        // BrandingDecision.TOAST
-})
+@IntDef({BrandingDecision.NONE, BrandingDecision.TOOLBAR, BrandingDecision.TOAST})
 @interface BrandingDecision {
     int NONE = 1;
     int TOOLBAR = 2;
-    // TODO(crrev.com/c/3769601): Support toast branding.
-    // int TOAST = 3; // Default.
+    int TOAST = 3;
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
index 2f6058a..16d1fde 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
@@ -16,6 +16,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Build;
@@ -106,20 +108,56 @@
 
     private MediaSessionBroadcastReceiver mMediaSessionReceiver;
 
-    private MediaActionButtonsManager mMediaActionsButtonsManager;
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    MediaActionButtonsManager mMediaActionsButtonsManager;
 
     /**
      * A helper class for managing media action buttons in PictureInPicture window.
      */
-    private class MediaActionButtonsManager {
-        private final RemoteAction mPreviousTrack;
-        private final RemoteAction mPlay;
-        private final RemoteAction mPause;
-        private final RemoteAction mReplay;
-        private final RemoteAction mNextTrack;
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    class MediaActionButtonsManager {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mPreviousTrack;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mPlay;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mPause;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mReplay;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mNextTrack;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final RemoteAction mHangUp;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final ToggleRemoteAction mMicrophone;
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        final ToggleRemoteAction mCamera;
 
         private @PlaybackState int mPlaybackState;
 
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        class ToggleRemoteAction {
+            private final RemoteAction mActionOn;
+            private final RemoteAction mActionOff;
+            private boolean mState;
+
+            private ToggleRemoteAction(
+                    RemoteAction actionOn, RemoteAction actionOff) {
+                mActionOn = actionOn;
+                mActionOff = actionOff;
+                mState = false;
+            }
+
+            private void setState(boolean on) {
+                mState = on;
+            }
+
+            @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+            RemoteAction getAction() {
+                return mState ? mActionOn : mActionOff;
+            }
+        }
+
         /** A set of {@link MediaSessionAction}. */
         private HashSet<Integer> mVisibleActions;
 
@@ -134,35 +172,85 @@
                     R.string.accessibility_replay);
             mNextTrack = createRemoteAction(MediaSessionAction.NEXT_TRACK,
                     R.drawable.ic_skip_next_white_36dp, R.string.accessibility_next_track);
+            mHangUp = createRemoteAction(MediaSessionAction.HANG_UP,
+                    R.drawable.ic_call_end_white_36dp, R.string.accessibility_hang_up);
+            mMicrophone = new ToggleRemoteAction(
+                    createRemoteAction(MediaSessionAction.TOGGLE_MICROPHONE,
+                            R.drawable.ic_mic_white_36dp, R.string.accessibility_mute_microphone),
+                    createRemoteAction(MediaSessionAction.TOGGLE_MICROPHONE,
+                            R.drawable.ic_mic_off_white_36dp,
+                            R.string.accessibility_unmute_microphone));
+            mCamera = new ToggleRemoteAction(createRemoteAction(MediaSessionAction.TOGGLE_CAMERA,
+                                                     R.drawable.ic_videocam_white_36dp,
+                                                     R.string.accessibility_turn_off_camera),
+                    createRemoteAction(MediaSessionAction.TOGGLE_CAMERA,
+                            R.drawable.ic_videocam_off_white_36dp,
+                            R.string.accessibility_turn_on_camera));
 
             mPlaybackState = PlaybackState.END_OF_VIDEO;
             mVisibleActions = new HashSet<>();
         }
 
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         @SuppressLint("NewApi")
-        private ArrayList<RemoteAction> getActionsForPictureInPictureParams() {
+        ArrayList<RemoteAction> getActionsForPictureInPictureParams() {
             ArrayList<RemoteAction> actions = new ArrayList<>();
 
-            mPreviousTrack.setEnabled(mVisibleActions.contains(MediaSessionAction.PREVIOUS_TRACK));
-            actions.add(mPreviousTrack);
-
-            RemoteAction playPauseAction = null;
-            switch (mPlaybackState) {
-                case PlaybackState.PLAYING:
-                    playPauseAction = mPause;
-                    break;
-                case PlaybackState.PAUSED:
-                    playPauseAction = mPlay;
-                    break;
-                case PlaybackState.END_OF_VIDEO:
-                    playPauseAction = mReplay;
-                    break;
+            boolean shouldShowPreviousNextTrack =
+                    mVisibleActions.contains(MediaSessionAction.PREVIOUS_TRACK)
+                    || mVisibleActions.contains(MediaSessionAction.NEXT_TRACK);
+            if (shouldShowPreviousNextTrack) {
+                mPreviousTrack.setEnabled(
+                        mVisibleActions.contains(MediaSessionAction.PREVIOUS_TRACK));
+                actions.add(mPreviousTrack);
             }
-            playPauseAction.setEnabled(mVisibleActions.contains(MediaSessionAction.PLAY));
-            actions.add(playPauseAction);
 
-            mNextTrack.setEnabled(mVisibleActions.contains(MediaSessionAction.NEXT_TRACK));
-            actions.add(mNextTrack);
+            if (mVisibleActions.contains(MediaSessionAction.PLAY)) {
+                switch (mPlaybackState) {
+                    case PlaybackState.PLAYING:
+                        actions.add(mPause);
+                        break;
+                    case PlaybackState.PAUSED:
+                        actions.add(mPlay);
+                        break;
+                    case PlaybackState.END_OF_VIDEO:
+                        actions.add(mReplay);
+                        break;
+                }
+            }
+
+            if (shouldShowPreviousNextTrack) {
+                mNextTrack.setEnabled(mVisibleActions.contains(MediaSessionAction.NEXT_TRACK));
+                actions.add(mNextTrack);
+            }
+
+            if (mVisibleActions.contains(MediaSessionAction.TOGGLE_MICROPHONE)) {
+                actions.add(mMicrophone.getAction());
+            }
+
+            if (mVisibleActions.contains(MediaSessionAction.TOGGLE_CAMERA)) {
+                actions.add(mCamera.getAction());
+            }
+
+            if (mVisibleActions.contains(MediaSessionAction.HANG_UP)) {
+                actions.add(mHangUp);
+            }
+
+            // Insert a disabled dummy remote action with transparent icon if action list is empty.
+            // This is a workaround of the issue that android picture-in-picture will fallback to
+            // default MediaSession when action list given is empty.
+            // TODO (jazzhsu): Remove this when android picture-in-picture can accept empty list and
+            // not fallback to default MediaSession.
+            if (actions.isEmpty()) {
+                RemoteAction dummyAction = new RemoteAction(
+                        Icon.createWithBitmap(Bitmap.createBitmap(
+                                new int[] {Color.TRANSPARENT}, 1, 1, Bitmap.Config.ARGB_8888)),
+                        "", "",
+                        PendingIntent.getBroadcast(getApplicationContext(), -1,
+                                new Intent(MEDIA_ACTION), PendingIntent.FLAG_IMMUTABLE));
+                dummyAction.setEnabled(false);
+                actions.add(dummyAction);
+            }
 
             return actions;
         }
@@ -180,8 +268,16 @@
             mPlaybackState = playbackState;
         }
 
+        private void setMicrophoneMuted(boolean muted) {
+            mMicrophone.setState(!muted);
+        }
+
+        private void setCameraOn(boolean cameraOn) {
+            mCamera.setState(cameraOn);
+        }
+
         /**
-         * Create a disabled remote action for picture-in-picture window.
+         * Create a remote action for picture-in-picture window.
          *
          * @param action {@link MediaSessionAction} that the action button is corresponding to.
          * @param iconResourceId used for getting icon associated with the id.
@@ -198,13 +294,10 @@
                     PendingIntent.getBroadcast(getApplicationContext(), action, intent,
                             PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
 
-            RemoteAction remoteAction = new RemoteAction(
+            return new RemoteAction(
                     Icon.createWithResource(getApplicationContext(), iconResourceId),
                     getApplicationContext().getResources().getText(titleResourceId), "",
                     pendingIntent);
-
-            remoteAction.setEnabled(false);
-            return remoteAction;
         }
     }
 
@@ -235,6 +328,15 @@
                 case MediaSessionAction.NEXT_TRACK:
                     PictureInPictureActivityJni.get().nextTrack(nativeOverlayWindowAndroid);
                     return;
+                case MediaSessionAction.TOGGLE_MICROPHONE:
+                    PictureInPictureActivityJni.get().toggleMicrophone(nativeOverlayWindowAndroid);
+                    return;
+                case MediaSessionAction.TOGGLE_CAMERA:
+                    PictureInPictureActivityJni.get().toggleCamera(nativeOverlayWindowAndroid);
+                    return;
+                case MediaSessionAction.HANG_UP:
+                    PictureInPictureActivityJni.get().hangUp(nativeOverlayWindowAndroid);
+                    return;
                 default:
                     return;
             }
@@ -484,14 +586,28 @@
         mAspectRatio = new Rational(width, height);
     }
 
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     @CalledByNative
-    private void setPlaybackState(@PlaybackState int playbackState) {
+    void setPlaybackState(@PlaybackState int playbackState) {
         mMediaActionsButtonsManager.updatePlaybackState(playbackState);
         updatePictureInPictureParams();
     }
 
     @CalledByNative
-    private void updateVisibleActions(int[] actions) {
+    private void setMicrophoneMuted(boolean muted) {
+        mMediaActionsButtonsManager.setMicrophoneMuted(muted);
+        updatePictureInPictureParams();
+    }
+
+    @CalledByNative
+    private void setCameraState(boolean turnedOn) {
+        mMediaActionsButtonsManager.setCameraOn(turnedOn);
+        updatePictureInPictureParams();
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @CalledByNative
+    void updateVisibleActions(int[] actions) {
         HashSet<Integer> visibleActions = new HashSet<>();
         for (int action : actions) visibleActions.add(action);
         mMediaActionsButtonsManager.updateVisibleActions(visibleActions);
@@ -604,6 +720,9 @@
         void togglePlayPause(long nativeOverlayWindowAndroid);
         void nextTrack(long nativeOverlayWindowAndroid);
         void previousTrack(long nativeOverlayWindowAndroid);
+        void toggleMicrophone(long nativeOverlayWindowAndroid);
+        void toggleCamera(long nativeOverlayWindowAndroid);
+        void hangUp(long nativeOverlayWindowAndroid);
 
         void compositorViewCreated(long nativeOverlayWindowAndroid, CompositorView compositorView);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
index 4e36bd0..4a7889a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
@@ -13,6 +13,7 @@
 import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
 
 import android.app.Activity;
+import android.app.RemoteAction;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -48,9 +49,12 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ActivityTestUtils;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.overlay_window.PlaybackState;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
+import org.chromium.media_session.mojom.MediaSessionAction;
 
+import java.util.ArrayList;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
 
@@ -186,6 +190,71 @@
         testExitOn(activity, () -> activity.close());
     }
 
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.O)
+    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    public void testMediaActions() throws Throwable {
+        PictureInPictureActivity activity = startPictureInPictureActivity();
+        PictureInPictureActivity.MediaActionButtonsManager manager =
+                activity.mMediaActionsButtonsManager;
+
+        activity.updateVisibleActions(new int[] {MediaSessionAction.PLAY});
+        activity.setPlaybackState(PlaybackState.PAUSED);
+        ArrayList<RemoteAction> actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 1);
+        Assert.assertEquals(actions.get(0), manager.mPlay);
+
+        activity.setPlaybackState(PlaybackState.PLAYING);
+        actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.get(0), manager.mPause);
+
+        // Both next track and previous track button should be visible when only one of them is
+        // enabled. The one that is not handled should be visible and disabled.
+        activity.updateVisibleActions(
+                new int[] {MediaSessionAction.PLAY, MediaSessionAction.PREVIOUS_TRACK});
+        actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 3);
+        Assert.assertEquals(actions.get(0), manager.mPreviousTrack);
+        Assert.assertEquals(actions.get(2), manager.mNextTrack);
+        Assert.assertTrue(actions.get(0).isEnabled());
+        Assert.assertFalse(actions.get(2).isEnabled());
+
+        // When all actions are not handled, there should be a dummy action presented to prevent
+        // android picture-in-picture from using default MediaSession.
+        activity.updateVisibleActions(new int[] {});
+        actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 1);
+        Assert.assertFalse(actions.get(0).isEnabled());
+        testExitOn(activity, () -> activity.close());
+    }
+
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.O)
+    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    public void testMediaActionsForVideoConferencing() throws Throwable {
+        PictureInPictureActivity activity = startPictureInPictureActivity();
+        PictureInPictureActivity.MediaActionButtonsManager manager =
+                activity.mMediaActionsButtonsManager;
+
+        activity.updateVisibleActions(new int[] {MediaSessionAction.TOGGLE_MICROPHONE});
+        ArrayList<RemoteAction> actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 1);
+        Assert.assertEquals(actions.get(0), manager.mMicrophone.getAction());
+
+        activity.updateVisibleActions(new int[] {MediaSessionAction.TOGGLE_CAMERA});
+        actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 1);
+        Assert.assertEquals(actions.get(0), manager.mCamera.getAction());
+
+        activity.updateVisibleActions(new int[] {MediaSessionAction.HANG_UP});
+        actions = manager.getActionsForPictureInPictureParams();
+        Assert.assertEquals(actions.size(), 1);
+        Assert.assertEquals(actions.get(0), manager.mHangUp);
+        testExitOn(activity, () -> activity.close());
+    }
+
     private WebContents getWebContents() {
         return mActivityTestRule.getActivity().getCurrentWebContents();
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java
index 24a88bb..798936b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java
@@ -53,7 +53,7 @@
 @RunWith(BaseJUnit4ClassRunner.class)
 @Batch(Batch.UNIT_TESTS)
 @Features.DisableFeatures({ChromeFeatureList.OMNIBOX_UPDATED_CONNECTION_SECURITY_INDICATORS,
-        ChromeFeatureList.LOCATION_BAR_MODEL_OPTIMIZATIONS})
+        ChromeFeatureList.ANDROID_SCROLL_OPTIMIZATIONS})
 public final class ToolbarSecurityIconTest {
     private static final boolean IS_SMALL_DEVICE = true;
     private static final boolean IS_OFFLINE_PAGE = true;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingCheckerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingCheckerUnitTest.java
index 020336e..0f15a42 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingCheckerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingCheckerUnitTest.java
@@ -118,8 +118,8 @@
 
         mainLooper().idle();
         long showBrandingTime = SystemClock.elapsedRealtime();
-        assertEquals("Branding is checked for new package, BrandingDecision should be TOOLBAR. ",
-                BrandingDecision.TOOLBAR, callbackDelegate.getBrandingDecision());
+        assertEquals("Branding is checked for new package, BrandingDecision should be TOAST. ",
+                BrandingDecision.TOAST, callbackDelegate.getBrandingDecision());
         assertEquals("Show branding time is different.", showBrandingTime,
                 mStorage.get(NEW_APPLICATION));
     }
@@ -141,7 +141,7 @@
         mainLooper().idle();
         long showBrandingTime = SystemClock.elapsedRealtime();
         assertEquals("Branding check canceled, BrandingDecision should be the test default. ",
-                BrandingDecision.TOOLBAR, callbackDelegate.getBrandingDecision());
+                BrandingDecision.TOAST, callbackDelegate.getBrandingDecision());
         assertEquals("Show branding time is different.", showBrandingTime, mStorage.get(PACKAGE_1));
     }
 
@@ -153,7 +153,7 @@
 
         mainLooper().idle();
         assertEquals("Package is invalid, BrandingDecision should be the test default. ",
-                BrandingDecision.TOOLBAR, callbackDelegate.getBrandingDecision());
+                BrandingDecision.TOAST, callbackDelegate.getBrandingDecision());
         assertEquals("Branding time should not record for invalid package.", -1,
                 mStorage.get(INVALID_PACKAGE));
     }
@@ -190,7 +190,7 @@
     private BrandingChecker createBrandingChecker(
             String packageName, CallbackDelegate callbackDelegate) {
         return new BrandingChecker(mContext, packageName, mStorage, callbackDelegate::notifyCalled,
-                TEST_BRANDING_CADENCE, BrandingDecision.TOOLBAR);
+                TEST_BRANDING_CADENCE, BrandingDecision.TOAST);
     }
 
     private ShadowLooper mainLooper() {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingControllerUnitTest.java
index 688cd66..27b8937 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingControllerUnitTest.java
@@ -28,6 +28,7 @@
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.LooperMode.Mode;
 import org.robolectric.shadows.ShadowSystemClock;
+import org.robolectric.shadows.ShadowToast;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.FakeTimeTestRule;
@@ -42,7 +43,8 @@
  * Unit test for {@link BrandingController} and {@link SharedPreferencesBrandingTimeStorage}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, shadows = {ShadowSystemClock.class, ShadowPostTask.class})
+@Config(manifest = Config.NONE,
+        shadows = {ShadowSystemClock.class, ShadowPostTask.class, ShadowToast.class})
 @LooperMode(Mode.PAUSED)
 public class BrandingControllerUnitTest {
     private static final int TEST_BRANDING_CADENCE = 10_000;
@@ -84,6 +86,7 @@
         SharedPreferencesBrandingTimeStorage.getInstance().resetForTesting();
         ShadowPostTask.reset();
         ShadowSystemClock.reset();
+        ShadowToast.reset();
     }
 
     @Test
@@ -92,14 +95,11 @@
                 .newBrandingController()
                 .assertBrandingDecisionMade(null)
                 .idleMainLooper() // Finish branding checker
-                .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
+                .assertBrandingDecisionMade(BrandingDecision.TOAST)
                 .assertShownEmptyLocationBar(false)
                 .onToolbarInitialized()
                 .assertShownEmptyLocationBar(true)
-                .assertShownBrandingLocationBar(true)
-                .assertShownRegularLocationBar(false)
-                .advanceMills(BrandingController.TOTAL_BRANDING_DELAY_MS)
-                .idleMainLooper() // Finish toolbar branding
+                .assertShownBrandingLocationBar(false)
                 .assertShownRegularLocationBar(true);
     }
 
@@ -109,30 +109,28 @@
                 .newBrandingController()
                 .idleMainLooper() // Finish branding checker.
                 .onToolbarInitialized()
-                .advanceMills(BrandingController.TOTAL_BRANDING_DELAY_MS)
-                .idleMainLooper() // Finish toolbar branding.
-                .assertShownBrandingLocationBar(true)
+                .assertShownToastBranding(true)
+                .assertShownBrandingLocationBar(false)
                 .assertShownRegularLocationBar(true)
                 // Start 2nd branding immediately.
                 .newBrandingController()
                 .idleMainLooper()
                 .assertBrandingDecisionMade(BrandingDecision.NONE)
                 .onToolbarInitialized()
+                .assertShownToastBranding(false)
                 .assertShownEmptyLocationBar(true)
                 .assertShownBrandingLocationBar(false)
                 .assertShownRegularLocationBar(true);
     }
 
     @Test
-    public void testBrandingWorkflow_ShowBrandingPassingCadence() {
+    public void testBrandingWorkflow_ShowToolbarBranding() {
         new BrandingCheckTester()
                 .newBrandingController()
                 .idleMainLooper() // Finish branding checker.
-                .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
+                .assertBrandingDecisionMade(BrandingDecision.TOAST)
                 .onToolbarInitialized()
-                .advanceMills(BrandingController.TOTAL_BRANDING_DELAY_MS)
-                .idleMainLooper() // Finish toolbar branding.
-                .assertShownBrandingLocationBar(true)
+                .assertShownToastBranding(true)
                 .assertShownRegularLocationBar(true)
                 // Start 2nd branding with delay.
                 .advanceMills(TEST_BRANDING_CADENCE + 1)
@@ -140,7 +138,11 @@
                 .idleMainLooper() // Finish branding checker.
                 .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
                 .onToolbarInitialized()
-                .assertShownBrandingLocationBar(true);
+                .assertShownBrandingLocationBar(true)
+                .assertShownToastBranding(false)
+                .advanceMills(BrandingController.TOTAL_BRANDING_DELAY_MS + 1)
+                .idleMainLooper() // Finish toolbar branding
+                .assertShownRegularLocationBar(true);
     }
 
     @Test
@@ -153,11 +155,8 @@
                 .assertShownBrandingLocationBar(false)
                 .advanceMills(300)
                 .idleMainLooper() // Finish branding checker.
-                .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
-                .assertShownBrandingLocationBar(true)
-                .assertShownRegularLocationBar(false)
-                .advanceMills(1501) // BrandingController.TOTAL_BRANDING_DELAY_MS = 1800 - 300 + 1
-                .idleMainLooper()
+                .assertBrandingDecisionMade(BrandingDecision.TOAST)
+                .assertShownToastBranding(true)
                 .assertShownRegularLocationBar(true);
     }
 
@@ -167,18 +166,18 @@
                 .newBrandingController()
                 .onToolbarInitialized()
                 .idleMainLooper()
-                .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
+                .assertBrandingDecisionMade(BrandingDecision.TOAST)
+                .assertShownToastBranding(true)
                 .newBrandingController()
                 .onToolbarInitialized()
                 .advanceMills(TEST_MAX_TOOLBAR_BLANK_TIMEOUT)
                 .idleMainLooper() // Branding checker is finished, but timed out comes first.
-                .assertBrandingDecisionMade(BrandingDecision.TOOLBAR)
-                .assertShownBrandingLocationBar(true)
-                .assertShownRegularLocationBar(false)
-                .advanceMills(801) // BrandingController.TOTAL_BRANDING_DELAY_MS -
-                                   // TEST_MAX_TOOLBAR_BLANK_TIMEOUT + 1 = 801
-                .idleMainLooper()
+                .assertBrandingDecisionMade(BrandingDecision.TOAST)
                 .assertShownRegularLocationBar(true);
+
+        // BrandingController.TOTAL_BRANDING_DELAY_MS - TEST_MAX_TOOLBAR_BLANK_TIMEOUT = 800
+        assertEquals(
+                "Toast duration is different.", 800, ShadowToast.getLatestToast().getDuration());
     }
 
     class BrandingCheckTester {
@@ -188,6 +187,7 @@
 
             // Always initialize a new mock, as some tests were testing multiple branding runs.
             mToolbarBrandingDelegate = mock(ToolbarBrandingDelegate.class);
+            ShadowToast.reset(); // Reset the shadow toast so the toast shown count resets.
             return this;
         }
 
@@ -212,6 +212,12 @@
             return this;
         }
 
+        public BrandingCheckTester assertShownToastBranding(boolean shown) {
+            assertEquals("Toast shown count does not match.", shown ? 1 : 0,
+                    ShadowToast.shownToastCount());
+            return this;
+        }
+
         public BrandingCheckTester onToolbarInitialized() {
             mBrandingController.onToolbarInitialized(mToolbarBrandingDelegate);
             return this;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
index d02125f..38b68f5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
@@ -65,7 +65,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowGURL.class, ShadowTrustedCdn.class})
-@DisableFeatures({ChromeFeatureList.LOCATION_BAR_MODEL_OPTIMIZATIONS})
+@DisableFeatures({ChromeFeatureList.ANDROID_SCROLL_OPTIMIZATIONS})
 @SuppressWarnings("DoNotMock") // Mocks GURL
 public class LocationBarModelUnitTest {
     @Implements(TrustedCdn.class)
@@ -299,7 +299,7 @@
         verify(mLocationBarDataObserver).onSecurityStateChanged();
     }
 
-    @EnableFeatures({ChromeFeatureList.LOCATION_BAR_MODEL_OPTIMIZATIONS})
+    @EnableFeatures({ChromeFeatureList.ANDROID_SCROLL_OPTIMIZATIONS})
     @Test
     @MediumTest
     public void testSpannableCache() {
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3679db7..f7fd2499 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1227,6 +1227,8 @@
     "policy/value_provider/value_provider_util.h",
     "policy/webusb_allow_devices_for_urls_policy_handler.cc",
     "policy/webusb_allow_devices_for_urls_policy_handler.h",
+    "power_bookmarks/power_bookmark_service_factory.cc",
+    "power_bookmarks/power_bookmark_service_factory.h",
     "predictors/autocomplete_action_predictor.cc",
     "predictors/autocomplete_action_predictor.h",
     "predictors/autocomplete_action_predictor_factory.cc",
@@ -2197,6 +2199,7 @@
     "//components/policy/content/",
     "//components/policy/core/browser",
     "//components/policy/proto",
+    "//components/power_bookmarks/core",
     "//components/pref_registry",
     "//components/prefs",
     "//components/privacy_sandbox",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 6f51568..0d9d09b 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -123,6 +123,8 @@
   "+components/desks_storage",
   "+components/dbus",
   "+components/device_event_log",
+  "+components/device_signals/core/browser",
+  "+components/device_signals/core/common",
   "+components/digital_asset_links",
   "+components/digital_goods",
   "+components/domain_reliability",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c227b37e..28dfe74 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4861,7 +4861,7 @@
     {"enable-cros-virtual-keyboard-multitouch",
      flag_descriptions::kVirtualKeyboardMultitouchName,
      flag_descriptions::kVirtualKeyboardMultitouchDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kVirtualKeyboardMultitouch)},
+     FEATURE_VALUE_TYPE(chromeos::features::kVirtualKeyboardMultitouch)},
     {"enable-cros-virtual-keyboard-round-corners",
      flag_descriptions::kVirtualKeyboardRoundCornersName,
      flag_descriptions::kVirtualKeyboardRoundCornersDescription, kOsCrOS,
@@ -6790,11 +6790,6 @@
                                     kLensCameraAssistedSearchVariations,
                                     "LensCameraAssistedSearch")},
 
-    {"location-bar-model-optimizations",
-     flag_descriptions::kLocationBarModelOptimizationsName,
-     flag_descriptions::kLocationBarModelOptimizationsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kLocationBarModelOptimizations)},
-
     {"enable-iph", flag_descriptions::kEnableIphName,
      flag_descriptions::kEnableIphDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(feature_engagement::kEnableIPH)},
diff --git a/chrome/browser/accessibility/soda_installer_impl.cc b/chrome/browser/accessibility/soda_installer_impl.cc
index 02b6ed4..d633efa 100644
--- a/chrome/browser/accessibility/soda_installer_impl.cc
+++ b/chrome/browser/accessibility/soda_installer_impl.cc
@@ -157,7 +157,8 @@
         base::UmaHistogramBoolean(kSodaBinaryInstallationResult, false);
       }
 
-      NotifyOnSodaError(language_code);
+      NotifyOnSodaInstallError(
+          language_code, speech::SodaInstaller::ErrorCode::kUnspecifiedError);
       break;
     case Events::COMPONENT_CHECKING_FOR_UPDATES:
     case Events::COMPONENT_UPDATED:
diff --git a/chrome/browser/android/compositor/OWNERS b/chrome/browser/android/compositor/OWNERS
index 57fa9d4..7accf24 100644
--- a/chrome/browser/android/compositor/OWNERS
+++ b/chrome/browser/android/compositor/OWNERS
@@ -1,6 +1,7 @@
 dtrainor@chromium.org
 mdjones@chromium.org
 
+per-file *tab_strip*=file://chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/OWNERS
 per-file *contextual_search*=gangwu@chromium.org
 
 # Secondary
diff --git a/chrome/browser/android/contextualsearch/contextual_search_manager.cc b/chrome/browser/android/contextualsearch/contextual_search_manager.cc
index 8197d9d..1431402 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_manager.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_manager.cc
@@ -43,13 +43,7 @@
   Profile* profile = ProfileManager::GetActiveUserProfile();
   delegate_ = std::make_unique<ContextualSearchDelegate>(
       profile->GetURLLoaderFactory(),
-      TemplateURLServiceFactory::GetForProfile(profile),
-      base::BindRepeating(
-          &ContextualSearchManager::OnSearchTermResolutionResponse,
-          base::Unretained(this)),
-      base::BindRepeating(
-          &ContextualSearchManager::OnTextSurroundingSelectionAvailable,
-          base::Unretained(this)));
+      TemplateURLServiceFactory::GetForProfile(profile));
 }
 
 ContextualSearchManager::~ContextualSearchManager() {
@@ -74,8 +68,11 @@
       NativeContextualSearchContext::FromJavaContextualSearchContext(
           j_contextual_search_context);
   // Calls back to OnSearchTermResolutionResponse.
-  delegate_->StartSearchTermResolutionRequest(contextual_search_context,
-                                              base_web_contents);
+  delegate_->StartSearchTermResolutionRequest(
+      contextual_search_context, base_web_contents,
+      base::BindRepeating(
+          &ContextualSearchManager::OnSearchTermResolutionResponse,
+          base::Unretained(this)));
 }
 
 void ContextualSearchManager::GatherSurroundingText(
@@ -89,8 +86,11 @@
   base::WeakPtr<NativeContextualSearchContext> contextual_search_context =
       NativeContextualSearchContext::FromJavaContextualSearchContext(
           j_contextual_search_context);
-  delegate_->GatherAndSaveSurroundingText(contextual_search_context,
-                                          base_web_contents);
+  delegate_->GatherAndSaveSurroundingText(
+      contextual_search_context, base_web_contents,
+      base::BindRepeating(
+          &ContextualSearchManager::OnTextSurroundingSelectionAvailable,
+          base::Unretained(this)));
 }
 
 void ContextualSearchManager::OnSearchTermResolutionResponse(
diff --git a/chrome/browser/android/vr/gvr_graphics_delegate.cc b/chrome/browser/android/vr/gvr_graphics_delegate.cc
index 2c6f08c8..5b128d8 100644
--- a/chrome/browser/android/vr/gvr_graphics_delegate.cc
+++ b/chrome/browser/android/vr/gvr_graphics_delegate.cc
@@ -27,6 +27,7 @@
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_fence_egl.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace vr {
@@ -167,22 +168,25 @@
   // TODO(crbug.com/1170580): support ANGLE with cardboard?
   gl::init::DisableANGLE();
 
-  if (gl::GetGLImplementation() == gl::kGLImplementationNone &&
-      !gl::init::InitializeGLOneOff(/*system_device_id=*/0)) {
-    LOG(ERROR) << "gl::init::InitializeGLOneOff failed";
-    browser_->ForceExitVr();
-    return;
+  gl::GLDisplay* display = nullptr;
+  if (gl::GetGLImplementation() == gl::kGLImplementationNone) {
+    display = gl::init::InitializeGLOneOff(/*system_device_id=*/0);
+    if (!display) {
+      LOG(ERROR) << "gl::init::InitializeGLOneOff failed";
+      browser_->ForceExitVr();
+      return;
+    }
+  } else {
+    display = gl::GetDefaultDisplayEGL();
   }
 
-  DCHECK(gl::GetGLImplementation() != gl::kGLImplementationEGLANGLE);
-
   scoped_refptr<gl::GLSurface> surface;
   if (window) {
     DCHECK(!surfaceless_rendering_);
-    surface = gl::init::CreateViewGLSurface(window);
+    surface = gl::init::CreateViewGLSurface(display, window);
   } else {
     DCHECK(surfaceless_rendering_);
-    surface = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface = gl::init::CreateOffscreenGLSurface(display, gfx::Size());
   }
   if (!surface.get()) {
     LOG(ERROR) << "gl::init::CreateOffscreenGLSurface failed";
diff --git a/chrome/browser/apps/app_service/publishers/crostini_apps.cc b/chrome/browser/apps/app_service/publishers/crostini_apps.cc
index dc873f1..dcc4c95 100644
--- a/chrome/browser/apps/app_service/publishers/crostini_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/crostini_apps.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/apps/app_service/publishers/crostini_apps.h"
 
 #include <utility>
+#include <vector>
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_menu_constants.h"
@@ -20,6 +21,7 @@
 #include "chrome/browser/ash/crostini/crostini_pref_names.h"
 #include "chrome/browser/ash/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
+#include "chrome/browser/ash/file_manager/fileapi_util.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
 #include "chrome/browser/ash/guest_os/guest_os_terminal.h"
 #include "chrome/browser/profiles/profile.h"
@@ -31,6 +33,7 @@
 #include "components/services/app_service/public/cpp/intent_util.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "storage/browser/file_system/file_system_context.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/strings/grit/ui_strings.h"
@@ -71,7 +74,10 @@
   std::vector<std::string> mime_types_vector(mime_types.begin(),
                                              mime_types.end());
   apps::IntentFilterPtr intent_filter = apps_util::CreateFileFilter(
-      {apps_util::kIntentActionView}, mime_types_vector, {});
+      {apps_util::kIntentActionView}, mime_types_vector, {},
+      // TODO(crbug/1349974): Remove activity_name when default file handling
+      // preferences for Files App are migrated.
+      /*activity_name=*/apps_util::kGuestOsActivityName);
   intent_filters.push_back(std::move(intent_filter));
   return intent_filters;
 }
@@ -144,10 +150,21 @@
     LaunchSource launch_source,
     WindowInfoPtr window_info,
     base::OnceCallback<void(bool)> callback) {
+  // Retrieve URLs from the files in the intent.
+  std::vector<crostini::LaunchArg> args;
+  if (intent && intent->files.size() > 0) {
+    args.reserve(intent->files.size());
+    storage::FileSystemContext* file_system_context =
+        file_manager::util::GetFileManagerFileSystemContext(profile_);
+    for (auto& file : intent->files) {
+      args.emplace_back(
+          file_system_context->CrackURLInFirstPartyContext(file->url));
+    }
+  }
   crostini::LaunchCrostiniAppWithIntent(
       profile_, app_id,
       window_info ? window_info->display_id : display::kInvalidDisplayId,
-      std::move(intent), /*args=*/{},
+      std::move(intent), args,
       base::BindOnce(
           [](LaunchAppWithIntentCallback callback, bool success,
              const std::string& failure_reason) {
@@ -217,10 +234,21 @@
                                        apps::mojom::LaunchSource launch_source,
                                        apps::mojom::WindowInfoPtr window_info,
                                        LaunchAppWithIntentCallback callback) {
+  // Retrieve URLs from the files in the intent.
+  std::vector<crostini::LaunchArg> args;
+  if (intent && intent->files.has_value()) {
+    storage::FileSystemContext* file_system_context =
+        file_manager::util::GetFileManagerFileSystemContext(profile_);
+    args.reserve(intent->files.value().size());
+    for (auto& file : intent->files.value()) {
+      args.emplace_back(
+          file_system_context->CrackURLInFirstPartyContext(file->url));
+    }
+  }
   crostini::LaunchCrostiniAppWithIntent(
       profile_, app_id,
       window_info ? window_info->display_id : display::kInvalidDisplayId,
-      ConvertMojomIntentToIntent(intent), /*args=*/{},
+      ConvertMojomIntentToIntent(intent), args,
       base::BindOnce(
           [](LaunchAppWithIntentCallback callback, bool success,
              const std::string& failure_reason) {
diff --git a/chrome/browser/apps/app_service/publishers/crostini_apps_unittest.cc b/chrome/browser/apps/app_service/publishers/crostini_apps_unittest.cc
index cea1de60..8e6ab45 100644
--- a/chrome/browser/apps/app_service/publishers/crostini_apps_unittest.cc
+++ b/chrome/browser/apps/app_service/publishers/crostini_apps_unittest.cc
@@ -9,10 +9,14 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/crostini/crostini_test_helper.h"
+#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
 #include "components/services/app_service/public/cpp/intent_filter.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
@@ -30,6 +34,8 @@
 
   AppServiceProxy* app_service_proxy() { return app_service_proxy_; }
 
+  TestingProfile* profile() { return profile_.get(); }
+
   void SetUp() override {
     ash::CiceroneClient::InitializeFake();
     profile_ = std::make_unique<TestingProfile>();
@@ -107,4 +113,41 @@
   }
 }
 
+TEST_F(CrostiniAppsTest, AppReadinessUpdatesWhenCrostiniDisabled) {
+  // Install a Crostini app.
+  vm_tools::apps::App app;
+  app.set_desktop_file_id("app_id");
+  vm_tools::apps::App::LocaleString::Entry* entry =
+      app.mutable_name()->add_values();
+  entry->set_locale(std::string());
+  entry->set_value("app_name");
+  test_helper()->AddApp(app);
+
+  // Get the app ID so that we can find the Crostini app in App Service later.
+  std::string app_service_id = crostini::CrostiniTestHelper::GenerateAppId(
+      app.desktop_file_id(), crostini::kCrostiniDefaultVmName,
+      crostini::kCrostiniDefaultContainerName);
+
+  // Check that the app is ready.
+  apps::Readiness readiness_before;
+  app_service_proxy()->AppRegistryCache().ForOneApp(
+      app_service_id, [&readiness_before](const AppUpdate& update) {
+        readiness_before = update.Readiness();
+      });
+  ASSERT_EQ(readiness_before, Readiness::kReady);
+
+  // Disable Crostini. This call uninstalls all Crostini apps.
+  guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile())
+      ->ClearApplicationList(guest_os::VmType::TERMINA,
+                             crostini::kCrostiniDefaultVmName, "");
+
+  // Check that the app is now disabled.
+  apps::Readiness readiness_after;
+  app_service_proxy()->AppRegistryCache().ForOneApp(
+      app_service_id, [&readiness_after](const AppUpdate& update) {
+        readiness_after = update.Readiness();
+      });
+  ASSERT_EQ(readiness_after, Readiness::kUninstalledByUser);
+}
+
 }  // namespace apps
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 1f9b3e69..caaf08f 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1011,6 +1011,8 @@
     "file_manager/speedometer.h",
     "file_manager/trash_common_util.cc",
     "file_manager/trash_common_util.h",
+    "file_manager/trash_info_validator.cc",
+    "file_manager/trash_info_validator.h",
     "file_manager/trash_io_task.cc",
     "file_manager/trash_io_task.h",
     "file_manager/url_util.cc",
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index 3315703..1828dd3 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -2114,7 +2114,9 @@
   OnSodaInstallUpdated(100);
 }
 
-void AccessibilityManager::OnSodaError(speech::LanguageCode language_code) {
+void AccessibilityManager::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   if (language_code != speech::LanguageCode::kNone &&
       language_code != GetDictationLanguageCode()) {
     return;
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.h b/chrome/browser/ash/accessibility/accessibility_manager.h
index a5f7419..c27a24e 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.h
+++ b/chrome/browser/ash/accessibility/accessibility_manager.h
@@ -365,7 +365,8 @@
 
   // SodaInstaller::Observer:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override;
 
diff --git a/chrome/browser/ash/arc/intent_helper/arc_settings_service.cc b/chrome/browser/ash/arc/intent_helper/arc_settings_service.cc
index 0b5162b..9c725fc 100644
--- a/chrome/browser/ash/arc/intent_helper/arc_settings_service.cc
+++ b/chrome/browser/ash/arc/intent_helper/arc_settings_service.cc
@@ -254,7 +254,7 @@
   // automatically unregisters a callback when it's destructed.
   base::CallbackListSubscription default_zoom_level_subscription_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
@@ -434,7 +434,7 @@
   TimezoneSettings::GetInstance()->AddObserver(this);
 
   network_state_handler_observer_.Observe(
-      chromeos::NetworkHandler::Get()->network_state_handler());
+      ash::NetworkHandler::Get()->network_state_handler());
 }
 
 void ArcSettingsServiceImpl::StopObservingSettingsChanges() {
diff --git a/chrome/browser/ash/arc/intent_helper/arc_settings_service_browsertest.cc b/chrome/browser/ash/arc/intent_helper/arc_settings_service_browsertest.cc
index ff305f9..7a5a31d 100644
--- a/chrome/browser/ash/arc/intent_helper/arc_settings_service_browsertest.cc
+++ b/chrome/browser/ash/arc/intent_helper/arc_settings_service_browsertest.cc
@@ -302,9 +302,9 @@
   void SetProxyConfigForNetworkService(const std::string& service_path,
                                        base::Value proxy_config) {
     ProxyConfigDictionary proxy_config_dict(std::move(proxy_config));
-    const ash::NetworkState* network = chromeos::NetworkHandler::Get()
-                                           ->network_state_handler()
-                                           ->GetNetworkState(service_path);
+    const ash::NetworkState* network =
+        ash::NetworkHandler::Get()->network_state_handler()->GetNetworkState(
+            service_path);
     ASSERT_TRUE(network);
     ash::proxy_config::SetProxyConfigForNetwork(proxy_config_dict, *network);
   }
@@ -603,9 +603,8 @@
   proxy_config.SetKey("mode",
                       base::Value(ProxyPrefs::kAutoDetectProxyModeName));
   ProxyConfigDictionary proxy_config_dict(std::move(proxy_config));
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
-                                         ->network_state_handler()
-                                         ->DefaultNetwork();
+  const ash::NetworkState* network =
+      ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
   ASSERT_TRUE(network);
   ash::proxy_config::SetProxyConfigForNetwork(proxy_config_dict, *network);
   RunUntilIdle();
diff --git a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h b/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h
index fff36e92..157a124 100644
--- a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h
+++ b/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h
@@ -114,8 +114,7 @@
   bool is_observing_network_ = false;
   KerberosFilesHandler kerberos_files_handler_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   // Stores message ids of shown notifications. Each notification is shown at
diff --git a/chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h b/chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h
index f879376..e82abb9 100644
--- a/chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h
+++ b/chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h
@@ -20,8 +20,6 @@
 #include "chrome/browser/ash/cert_provisioning/cert_provisioning_platform_keys_helpers.h"
 #include "chrome/browser/ash/platform_keys/platform_keys_service.h"
 #include "chrome/browser/platform_keys/platform_keys.h"
-// TODO(https://crbug.com/1164001): forward declare NetworkStateHandler
-// after //chromeos/network is moved to ash.
 #include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -225,8 +223,7 @@
   // |platform_keys_service_| can be nullptr if it has been shut down.
   platform_keys::PlatformKeysService* platform_keys_service_ = nullptr;
   NetworkStateHandler* network_state_handler_ = nullptr;
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   PrefChangeRegistrar pref_change_registrar_;
diff --git a/chrome/browser/ash/crosapi/extension_info_private_ash.cc b/chrome/browser/ash/crosapi/extension_info_private_ash.cc
index f28a17c..fab7cc4 100644
--- a/chrome/browser/ash/crosapi/extension_info_private_ash.cc
+++ b/chrome/browser/ash/crosapi/extension_info_private_ash.cc
@@ -38,7 +38,7 @@
 #include "extensions/common/error_utils.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
-using chromeos::NetworkHandler;
+using ash::NetworkHandler;
 
 namespace crosapi {
 
diff --git a/chrome/browser/ash/crosapi/network_settings_service_ash.cc b/chrome/browser/ash/crosapi/network_settings_service_ash.cc
index bfa4bab..bca85948 100644
--- a/chrome/browser/ash/crosapi/network_settings_service_ash.cc
+++ b/chrome/browser/ash/crosapi/network_settings_service_ash.cc
@@ -51,9 +51,9 @@
     profile_manager_->AddObserver(this);
   }
   // Uninitialized in unit_tests.
-  if (chromeos::NetworkHandler::IsInitialized()) {
+  if (ash::NetworkHandler::IsInitialized()) {
     network_state_handler_observer_.Observe(
-        chromeos::NetworkHandler::Get()->network_state_handler());
+        ash::NetworkHandler::Get()->network_state_handler());
   }
   observers_.set_disconnect_handler(base::BindRepeating(
       &NetworkSettingsServiceAsh::OnDisconnect, base::Unretained(this)));
diff --git a/chrome/browser/ash/crosapi/network_settings_service_ash.h b/chrome/browser/ash/crosapi/network_settings_service_ash.h
index ab5a529..fd288d9 100644
--- a/chrome/browser/ash/crosapi/network_settings_service_ash.h
+++ b/chrome/browser/ash/crosapi/network_settings_service_ash.h
@@ -97,7 +97,7 @@
   PrefService* local_state_;
   ProfileManager* profile_manager_ = nullptr;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/crosapi/networking_attributes_ash.cc b/chrome/browser/ash/crosapi/networking_attributes_ash.cc
index ecdd159..7c5da17a 100644
--- a/chrome/browser/ash/crosapi/networking_attributes_ash.cc
+++ b/chrome/browser/ash/crosapi/networking_attributes_ash.cc
@@ -47,8 +47,8 @@
     return;
   }
 
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   const chromeos::NetworkState* network =
       network_state_handler->DefaultNetwork();
   if (!network) {
diff --git a/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc b/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
index 12a8a0b..c00b69f 100644
--- a/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
@@ -183,9 +183,8 @@
     testing::Mock::VerifyAndClearExpectations(&observer);
 
     const ash::DeviceState* device_state =
-        chromeos::NetworkHandler::Get()
-            ->network_state_handler()
-            ->GetDeviceState(kWifiDevicePath);
+        ash::NetworkHandler::Get()->network_state_handler()->GetDeviceState(
+            kWifiDevicePath);
     EXPECT_EQ(device_state->mac_address(), kFormattedMacAddress);
     EXPECT_EQ(device_state->GetIpAddressByType(shill::kTypeIPv4), kIpv4Address);
     EXPECT_EQ(device_state->GetIpAddressByType(shill::kTypeIPv6), kIpv6Address);
diff --git a/chrome/browser/ash/crosapi/networking_private_ash.cc b/chrome/browser/ash/crosapi/networking_private_ash.cc
index e3f399e..a5d8bcb 100644
--- a/chrome/browser/ash/crosapi/networking_private_ash.cc
+++ b/chrome/browser/ash/crosapi/networking_private_ash.cc
@@ -17,9 +17,9 @@
 #include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
+using ::ash::NetworkHandler;
 using ::ash::NetworkState;
-using chromeos::NetworkHandler;
-using chromeos::NetworkStateHandler;
+using ::ash::NetworkStateHandler;
 
 namespace crosapi {
 
diff --git a/chrome/browser/ash/crosapi/networking_private_ash.h b/chrome/browser/ash/crosapi/networking_private_ash.h
index a0a51802..78cc89b 100644
--- a/chrome/browser/ash/crosapi/networking_private_ash.h
+++ b/chrome/browser/ash/crosapi/networking_private_ash.h
@@ -108,7 +108,7 @@
   // Lacros observers to be notified of relevant events.
   mojo::RemoteSet<mojom::NetworkingPrivateDelegateObserver> observers_;
   // We observe network state to forward its events to our Lacros observers.
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_observation_{this};
   base::ScopedObservation<ash::NetworkCertificateHandler,
diff --git a/chrome/browser/ash/crosapi/vpn_service_ash.cc b/chrome/browser/ash/crosapi/vpn_service_ash.cc
index de3ae59..1d38fb8 100644
--- a/chrome/browser/ash/crosapi/vpn_service_ash.cc
+++ b/chrome/browser/ash/crosapi/vpn_service_ash.cc
@@ -178,7 +178,7 @@
     const std::string& extension_id)
     : extension_id_(extension_id) {
   network_configuration_observer_.Observe(
-      chromeos::NetworkHandler::Get()->network_configuration_handler());
+      ash::NetworkHandler::Get()->network_configuration_handler());
 }
 
 VpnServiceForExtensionAsh::~VpnServiceForExtensionAsh() = default;
@@ -209,7 +209,7 @@
   // Since the API is only designed to be used with the primary profile, it's
   // safe to get the hash of the primary profile here.
   const ash::NetworkProfile* profile =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_profile_handler()
           ->GetProfileForUserhash(ash::ProfileHelper::GetUserIdHashFromProfile(
               ProfileManager::GetPrimaryUserProfile()));
@@ -232,7 +232,7 @@
   properties.Set(shill::kGuidProperty, base::GenerateGUID());
 
   auto [success, failure] = AdaptCallback(std::move(callback));
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->network_configuration_handler()
       ->CreateShillConfiguration(
           base::Value(std::move(properties)),
@@ -274,7 +274,7 @@
   DestroyConfigurationInternal(configuration);
 
   auto [success, failure] = AdaptCallback(std::move(callback));
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->network_configuration_handler()
       ->RemoveConfiguration(
           *service_path,
@@ -546,12 +546,12 @@
 
 VpnServiceAsh::VpnServiceAsh() {
   // Can be false in unit tests.
-  if (!chromeos::NetworkHandler::IsInitialized()) {
+  if (!ash::NetworkHandler::IsInitialized()) {
     return;
   }
 
   network_state_handler_observer_.Observe(
-      chromeos::NetworkHandler::Get()->network_state_handler());
+      ash::NetworkHandler::Get()->network_state_handler());
 
   vpn_providers_observer_ = std::make_unique<VpnProvidersObserver>(this);
 }
@@ -589,9 +589,9 @@
 }
 
 void VpnServiceAsh::NetworkListChanged() {
-  chromeos::NetworkStateHandler::NetworkStateList network_list;
+  ash::NetworkStateHandler::NetworkStateList network_list;
 
-  auto* network_handler = chromeos::NetworkHandler::Get();
+  auto* network_handler = ash::NetworkHandler::Get();
   network_handler->network_state_handler()->GetVisibleNetworkListByType(
       ash::NetworkTypePattern::VPN(), &network_list);
 
diff --git a/chrome/browser/ash/crosapi/vpn_service_ash.h b/chrome/browser/ash/crosapi/vpn_service_ash.h
index 91f2acb..ceaece1a 100644
--- a/chrome/browser/ash/crosapi/vpn_service_ash.h
+++ b/chrome/browser/ash/crosapi/vpn_service_ash.h
@@ -264,7 +264,7 @@
   // Ids of enabled vpn extensions.
   base::flat_set<std::string> vpn_extensions_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index 705b1ab..70f8343 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -46,11 +46,13 @@
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/guest_os_share_path.h"
 #include "chrome/browser/ash/guest_os/guest_os_stability_monitor.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_service.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_service_factory.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_wayland_server.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 #include "chrome/browser/ash/policy/handlers/powerwash_requirements_checker.h"
 #include "chrome/browser/ash/scheduler_configuration_manager.h"
 #include "chrome/browser/browser_process.h"
@@ -567,8 +569,10 @@
   // are finished. Also, a lot of unit tests don't inject a fake container so
   // it's possible in tests to end up here without a running container. Don't
   // try mounting sshfs in that case.
-  auto info = crostini_manager_->GetContainerInfo(container_id_);
-  if (container_id_ == DefaultContainerId() && info) {
+  bool running =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)->IsRunning(
+          container_id_);
+  if (container_id_ == DefaultContainerId() && running) {
     crostini_manager_->MountCrostiniFiles(container_id_, base::DoNothing(),
                                           true);
   }
@@ -1190,23 +1194,17 @@
   is_unclean_startup_ = is_unclean_startup;
 }
 
-absl::optional<ContainerInfo> CrostiniManager::GetContainerInfo(
-    const guest_os::GuestId& container_id) {
-  if (!IsVmRunning(container_id.vm_name)) {
-    return absl::nullopt;
-  }
-  auto range = running_containers_.equal_range(container_id.vm_name);
-  for (auto it = range.first; it != range.second; ++it) {
-    if (it->second.name == container_id.container_name) {
-      return it->second;
-    }
-  }
-  return absl::nullopt;
-}
-
 void CrostiniManager::AddRunningContainerForTesting(std::string vm_name,
                                                     ContainerInfo info) {
-  running_containers_.emplace(std::move(vm_name), info);
+  auto* tracker = guest_os::GuestOsSessionTracker::GetForProfile(profile_);
+  guest_os::GuestId id{guest_os::VmType::TERMINA, vm_name, info.name};
+  guest_os::GuestInfo guest_info{id,
+                                 0,
+                                 info.username,
+                                 info.homedir,
+                                 info.ipv4_address,
+                                 info.sftp_vsock_port};
+  tracker->AddGuestForTesting(id, guest_info);  // IN-TEST
 }
 
 void CrostiniManager::UpdateLaunchMetricsForEnterpriseReporting() {
@@ -1230,9 +1228,9 @@
   if (ash::AnomalyDetectorClient::Get()) {  // May be null in tests.
     ash::AnomalyDetectorClient::Get()->AddObserver(this);
   }
-  if (chromeos::NetworkHandler::IsInitialized()) {
+  if (ash::NetworkHandler::IsInitialized()) {
     network_state_handler_observer_.Observe(
-        chromeos::NetworkHandler::Get()->network_state_handler());
+        ash::NetworkHandler::Get()->network_state_handler());
   }
   if (chromeos::PowerManagerClient::Get()) {
     chromeos::PowerManagerClient::Get()->AddObserver(this);
@@ -2523,7 +2521,6 @@
     LOG(ERROR) << "Failed to start VM: " << response->failure_reason();
     // If we thought vms and containers were running before, they aren't now.
     running_vms_.erase(vm_name);
-    running_containers_.erase(vm_name);
     std::move(callback).Run(/*success=*/false);
     return;
   }
@@ -2537,7 +2534,6 @@
       VmInfo{VmState::STARTING, std::move(response->vm_info())};
   // If we thought a container was running for this VM, we're wrong. This can
   // happen if the vm was formerly running, then stopped via crosh.
-  running_containers_.erase(vm_name);
 
   if (wait_for_tremplin) {
     VLOG(1) << "Awaiting TremplinStartedSignal for " << owner_id_ << ", "
@@ -2657,7 +2653,6 @@
 
   // Remove from running_vms_, and other vm-keyed state.
   running_vms_.erase(vm_name);
-  running_containers_.erase(vm_name);
   InvokeAndErasePendingCallbacks(
       &export_lxd_container_callbacks_, vm_name,
       CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED, 0, 0);
@@ -2702,11 +2697,6 @@
   }
 
   VLOG(1) << "Container " << signal.container_name() << " started";
-  running_containers_.emplace(
-      signal.vm_name(),
-      ContainerInfo(signal.container_name(), signal.container_username(),
-                    signal.container_homedir(), signal.ipv4_address(),
-                    signal.sftp_vsock_port()));
   InvokeAndErasePendingContainerCallbacks(
       &start_container_callbacks_, container_id, CrostiniResult::SUCCESS);
 
@@ -2757,7 +2747,7 @@
   }
   shutdown_container_callbacks_.erase(range_callbacks.first,
                                       range_callbacks.second);
-  RemoveStoppedContainer(container_id);
+  HandleContainerShutdown(container_id);
 }
 
 void CrostiniManager::OnInstallLinuxPackageProgress(
@@ -3039,7 +3029,7 @@
       break;
 
     case vm_tools::cicerone::StopLxdContainerResponse::STOPPED:
-      RemoveStoppedContainer(container_id);
+      HandleContainerShutdown(container_id);
       std::move(callback).Run(CrostiniResult::SUCCESS);
       break;
 
@@ -3266,8 +3256,10 @@
       (version != ContainerOsVersion::kOtherOs &&
        version != ContainerOsVersion::kUnknown);
 
-  if (result == CrostiniResult::SUCCESS && !GetContainerInfo(container_id) &&
-      is_garcon_required) {
+  bool running =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)->IsRunning(
+          container_id);
+  if (result == CrostiniResult::SUCCESS && !running && is_garcon_required) {
     VLOG(1) << "Awaiting ContainerStarted signal from Garcon, did not yet have "
                "information for container "
             << container_id.container_name;
@@ -3297,7 +3289,7 @@
       result = CrostiniResult::CONTAINER_STOP_CANCELLED;
       break;
     case vm_tools::cicerone::LxdContainerStoppingSignal::STOPPED:
-      RemoveStoppedContainer(container_id);
+      HandleContainerShutdown(container_id);
       result = CrostiniResult::SUCCESS;
       break;
     case vm_tools::cicerone::LxdContainerStoppingSignal::STOPPING:
@@ -3782,8 +3774,8 @@
 // TODO(danielng): Consider handling instant tethering.
 void CrostiniManager::ActiveNetworksChanged(
     const std::vector<const ash::NetworkState*>& active_networks) {
-  chromeos::NetworkStateHandler::NetworkStateList active_physical_networks;
-  chromeos::NetworkHandler::Get()
+  ash::NetworkStateHandler::NetworkStateList active_physical_networks;
+  ash::NetworkHandler::Get()
       ->network_state_handler()
       ->GetActiveNetworkListByType(ash::NetworkTypePattern::Physical(),
                                    &active_physical_networks);
@@ -3793,7 +3785,7 @@
   if (!network)
     return;
   const ash::DeviceState* device =
-      chromeos::NetworkHandler::Get()->network_state_handler()->GetDeviceState(
+      ash::NetworkHandler::Get()->network_state_handler()->GetDeviceState(
           network->device_path());
   if (!device)
     return;
@@ -3809,7 +3801,6 @@
 
 void CrostiniManager::SuspendImminent(
     power_manager::SuspendImminent::Reason reason) {
-  auto info = GetContainerInfo(DefaultContainerId());
   if (!crostini_sshfs_->IsSshfsMounted(DefaultContainerId())) {
     return;
   }
@@ -3827,7 +3818,10 @@
   // https://crbug.com/968060.  Sshfs is unmounted before suspend,
   // call RestartCrostini to force remount if container is running.
   guest_os::GuestId container_id = DefaultContainerId();
-  if (GetContainerInfo(container_id)) {
+  bool running =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)->IsRunning(
+          container_id);
+  if (running) {
     // TODO(crbug/1142321): Double-check if anything breaks if we change this
     // to just remount the sshfs mounts, in particular check 9p mounts.
     RestartCrostini(container_id, base::DoNothing());
@@ -3956,21 +3950,13 @@
   restarter_it->second->StartLxdContainerFinished(result);
 }
 
-void CrostiniManager::RemoveStoppedContainer(
+void CrostiniManager::HandleContainerShutdown(
     const guest_os::GuestId& container_id) {
   // Run all ContainerShutdown observers
   for (auto& observer : container_shutdown_observers_) {
     observer.OnContainerShutdown(container_id);
   }
-  // Remove from running containers multimap.
-  auto range_containers = running_containers_.equal_range(container_id.vm_name);
-  for (auto it = range_containers.first; it != range_containers.second; ++it) {
-    if (it->second.name == container_id.container_name) {
-      running_containers_.erase(it);
-      break;
-    }
-  }
-  if (running_containers_.empty()) {
+  if (!IsVmRunning(kCrostiniDefaultVmName)) {
     auto* engagement_metrics_service =
         CrostiniEngagementMetricsService::Factory::GetForProfile(profile_);
     // This is null in unit tests.
diff --git a/chrome/browser/ash/crostini/crostini_manager.h b/chrome/browser/ash/crostini/crostini_manager.h
index 4a5d9697..2e588cb 100644
--- a/chrome/browser/ash/crostini/crostini_manager.h
+++ b/chrome/browser/ash/crostini/crostini_manager.h
@@ -565,9 +565,6 @@
                              const vm_tools::cicerone::OsRelease& os_release);
   const vm_tools::cicerone::OsRelease* GetContainerOsRelease(
       const guest_os::GuestId& container_id) const;
-  // Returns null if VM or container is not running.
-  absl::optional<ContainerInfo> GetContainerInfo(
-      const guest_os::GuestId& container_id);
   void AddRunningContainerForTesting(std::string vm_name, ContainerInfo info);
 
   // If the Crostini reporting policy is set, save the last app launch
@@ -808,8 +805,9 @@
   // metric logging the type. Mostly happens async and best-effort.
   void EmitVmDiskTypeMetric(const std::string vm_name);
 
-  // Removes specified container id from running_containers list.
-  void RemoveStoppedContainer(const guest_os::GuestId& container_id);
+  // Runs things that should happened whenever a container shutdowns e.g.
+  // triggering observers.
+  void HandleContainerShutdown(const guest_os::GuestId& container_id);
 
   // Registers a container with GuestOsService's registries. No-op if it's
   // already registered.
@@ -860,9 +858,6 @@
 
   std::map<std::string, VmInfo> running_vms_;
 
-  // Running containers as keyed by vm name.
-  std::multimap<std::string, ContainerInfo> running_containers_;
-
   // OsRelease protos keyed by guest_os::GuestId. We populate this map even if a
   // container fails to start normally.
   std::map<guest_os::GuestId, vm_tools::cicerone::OsRelease>
@@ -930,7 +925,7 @@
                  guest_os::GuestOsTerminalProviderRegistry::Id>
       terminal_provider_ids_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/crostini/crostini_manager_unittest.cc b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
index a29b5ac..3502bd7 100644
--- a/chrome/browser/ash/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/crostini/fake_crostini_features.h"
 #include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_service.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_wayland_server.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
@@ -889,11 +890,10 @@
   EXPECT_GE(fake_concierge_client_->create_disk_image_call_count(), 1);
   EXPECT_GE(fake_concierge_client_->start_vm_call_count(), 1);
   EXPECT_EQ(1, restart_crostini_callback_count_);
-
-  absl::optional<ContainerInfo> container_info =
-      crostini_manager()->GetContainerInfo(container_id());
-  EXPECT_EQ(container_info.value().username,
+  auto req = fake_cicerone_client_->get_setup_lxd_container_user_request();
+  EXPECT_EQ(req.container_username(),
             DefaultContainerUserNameForProfile(profile()));
+
   ExpectRestarterUmaCount(1);
   histogram_tester_.ExpectTotalCount("Crostini.RestarterTimeInState2.Start", 1);
   histogram_tester_.ExpectTotalCount(
@@ -929,10 +929,6 @@
   EXPECT_GE(fake_concierge_client_->start_vm_call_count(), 1);
   EXPECT_EQ(1, restart_crostini_callback_count_);
 
-  absl::optional<ContainerInfo> container_info =
-      crostini_manager()->GetContainerInfo(container_id());
-  EXPECT_EQ(container_info.value().username,
-            DefaultContainerUserNameForProfile(profile()));
   histogram_tester_.ExpectTotalCount("Crostini.Restarter.Started", 1);
   histogram_tester_.ExpectTotalCount("Crostini.RestarterResult", 1);
   histogram_tester_.ExpectTotalCount("Crostini.Installer.Started", 0);
@@ -958,10 +954,6 @@
   EXPECT_GE(fake_concierge_client_->start_vm_call_count(), 1);
   EXPECT_EQ(1, restart_crostini_callback_count_);
 
-  absl::optional<ContainerInfo> container_info =
-      crostini_manager()->GetContainerInfo(container_id());
-  EXPECT_EQ(container_info.value().username,
-            DefaultContainerUserNameForProfile(profile()));
   ExpectRestarterUmaCount(1);
 }
 
@@ -977,10 +969,8 @@
   EXPECT_GE(fake_concierge_client_->create_disk_image_call_count(), 1);
   EXPECT_GE(fake_concierge_client_->start_vm_call_count(), 1);
   EXPECT_EQ(1, restart_crostini_callback_count_);
-
-  absl::optional<ContainerInfo> container_info =
-      crostini_manager()->GetContainerInfo(container_id());
-  EXPECT_EQ(container_info.value().username, "helloworld");
+  auto req = fake_cicerone_client_->get_setup_lxd_container_user_request();
+  EXPECT_EQ(req.container_username(), "helloworld");
   ExpectRestarterUmaCount(1);
 }
 
@@ -1521,7 +1511,6 @@
   EXPECT_EQ(1, restart_crostini_callback_count_);
 
   EXPECT_TRUE(crostini_manager()->IsVmRunning(kVmName));
-  EXPECT_TRUE(crostini_manager()->GetContainerInfo(container_id()));
 
   // Now call StartTerminaVm again. The default response state is "STARTING",
   // so no container should be considered running.
@@ -1534,7 +1523,6 @@
   run_loop2.Run();
   EXPECT_GE(fake_concierge_client_->start_vm_call_count(), 1);
   EXPECT_TRUE(crostini_manager()->IsVmRunning(kVmName));
-  EXPECT_FALSE(crostini_manager()->GetContainerInfo(container_id()));
   ExpectRestarterUmaCount(1);
 }
 
diff --git a/chrome/browser/ash/crostini/crostini_mount_provider.cc b/chrome/browser/ash/crostini/crostini_mount_provider.cc
index 44915b3..bfa70e3 100644
--- a/chrome/browser/ash/crostini/crostini_mount_provider.cc
+++ b/chrome/browser/ash/crostini/crostini_mount_provider.cc
@@ -6,8 +6,10 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/public/types.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 
@@ -51,15 +53,23 @@
     return;
   }
   auto* manager = CrostiniManager::GetForProfile(profile_);
-  auto vm_info = manager->GetVmInfo(container_id_.vm_name);
-  auto container_info = manager->GetContainerInfo(container_id_);
   if (!container_shutdown_observer_.IsObserving()) {
     container_shutdown_observer_.Observe(manager);
   }
-
-  std::move(callback).Run(true, vm_info->info.cid(),
-                          container_info->sftp_vsock_port,
-                          container_info->homedir);
+  // The container's finished booting but we need to wait for the session
+  // tracker to update which races against CrostiniManager calling us.
+  subscription_ =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)
+          ->RunOnceContainerStarted(container_id_,
+                                    base::BindOnce(
+                                        [](PrepareCallback callback,
+                                           guest_os::GuestInfo container_info) {
+                                          std::move(callback).Run(
+                                              true, container_info.cid,
+                                              container_info.sftp_vsock_port,
+                                              container_info.homedir);
+                                        },
+                                        std::move(callback)));
 }
 
 std::unique_ptr<guest_os::GuestOsFileWatcher>
diff --git a/chrome/browser/ash/crostini/crostini_mount_provider.h b/chrome/browser/ash/crostini/crostini_mount_provider.h
index ca5b89bc..f4660e1c4 100644
--- a/chrome/browser/ash/crostini/crostini_mount_provider.h
+++ b/chrome/browser/ash/crostini/crostini_mount_provider.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_CROSTINI_CROSTINI_MOUNT_PROVIDER_H_
 #define CHROME_BROWSER_ASH_CROSTINI_CROSTINI_MOUNT_PROVIDER_H_
 
+#include "base/callback_list.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/crostini/crostini_simple_types.h"
@@ -55,6 +56,7 @@
                           &CrostiniManager::AddContainerShutdownObserver,
                           &CrostiniManager::RemoveContainerShutdownObserver>
       container_shutdown_observer_{this};
+  base::CallbackListSubscription subscription_;
 
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
diff --git a/chrome/browser/ash/crostini/crostini_port_forwarder.cc b/chrome/browser/ash/crostini/crostini_port_forwarder.cc
index 076e135..c079614 100644
--- a/chrome/browser/ash/crostini/crostini_port_forwarder.cc
+++ b/chrome/browser/ash/crostini/crostini_port_forwarder.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ash/crostini/crostini_pref_names.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
 #include "chromeos/dbus/permission_broker/permission_broker_client.h"
@@ -154,8 +155,8 @@
     const PortRuleKey& key,
     const guest_os::GuestId& container_id,
     base::OnceCallback<void(bool)> result_callback) {
-  auto info =
-      CrostiniManager::GetForProfile(profile_)->GetContainerInfo(container_id);
+  auto info = guest_os::GuestOsSessionTracker::GetForProfile(profile_)->GetInfo(
+      container_id);
   if (!info) {
     LOG(ERROR) << "Inactive container to make port rules for.";
     std::move(result_callback).Run(false);
@@ -201,9 +202,10 @@
     const PortRuleKey& key,
     const guest_os::GuestId& container_id,
     base::OnceCallback<void(bool)> result_callback) {
-  auto info =
-      CrostiniManager::GetForProfile(profile_)->GetContainerInfo(container_id);
-  if (!info) {
+  bool running =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)->IsRunning(
+          container_id);
+  if (!running) {
     LOG(ERROR) << "Inactive container to make port rules for.";
     std::move(result_callback).Run(false);
     return;
diff --git a/chrome/browser/ash/crostini/crostini_sshfs.cc b/chrome/browser/ash/crostini/crostini_sshfs.cc
index 92761c21..11962c0 100644
--- a/chrome/browser/ash/crostini/crostini_sshfs.cc
+++ b/chrome/browser/ash/crostini/crostini_sshfs.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
 #include "content/public/browser/browser_thread.h"
@@ -105,8 +106,10 @@
   }
 
   auto* manager = CrostiniManagerFactory::GetForProfile(profile_);
-  absl::optional<ContainerInfo> info = manager->GetContainerInfo(container_id);
-  if (!info) {
+  bool running =
+      guest_os::GuestOsSessionTracker::GetForProfile(profile_)->IsRunning(
+          in_progress_mount_->container_id);
+  if (!running) {
     LOG(ERROR) << "Unable to mount files for a container that's not running";
     Finish(CrostiniSshfsResult::kContainerNotRunning);
     return;
@@ -129,9 +132,8 @@
     return;
   }
 
-  auto* manager = CrostiniManagerFactory::GetForProfile(profile_);
-  absl::optional<ContainerInfo> info =
-      manager->GetContainerInfo(in_progress_mount_->container_id);
+  auto info = guest_os::GuestOsSessionTracker::GetForProfile(profile_)->GetInfo(
+      in_progress_mount_->container_id);
   if (!info) {
     LOG(ERROR) << "Got ssh keys for a container that's not running. Aborting.";
     Finish(CrostiniSshfsResult::kGetContainerInfoFailed);
@@ -141,18 +143,14 @@
   // Add ourselves as an observer so we can continue once the path is mounted.
   auto* dmgr = ash::disks::DiskMountManager::GetInstance();
 
-  // Construct sshfs:// source path.
-  in_progress_mount_->source_path = base::StringPrintf(
-      "sshfs://%s@%s:", info->username.c_str(), hostname.c_str());
-
-  // If we have a vsock port and cid, use sftp:// over vsock instead.
   if (info->sftp_vsock_port != 0) {
-    absl::optional<VmInfo> vm_info =
-        manager->GetVmInfo(in_progress_mount_->container_id.vm_name);
-    if (vm_info) {
-      in_progress_mount_->source_path = base::StringPrintf(
-          "sftp://%" PRId64 ":%u", vm_info->info.cid(), info->sftp_vsock_port);
-    }
+    // If we have a vsock port and cid, use sftp:// over vsock instead.
+    in_progress_mount_->source_path = base::StringPrintf(
+        "sftp://%" PRId64 ":%u", info->cid, info->sftp_vsock_port);
+  } else {
+    // otherwise construct sshfs:// source path.
+    in_progress_mount_->source_path = base::StringPrintf(
+        "sshfs://%s@%s:", info->username.c_str(), hostname.c_str());
   }
   in_progress_mount_->container_homedir = info->homedir;
 
diff --git a/chrome/browser/ash/crostini/crostini_util.cc b/chrome/browser/ash/crostini/crostini_util.cc
index daf96d8..df122223 100644
--- a/chrome/browser/ash/crostini/crostini_util.cc
+++ b/chrome/browser/ash/crostini/crostini_util.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/guest_os_share_path.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
@@ -500,7 +501,7 @@
        guest_os::GetContainers(profile, kCrostiniDefaultVmType)) {
     if (container.container_name != container_id.container_name &&
         container.vm_name == container_id.vm_name) {
-      if (CrostiniManager::GetForProfile(profile)->GetContainerInfo(
+      if (guest_os::GuestOsSessionTracker::GetForProfile(profile)->IsRunning(
               container)) {
         return false;
       }
diff --git a/chrome/browser/ash/cryptauth/client_app_metadata_provider_service.h b/chrome/browser/ash/cryptauth/client_app_metadata_provider_service.h
index 61001df..191609e 100644
--- a/chrome/browser/ash/cryptauth/client_app_metadata_provider_service.h
+++ b/chrome/browser/ash/cryptauth/client_app_metadata_provider_service.h
@@ -13,8 +13,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/system/sys_info.h"
-// TODO(https://crbug.com/1164001): move to forward declaration.
-#include "chromeos/ash/components/network/network_state_handler.h"
 #include "components/gcm_driver/instance_id/instance_id.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -33,6 +31,8 @@
 
 namespace ash {
 
+class NetworkStateHandler;
+
 // Concrete ClientAppMetadataProvider implementation, which lazily computes the
 // ClientAppMetadata when GetClientAppMetadata() is called. Once the
 // ClientAppMetadata has been successfully computed once, the same instance is
diff --git a/chrome/browser/ash/file_manager/app_service_file_tasks.cc b/chrome/browser/ash/file_manager/app_service_file_tasks.cc
index 5e71982b..a9592b3 100644
--- a/chrome/browser/ash/file_manager/app_service_file_tasks.cc
+++ b/chrome/browser/ash/file_manager/app_service_file_tasks.cc
@@ -69,8 +69,9 @@
       // because both are executed through App Service, which can tell the
       // difference itself.
       return TASK_TYPE_FILE_HANDLER;
-    case apps::AppType::kUnknown:
     case apps::AppType::kCrostini:
+      return TASK_TYPE_CROSTINI_APP;
+    case apps::AppType::kUnknown:
     case apps::AppType::kBuiltIn:
     case apps::AppType::kMacOs:
     case apps::AppType::kPluginVm:
@@ -98,6 +99,24 @@
   return true;
 }
 
+// Check if the file URLs can be mapped to a path inside VMs for
+// GuestOS apps to access.
+bool FilesCanBeSharedToVm(Profile* profile, std::vector<GURL> file_urls) {
+  storage::FileSystemContext* file_system_context =
+      util::GetFileManagerFileSystemContext(profile);
+  base::FilePath placeholder_vm_mount("/");
+  base::FilePath not_used;
+  for (const GURL& file_url : file_urls) {
+    if (!file_manager::util::ConvertFileSystemURLToPathInsideVM(
+            profile, file_system_context->CrackURLInFirstPartyContext(file_url),
+            placeholder_vm_mount,
+            /*map_crostini_home=*/false, &not_used)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 Profile* GetProfileWithAppService(Profile* profile) {
   if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
     return profile;
@@ -144,11 +163,16 @@
   // the base profile in these cases (see crbug.com/1111695).
   Profile* profile_with_app_service = GetProfileWithAppService(profile);
   if (!profile_with_app_service) {
+    LOG(WARNING) << "Unexpected profile type";
     return;
   }
+
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(profile_with_app_service);
 
+  bool files_shareable_to_vm =
+      FilesCanBeSharedToVm(profile_with_app_service, file_urls);
+
   std::vector<apps::IntentFilePtr> intent_files;
   intent_files.reserve(entries.size());
   for (size_t i = 0; i < entries.size(); i++) {
@@ -169,6 +193,7 @@
       apps::AppType::kStandaloneBrowserExtension};
   if (ash::features::ShouldArcAndGuestOsFileTasksUseAppService()) {
     supported_app_types.push_back(apps::AppType::kArc);
+    supported_app_types.push_back(apps::AppType::kCrostini);
   }
   for (auto& launch_entry : intent_launch_info) {
     auto app_type = proxy->AppRegistryCache().GetAppType(launch_entry.app_id);
@@ -213,6 +238,10 @@
         continue;
     }
 
+    if (app_type == apps::AppType::kCrostini && !files_shareable_to_vm) {
+      continue;
+    }
+
     constexpr int kIconSize = 32;
     GURL icon_url =
         apps::AppIconSource::GetIconURL(launch_entry.app_id, kIconSize);
@@ -265,7 +294,8 @@
   if (ash::features::ShouldArcAndGuestOsFileTasksUseAppService()) {
     DCHECK(task.task_type == TASK_TYPE_ARC_APP ||
            task.task_type == TASK_TYPE_WEB_APP ||
-           task.task_type == TASK_TYPE_FILE_HANDLER);
+           task.task_type == TASK_TYPE_FILE_HANDLER ||
+           task.task_type == TASK_TYPE_CROSTINI_APP);
   } else {
     DCHECK(task.task_type == TASK_TYPE_WEB_APP ||
            task.task_type == TASK_TYPE_FILE_HANDLER);
diff --git a/chrome/browser/ash/file_manager/app_service_file_tasks_unittest.cc b/chrome/browser/ash/file_manager/app_service_file_tasks_unittest.cc
index b6035f4..43445bd 100644
--- a/chrome/browser/ash/file_manager/app_service_file_tasks_unittest.cc
+++ b/chrome/browser/ash/file_manager/app_service_file_tasks_unittest.cc
@@ -9,15 +9,18 @@
 #include <vector>
 
 #include "ash/constants/ash_features.h"
+#include "base/strings/escape.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/app_service_test.h"
 #include "chrome/browser/apps/app_service/intent_util.h"
+#include "chrome/browser/ash/crostini/crostini_test_helper.h"
 #include "chrome/browser/ash/file_manager/file_tasks.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/intent_filter.h"
 #include "components/services/app_service/public/cpp/intent_test_util.h"
@@ -26,6 +29,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/entry_info.h"
 #include "extensions/common/extension_builder.h"
+#include "storage/browser/file_system/external_mount_points.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
@@ -54,13 +58,6 @@
 const char kActivityLabelAny[] = "some_any_file";
 const char kActivityLabelTextWild[] = "some_text_wild_file";
 
-GURL test_url(const std::string& file_name) {
-  GURL url =
-      GURL("filesystem:chrome-extension://extensionid/external/" + file_name);
-  EXPECT_TRUE(url.is_valid());
-  return url;
-}
-
 }  // namespace
 
 namespace file_manager {
@@ -75,6 +72,10 @@
     app_service_proxy_ =
         apps::AppServiceProxyFactory::GetForProfile(profile_.get());
     ASSERT_TRUE(app_service_proxy_);
+    storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+        util::GetDownloadsMountPointName(profile_.get()),
+        storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
+        util::GetMyFilesFolderForProfile(profile_.get()));
   }
 
   Profile* profile() { return profile_.get(); }
@@ -83,8 +84,19 @@
     std::string file_name;
     std::string mime_type;
     bool is_directory = false;
+    GURL file_url;
   };
 
+  GURL test_url(const std::string& file_name) {
+    GURL url =
+        GURL("filesystem:chrome-extension://id/external/" +
+             base::EscapeUrlEncodedData(
+                 util::GetDownloadsMountPointName(profile()) + "/" + file_name,
+                 /*use_plus=*/false));
+    EXPECT_TRUE(url.is_valid());
+    return url;
+  }
+
   std::vector<FullTaskDescriptor> FindAppServiceTasks(
       const std::vector<FakeFile>& files) {
     std::vector<extensions::EntryInfo> entries;
@@ -94,7 +106,11 @@
           util::GetMyFilesFolderForProfile(profile()).AppendASCII(
               fake_file.file_name),
           fake_file.mime_type, fake_file.is_directory);
-      file_urls.push_back(test_url(fake_file.file_name));
+      if (fake_file.file_url.is_empty()) {
+        file_urls.push_back(test_url(fake_file.file_name));
+      } else {
+        file_urls.push_back(fake_file.file_url);
+      }
     }
 
     std::vector<FullTaskDescriptor> tasks;
@@ -297,8 +313,8 @@
                                 apps::AppType::kChromeApp, true);
   }
 
-  apps::IntentFilterPtr CreateArcFileIntentFilter(std::string action,
-                                                  std::string mime_type) {
+  apps::IntentFilterPtr CreateFileIntentFilter(std::string action,
+                                               std::string mime_type) {
     auto intent_filter = std::make_unique<apps::IntentFilter>();
     intent_filter->AddSingleValueCondition(apps::ConditionType::kAction, action,
                                            apps::PatternMatchType::kLiteral);
@@ -319,6 +335,14 @@
     return app_id;
   }
 
+  void AddCrostiniAppWithIntentFilter(std::string app_id,
+                                      apps::IntentFilterPtr intent_filter) {
+    std::vector<apps::IntentFilterPtr> filters;
+    filters.push_back(std::move(intent_filter));
+    AddFakeAppWithIntentFilters(app_id, std::move(filters),
+                                apps::AppType::kCrostini, true);
+  }
+
   base::test::ScopedFeatureList feature_list_;
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> profile_;
@@ -365,13 +389,28 @@
   std::string text_activity = "TextViewerActivity";
   std::string text_app_id = AddArcAppWithIntentFilter(
       text_package_name, text_activity,
-      CreateArcFileIntentFilter(apps_util::kIntentActionView, text_mime_type));
+      CreateFileIntentFilter(apps_util::kIntentActionView, text_mime_type));
 
   std::vector<FullTaskDescriptor> tasks =
       FindAppServiceTasks({{"foo.txt", text_mime_type}});
   ASSERT_EQ(0U, tasks.size());
 }
 
+// Crostini apps should not be found when kArcAndGuestOsFileTasksUseAppService
+// is disabled.
+TEST_F(AppServiceFileTasksTestDisabled, FindAppServiceCrostiniApp) {
+  std::string text_mime_type = "text/plain";
+  std::string file_name = "foo.txt";
+  std::string text_app_id = "Text app";
+  AddCrostiniAppWithIntentFilter(
+      text_app_id,
+      CreateFileIntentFilter(apps_util::kIntentActionView, text_mime_type));
+
+  std::vector<FullTaskDescriptor> tasks =
+      FindAppServiceTasks({{file_name, text_mime_type}});
+  ASSERT_EQ(0U, tasks.size());
+}
+
 // An app which does not handle intents should not be found even if the filters
 // match.
 TEST_F(AppServiceFileTasksTestEnabled, FindAppServiceFileTasksHandlesIntent) {
@@ -647,14 +686,14 @@
   std::string text_activity = "TextViewerActivity";
   std::string text_app_id = AddArcAppWithIntentFilter(
       text_package_name, text_activity,
-      CreateArcFileIntentFilter(apps_util::kIntentActionView, text_mime_type));
+      CreateFileIntentFilter(apps_util::kIntentActionView, text_mime_type));
 
   // Create an app with an image file filter.
   std::string image_package_name = "com.example.imageViewer";
   std::string image_activity = "ImageViewerActivity";
   std::string image_app_id = AddArcAppWithIntentFilter(
       image_package_name, image_activity,
-      CreateArcFileIntentFilter(apps_util::kIntentActionView, image_mime_type));
+      CreateFileIntentFilter(apps_util::kIntentActionView, image_mime_type));
 
   // Check if only the text ARC app appears as a result.
   std::vector<FullTaskDescriptor> tasks =
@@ -665,5 +704,67 @@
   EXPECT_FALSE(tasks[0].is_file_extension_match);
 }
 
+TEST_F(AppServiceFileTasksTestEnabled, FindAppServiceCrostiniApp) {
+  std::string file_name = "foo.txt";
+  std::string text_app_id = "Text app";
+  AddCrostiniAppWithIntentFilter(
+      text_app_id,
+      CreateFileIntentFilter(apps_util::kIntentActionView, kMimeTypeText));
+
+  // Check if the text Crostini app is returned.
+  std::vector<FullTaskDescriptor> tasks =
+      FindAppServiceTasks({{file_name, kMimeTypeText}});
+  ASSERT_EQ(1U, tasks.size());
+  EXPECT_EQ(text_app_id, tasks[0].task_descriptor.app_id);
+  EXPECT_FALSE(tasks[0].is_generic_file_handler);
+  EXPECT_FALSE(tasks[0].is_file_extension_match);
+}
+
+TEST_F(AppServiceFileTasksTestEnabled, CrostiniCheckPathsCanBeShared) {
+  std::string file_name = "foo.txt";
+  std::string text_app_id = "Text app";
+  AddCrostiniAppWithIntentFilter(
+      text_app_id,
+      CreateFileIntentFilter(apps_util::kIntentActionView, kMimeTypeText));
+
+  // Possible to share path.
+  std::vector<FullTaskDescriptor> tasks =
+      FindAppServiceTasks({{file_name, kMimeTypeText}});
+  ASSERT_EQ(1U, tasks.size());
+  EXPECT_EQ(text_app_id, tasks[0].task_descriptor.app_id);
+
+  // Should not be possible to share path.
+  GURL invalid_url = GURL("broken:url");
+  tasks =
+      FindAppServiceTasks({{file_name, kMimeTypeText, /*is_directory=*/false,
+                            /*file_url=*/invalid_url}});
+  ASSERT_EQ(0U, tasks.size());
+}
+
+TEST_F(AppServiceFileTasksTestEnabled, FindMultipleAppServiceCrostiniApps) {
+  std::string file_name = "foo.txt";
+  std::string app_id_1 = "Text app 1";
+  std::string app_id_2 = "Text app 2";
+  AddCrostiniAppWithIntentFilter(
+      app_id_1,
+      CreateFileIntentFilter(apps_util::kIntentActionView, kMimeTypeText));
+  AddCrostiniAppWithIntentFilter(
+      app_id_2,
+      CreateFileIntentFilter(apps_util::kIntentActionView, kMimeTypeText));
+
+  // Check if both Crostini apps are returned.
+  std::vector<FullTaskDescriptor> tasks =
+      FindAppServiceTasks({{file_name, kMimeTypeText}});
+  ASSERT_EQ(2U, tasks.size());
+
+  EXPECT_EQ(app_id_1, tasks[0].task_descriptor.app_id);
+  EXPECT_FALSE(tasks[0].is_generic_file_handler);
+  EXPECT_FALSE(tasks[0].is_file_extension_match);
+
+  EXPECT_EQ(app_id_2, tasks[1].task_descriptor.app_id);
+  EXPECT_FALSE(tasks[1].is_generic_file_handler);
+  EXPECT_FALSE(tasks[1].is_file_extension_match);
+}
+
 }  // namespace file_tasks
 }  // namespace file_manager.
diff --git a/chrome/browser/ash/file_manager/file_tasks.cc b/chrome/browser/ash/file_manager/file_tasks.cc
index 17943d7..f99da98 100644
--- a/chrome/browser/ash/file_manager/file_tasks.cc
+++ b/chrome/browser/ash/file_manager/file_tasks.cc
@@ -717,10 +717,12 @@
     return true;
   }
 
-  // ARC apps and web apps need mime types for launching. Retrieve them first.
+  // Apps from App Service need mime types for launching. Retrieve them first.
   if (task.task_type == TASK_TYPE_ARC_APP ||
       task.task_type == TASK_TYPE_WEB_APP ||
-      task.task_type == TASK_TYPE_FILE_HANDLER) {
+      task.task_type == TASK_TYPE_FILE_HANDLER ||
+      (ash::features::ShouldArcAndGuestOsFileTasksUseAppService() &&
+       task.task_type == TASK_TYPE_CROSTINI_APP)) {
     // TODO(petermarshall): Implement GetProfileForExtensionTask in Lacros if
     // necessary, for Chrome Apps.
     extensions::app_file_handler_util::MimeTypeCollector* mime_collector =
@@ -732,8 +734,9 @@
     return true;
   }
 
-  if (task.task_type == TASK_TYPE_CROSTINI_APP ||
-      task.task_type == TASK_TYPE_PLUGIN_VM_APP) {
+  if (!ash::features::ShouldArcAndGuestOsFileTasksUseAppService() &&
+      (task.task_type == TASK_TYPE_CROSTINI_APP ||
+       task.task_type == TASK_TYPE_PLUGIN_VM_APP)) {
     DCHECK_EQ(kGuestOsAppActionID, task.action_id);
     ExecuteGuestOsTask(profile, task, file_urls, std::move(done));
     return true;
@@ -817,9 +820,11 @@
       new std::vector<FullTaskDescriptor>);
 
   if (ash::features::ShouldArcAndGuestOsFileTasksUseAppService()) {
-    // Skip FindArcTasks since ARC tasks are now found in App Service.
-    FindExtensionAndAppTasks(profile, entries, file_urls, std::move(callback),
-                             std::move(result_list));
+    // Skip FindArcTasks and FindGuestOsTasks since these tasks are now found in
+    // App Service.
+    FindAppServiceTasks(profile, entries, file_urls, result_list.get());
+    PostProcessFoundTasks(profile, entries, std::move(callback),
+                          std::move(result_list));
   } else {
     // 1. Find and append ARC handler tasks.
     FindArcTasks(profile, entries, file_urls, std::move(result_list),
diff --git a/chrome/browser/ash/file_manager/path_util.cc b/chrome/browser/ash/file_manager/path_util.cc
index 15deb3f..964f829 100644
--- a/chrome/browser/ash/file_manager/path_util.cc
+++ b/chrome/browser/ash/file_manager/path_util.cc
@@ -33,6 +33,7 @@
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/smb_client/smb_service.h"
@@ -493,8 +494,8 @@
   } else if (id == GetCrostiniMountPointName(profile)) {
     // Crostini.
     if (map_crostini_home) {
-      absl::optional<crostini::ContainerInfo> container_info =
-          crostini::CrostiniManager::GetForProfile(profile)->GetContainerInfo(
+      auto container_info =
+          guest_os::GuestOsSessionTracker::GetForProfile(profile)->GetInfo(
               crostini::DefaultContainerId());
       if (!container_info) {
         return false;
@@ -548,8 +549,8 @@
   base::FilePath relative_path;
 
   if (map_crostini_home) {
-    absl::optional<crostini::ContainerInfo> container_info =
-        crostini::CrostiniManager::GetForProfile(profile)->GetContainerInfo(
+    auto container_info =
+        guest_os::GuestOsSessionTracker::GetForProfile(profile)->GetInfo(
             crostini::DefaultContainerId());
     if (container_info &&
         AppendRelativePath(container_info->homedir, inside, &relative_path)) {
diff --git a/chrome/browser/ash/file_manager/restore_io_task.cc b/chrome/browser/ash/file_manager/restore_io_task.cc
index 961ee5c3..5f92bd17c 100644
--- a/chrome/browser/ash/file_manager/restore_io_task.cc
+++ b/chrome/browser/ash/file_manager/restore_io_task.cc
@@ -7,7 +7,6 @@
 #include "base/callback.h"
 #include "base/files/file.h"
 #include "base/files/file_util.h"
-#include "base/strings/string_piece.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -78,13 +77,10 @@
     return;
   }
 
-  enabled_trash_locations_ =
-      GenerateEnabledTrashLocationsForProfile(profile_, base_path_);
   progress_.state = State::kInProgress;
-
-  parser_ = std::make_unique<chromeos::trash_service::TrashInfoParser>(
-      base::BindOnce(&RestoreIOTask::Complete, weak_ptr_factory_.GetWeakPtr(),
-                     State::kError));
+  validator_ = std::make_unique<TrashInfoValidator>(profile_, base_path_);
+  validator_->SetDisconnectHandler(base::BindOnce(
+      &RestoreIOTask::Complete, weak_ptr_factory_.GetWeakPtr(), State::kError));
 
   ValidateTrashInfo(0);
 }
@@ -104,101 +100,32 @@
       (base_path_.empty())
           ? progress_.sources[idx].url.path()
           : base_path_.Append(progress_.sources[idx].url.path());
-  if (trash_info.FinalExtension() != kTrashInfoExtension) {
-    progress_.sources[idx].error = base::File::FILE_ERROR_INVALID_URL;
-    Complete(State::kError);
-    return;
-  }
 
-  // Ensures the trash location is parented at an enabled trash location.
-  base::FilePath trash_folder_location;
-  base::FilePath mount_point_path;
-  for (const auto& [parent_path, info] : enabled_trash_locations_) {
-    if (parent_path.Append(info.relative_folder_path).IsParent(trash_info)) {
-      trash_folder_location = parent_path.Append(info.relative_folder_path);
-      mount_point_path = info.mount_point_path;
-      break;
-    }
-  }
-
-  if (mount_point_path.empty() || trash_folder_location.empty()) {
-    progress_.sources[idx].error = base::File::FILE_ERROR_INVALID_OPERATION;
-    Complete(State::kError);
-    return;
-  }
-
-  // Ensure the corresponding file that this metadata file refers to actually
-  // exists.
-  base::FilePath trashed_file_location =
-      trash_folder_location.Append(kFilesFolderName)
-          .Append(trash_info.BaseName().RemoveFinalExtension());
-
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&base::PathExists, trashed_file_location),
-      base::BindOnce(&RestoreIOTask::OnTrashedFileExists,
-                     weak_ptr_factory_.GetWeakPtr(), idx, mount_point_path,
-                     trashed_file_location));
-}
-
-void RestoreIOTask::OnTrashedFileExists(
-    size_t idx,
-    const base::FilePath& mount_point_path,
-    const base::FilePath& trashed_file_location,
-    bool exists) {
-  if (!exists) {
-    progress_.sources[idx].error = base::File::FILE_ERROR_NOT_FOUND;
-    Complete(State::kError);
-    return;
-  }
-
-  auto complete_callback = base::BindPostTask(
-      base::SequencedTaskRunnerHandle::Get(),
+  auto on_parsed_callback =
       base::BindOnce(&RestoreIOTask::EnsureParentRestorePathExists,
-                     weak_ptr_factory_.GetWeakPtr(), idx, mount_point_path,
-                     trashed_file_location));
+                     weak_ptr_factory_.GetWeakPtr(), idx);
 
-  base::FilePath trashinfo_path =
-      (base_path_.empty())
-          ? progress_.sources[idx].url.path()
-          : base_path_.Append(progress_.sources[idx].url.path());
-
-  parser_->ParseTrashInfoFile(std::move(trashinfo_path),
-                              std::move(complete_callback));
+  validator_->ValidateAndParseTrashInfo(std::move(trash_info),
+                                        std::move(on_parsed_callback));
 }
 
 void RestoreIOTask::EnsureParentRestorePathExists(
     size_t idx,
-    const base::FilePath& mount_point_path,
-    const base::FilePath& trashed_file_location,
-    base::File::Error status,
-    const base::FilePath& restore_path,
-    base::Time deletion_date) {
-  if (status != base::File::FILE_OK) {
-    progress_.sources[idx].error = status;
+    base::FileErrorOr<ParsedTrashInfoData> parsed_data) {
+  if (parsed_data.is_error()) {
+    progress_.sources[idx].error = parsed_data.error();
     Complete(State::kError);
     return;
   }
 
-  if (restore_path.empty() ||
-      restore_path.value()[0] != base::FilePath::kSeparators[0]) {
-    progress_.sources[idx].error = base::File::FILE_ERROR_INVALID_URL;
-    Complete(State::kError);
-    return;
-  }
-
-  // Remove the leading "/" character to make the restore path relative from the
-  // known trash parent path.
-  base::StringPiece relative_path =
-      base::StringPiece(restore_path.value()).substr(1);
-  base::FilePath absolute_restore_path = mount_point_path.Append(relative_path);
-
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&CreateNestedPath, absolute_restore_path.DirName()),
+      base::BindOnce(&CreateNestedPath,
+                     parsed_data.value().absolute_restore_path.DirName()),
       base::BindOnce(&RestoreIOTask::OnParentRestorePathExists,
-                     weak_ptr_factory_.GetWeakPtr(), idx, trashed_file_location,
-                     absolute_restore_path));
+                     weak_ptr_factory_.GetWeakPtr(), idx,
+                     parsed_data.value().trashed_file_path,
+                     parsed_data.value().absolute_restore_path));
 }
 
 void RestoreIOTask::OnParentRestorePathExists(
diff --git a/chrome/browser/ash/file_manager/restore_io_task.h b/chrome/browser/ash/file_manager/restore_io_task.h
index ac9b6b28..b0d4656 100644
--- a/chrome/browser/ash/file_manager/restore_io_task.h
+++ b/chrome/browser/ash/file_manager/restore_io_task.h
@@ -11,7 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/file_manager/io_task.h"
 #include "chrome/browser/ash/file_manager/trash_common_util.h"
-#include "chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h"
+#include "chrome/browser/ash/file_manager/trash_info_validator.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "storage/browser/file_system/file_system_operation_runner.h"
 #include "storage/browser/file_system/file_system_url.h"
@@ -46,27 +46,15 @@
   // Finalises the RestoreIOTask with the `state`.
   void Complete(State state);
 
-  // Ensure the metadata file conforms to the following:
-  //   - Has a .trashinfo suffix
-  //   - Resides in an enabled trash directory
-  //   - The file resides in the info directory
-  //   - Has an identical item in the files directory with no .trashinfo suffix
+  // Calls the underlying TrashInfoValidator to perform validation on the
+  // supplied .trashinfo file.
   void ValidateTrashInfo(size_t idx);
 
-  void OnTrashedFileExists(size_t idx,
-                           const base::FilePath& mount_point_path,
-                           const base::FilePath& trashed_file_location,
-                           bool exists);
-
   // Make sure the enclosing folder where the trashed file to be restored to
   // actually exists. In the event the file path has been removed, recreate it.
   void EnsureParentRestorePathExists(
       size_t idx,
-      const base::FilePath& mount_point_path,
-      const base::FilePath& trashed_file_location,
-      base::File::Error status,
-      const base::FilePath& restore_path,
-      base::Time deletion_date);
+      base::FileErrorOr<ParsedTrashInfoData> parsed_data);
 
   void OnParentRestorePathExists(size_t idx,
                                  const base::FilePath& trashed_file_location,
@@ -107,16 +95,12 @@
   // only in testing.
   base::FilePath base_path_;
 
-  // A map containing paths which are enabled for trashing.
-  TrashPathsMap enabled_trash_locations_;
-
   // Stores the id of the restore operation if one is in progress. Used so the
   // restore can be cancelled.
   absl::optional<storage::FileSystemOperationRunner::OperationID> operation_id_;
 
-  // Holds the connection open to the `TrashService`. This is a sandboxed
-  // process that performs parsing of the trashinfo files.
-  std::unique_ptr<chromeos::trash_service::TrashInfoParser> parser_ = nullptr;
+  // Validates and parses .trashinfo files.
+  std::unique_ptr<TrashInfoValidator> validator_ = nullptr;
 
   ProgressCallback progress_callback_;
   CompleteCallback complete_callback_;
diff --git a/chrome/browser/ash/file_manager/trash_info_validator.cc b/chrome/browser/ash/file_manager/trash_info_validator.cc
new file mode 100644
index 0000000..f35e6ca
--- /dev/null
+++ b/chrome/browser/ash/file_manager/trash_info_validator.cc
@@ -0,0 +1,146 @@
+// 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/ash/file_manager/trash_info_validator.h"
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_piece.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+class Profile;
+
+namespace file_manager {
+
+namespace {
+
+void RunCallbackWithError(base::File::Error error,
+                          ValidateAndParseTrashInfoCallback callback) {
+  std::move(callback).Run(base::FileErrorOr<ParsedTrashInfoData>(error));
+}
+
+}  // namespace
+
+TrashInfoValidator::TrashInfoValidator(Profile* profile,
+                                       const base::FilePath& base_path) {
+  enabled_trash_locations_ =
+      io_task::GenerateEnabledTrashLocationsForProfile(profile, base_path);
+
+  parser_ = std::make_unique<chromeos::trash_service::TrashInfoParser>();
+}
+
+TrashInfoValidator::~TrashInfoValidator() = default;
+
+void TrashInfoValidator::SetDisconnectHandler(
+    base::OnceCallback<void()> disconnect_callback) {
+  DCHECK(parser_) << "Parser should not be null here";
+  if (parser_) {
+    parser_->SetDisconnectHandler(std::move(disconnect_callback));
+  }
+}
+
+void TrashInfoValidator::ValidateAndParseTrashInfo(
+    const base::FilePath& trash_info_path,
+    ValidateAndParseTrashInfoCallback callback) {
+  // Validates the supplied file ends in a .trashinfo extension.
+  if (trash_info_path.FinalExtension() != io_task::kTrashInfoExtension) {
+    RunCallbackWithError(base::File::FILE_ERROR_INVALID_URL,
+                         std::move(callback));
+    return;
+  }
+
+  // Validate the .trashinfo file belongs in an enabled trash location.
+  base::FilePath trash_folder_location;
+  base::FilePath mount_point_path;
+  for (const auto& [parent_path, info] : enabled_trash_locations_) {
+    if (parent_path.Append(info.relative_folder_path)
+            .IsParent(trash_info_path)) {
+      trash_folder_location = parent_path.Append(info.relative_folder_path);
+      mount_point_path = info.mount_point_path;
+      break;
+    }
+  }
+
+  if (mount_point_path.empty() || trash_folder_location.empty()) {
+    RunCallbackWithError(base::File::FILE_ERROR_INVALID_OPERATION,
+                         std::move(callback));
+    return;
+  }
+
+  // Ensure the corresponding file that this metadata file refers to actually
+  // exists.
+  base::FilePath trashed_file_location =
+      trash_folder_location.Append(io_task::kFilesFolderName)
+          .Append(trash_info_path.BaseName().RemoveFinalExtension());
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&base::PathExists, trashed_file_location),
+      base::BindOnce(&TrashInfoValidator::OnTrashedFileExists,
+                     weak_ptr_factory_.GetWeakPtr(), mount_point_path,
+                     trashed_file_location, std::move(trash_info_path),
+                     std::move(callback)));
+}
+
+void TrashInfoValidator::OnTrashedFileExists(
+    const base::FilePath& mount_point_path,
+    const base::FilePath& trashed_file_location,
+    const base::FilePath& trash_info_path,
+    ValidateAndParseTrashInfoCallback callback,
+    bool exists) {
+  if (!exists) {
+    RunCallbackWithError(base::File::FILE_ERROR_NOT_FOUND, std::move(callback));
+    return;
+  }
+
+  auto complete_callback = base::BindPostTask(
+      base::SequencedTaskRunnerHandle::Get(),
+      base::BindOnce(&TrashInfoValidator::OnTrashInfoParsed,
+                     weak_ptr_factory_.GetWeakPtr(), mount_point_path,
+                     trashed_file_location, std::move(callback)));
+
+  parser_->ParseTrashInfoFile(std::move(trash_info_path),
+                              std::move(complete_callback));
+}
+
+void TrashInfoValidator::OnTrashInfoParsed(
+    const base::FilePath& mount_point_path,
+    const base::FilePath& trashed_file_location,
+    ValidateAndParseTrashInfoCallback callback,
+    base::File::Error status,
+    const base::FilePath& restore_path,
+    base::Time deletion_date) {
+  if (status != base::File::FILE_OK) {
+    RunCallbackWithError(status, std::move(callback));
+    return;
+  }
+
+  // The restore path that was parsed could be empty or not have a leading "/".
+  if (restore_path.empty() ||
+      restore_path.value()[0] != base::FilePath::kSeparators[0]) {
+    RunCallbackWithError(base::File::FILE_ERROR_INVALID_URL,
+                         std::move(callback));
+    return;
+  }
+
+  // Remove the leading "/" character to make the restore path relative from the
+  // known trash parent path.
+  base::StringPiece relative_path =
+      base::StringPiece(restore_path.value()).substr(1);
+  base::FilePath absolute_restore_path = mount_point_path.Append(relative_path);
+
+  ParsedTrashInfoData parsed_data = {
+      .trashed_file_path = std::move(trashed_file_location),
+      .absolute_restore_path = std::move(absolute_restore_path),
+      .deletion_date = std::move(deletion_date),
+  };
+
+  std::move(callback).Run(
+      base::FileErrorOr<ParsedTrashInfoData>(std::move(parsed_data)));
+}
+
+}  // namespace file_manager
diff --git a/chrome/browser/ash/file_manager/trash_info_validator.h b/chrome/browser/ash/file_manager/trash_info_validator.h
new file mode 100644
index 0000000..25b3295e
--- /dev/null
+++ b/chrome/browser/ash/file_manager/trash_info_validator.h
@@ -0,0 +1,108 @@
+// 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 CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_INFO_VALIDATOR_H_
+#define CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_INFO_VALIDATOR_H_
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/files/file_error_or.h"
+#include "base/files/file_path.h"
+#include "base/time/time.h"
+#include "chrome/browser/ash/file_manager/trash_common_util.h"
+#include "chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h"
+
+namespace file_manager {
+
+// On a successful parse of .trashinfo files, returns the restoration path,
+// deletion date and actual location of the trashed file.
+struct ParsedTrashInfoData {
+  // The actual on-disk location of the trashed file, e.g. .Trash/files/foo.txt.
+  base::FilePath trashed_file_path;
+
+  // The path to restore the file back to. The basename here and the basename
+  // for the `trashed_file_path` may differ as the path may conflict with
+  // another in the trash.
+  base::FilePath absolute_restore_path;
+
+  // The date/time the file was trashed.
+  base::Time deletion_date;
+};
+
+// Helper alias to define the callback type that is returned from the validator.
+using ValidateAndParseTrashInfoCallback =
+    base::OnceCallback<void(base::FileErrorOr<ParsedTrashInfoData>)>;
+
+// Validates and parses individual .trashinfo files to ensure they conform to
+// the XDG specification. This is exposed here as we need to get a file handler
+// and ensure files exist prior to parsing them. This can be done in a
+// privileged context, but the parsing cannot. To use this:
+//   1. Initialize a `TrashInfoValidator` like:
+//        auto parser = std::make_unique<TrashInfoValidator>(profile,
+//          base_path);
+//   2. Set your disconnect handler in the event the underlying trash service
+//   disconnects errorenously.
+//        parser->SetDisconnectHandler(base::BindOnce(&Method, WeakPtr()));
+//   3. For every file to validate and parse, call the ValidateAndParseTrashInfo
+//   method, e.g.
+//        parser->ValidateAndParseTrashInfo(trash_info_path,
+//          base::BindOnce(&OnParsed, WeakPtr()));
+class TrashInfoValidator {
+ public:
+  // The `base_path` here is used primarily for testing purposes to identify the
+  // enabled trash locations.
+  TrashInfoValidator(Profile* profile, const base::FilePath& base_path);
+  ~TrashInfoValidator();
+
+  TrashInfoValidator(const TrashInfoValidator&) = delete;
+  TrashInfoValidator& operator=(const TrashInfoValidator&) = delete;
+
+  // Ensure the metadata file conforms to the following:
+  //   - Has a .trashinfo suffix
+  //   - Resides in an enabled trash directory
+  //   - The file resides in the info directory
+  //   - Has an identical item in the files directory with no .trashinfo suffix
+  // On confirming the above it then calls the TrashService to retrieve the
+  // parsed trashinfo data. The `trash_info_path` must be absolute.
+  void ValidateAndParseTrashInfo(const base::FilePath& trash_info_path,
+                                 ValidateAndParseTrashInfoCallback callback);
+
+  // Set the disconnect handler for the underlying TrashService.
+  // TODO(b/238946031): Potentially centralize this by calling the `callback`
+  // instead of having a separate disconnect callback.
+  void SetDisconnectHandler(base::OnceCallback<void()> disconnect_callback);
+
+ private:
+  // Invoked after verifying if the on-disk file exists. The `mount_point_path`
+  // represents the location where the .Trash folder resides (e.g. ~/MyFiles),
+  // the `trashed_file_location` is the on-disk file that should be restored and
+  // the `trash_info_path` represents the location of the .trashinfo file.
+  void OnTrashedFileExists(const base::FilePath& mount_point_path,
+                           const base::FilePath& trashed_file_location,
+                           const base::FilePath& trash_info_path,
+                           ValidateAndParseTrashInfoCallback callback,
+                           bool exists);
+
+  // Invoked when the TrashService has finished parsing the .trashinfo file.
+  void OnTrashInfoParsed(const base::FilePath& mount_point_path,
+                         const base::FilePath& trashed_file_location,
+                         ValidateAndParseTrashInfoCallback callback,
+                         base::File::Error status,
+                         const base::FilePath& restore_path,
+                         base::Time deletion_date);
+
+  // A map containing paths which are enabled for trashing.
+  io_task::TrashPathsMap enabled_trash_locations_;
+
+  // Holds the connection open to the `TrashService`. This is a sandboxed
+  // process that performs parsing of the trashinfo files.
+  std::unique_ptr<chromeos::trash_service::TrashInfoParser> parser_ = nullptr;
+
+  base::WeakPtrFactory<TrashInfoValidator> weak_ptr_factory_{this};
+};
+
+}  // namespace file_manager
+
+#endif  // CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_INFO_VALIDATOR_H_
diff --git a/chrome/browser/ash/guest_os/guest_os_session_tracker.cc b/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
index 70c95ff..0cadeb99 100644
--- a/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
+++ b/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
@@ -132,6 +132,10 @@
   return iter->second;
 }
 
+bool GuestOsSessionTracker::IsRunning(const GuestId& id) {
+  return guests_.contains(id);
+}
+
 // ash::ConciergeClient::VmObserver overrides.
 void GuestOsSessionTracker::OnVmStarted(
     const vm_tools::concierge::VmStartedSignal& signal) {
diff --git a/chrome/browser/ash/guest_os/guest_os_session_tracker.h b/chrome/browser/ash/guest_os/guest_os_session_tracker.h
index 652f41a..2a2f9bb3 100644
--- a/chrome/browser/ash/guest_os/guest_os_session_tracker.h
+++ b/chrome/browser/ash/guest_os/guest_os_session_tracker.h
@@ -57,9 +57,13 @@
       base::OnceCallback<void(GuestInfo)> callback);
 
   // Returns information about a running guest. Returns nullopt if the guest
-  // isn't recognised e.g. it's not running.
+  // isn't recognised e.g. it's not running. If you just want to check if a
+  // guest is running or not and don't need the info, use `IsRunning` instead
   absl::optional<GuestInfo> GetInfo(const GuestId& id);
 
+  // Returns true if a guest is running, false otherwise.
+  bool IsRunning(const GuestId& id);
+
   void AddGuestForTesting(const GuestId& id, const GuestInfo& info);
 
  protected:
diff --git a/chrome/browser/ash/login/helper.cc b/chrome/browser/ash/login/helper.cc
index bffde6a..9191f49 100644
--- a/chrome/browser/ash/login/helper.cc
+++ b/chrome/browser/ash/login/helper.cc
@@ -87,20 +87,17 @@
 }
 
 bool NetworkStateHelper::IsConnected() const {
-  chromeos::NetworkStateHandler* nsh =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* nsh = NetworkHandler::Get()->network_state_handler();
   return nsh->ConnectedNetworkByType(NetworkTypePattern::Default()) != nullptr;
 }
 
 bool NetworkStateHelper::IsConnectedToEthernet() const {
-  chromeos::NetworkStateHandler* nsh =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* nsh = NetworkHandler::Get()->network_state_handler();
   return nsh->ConnectedNetworkByType(NetworkTypePattern::Ethernet()) != nullptr;
 }
 
 bool NetworkStateHelper::IsConnecting() const {
-  chromeos::NetworkStateHandler* nsh =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* nsh = NetworkHandler::Get()->network_state_handler();
   return nsh->ConnectingNetworkByType(NetworkTypePattern::Default()) != nullptr;
 }
 
diff --git a/chrome/browser/ash/login/screens/network_screen.h b/chrome/browser/ash/login/screens/network_screen.h
index dd6fc91..09fc84e 100644
--- a/chrome/browser/ash/login/screens/network_screen.h
+++ b/chrome/browser/ash/login/screens/network_screen.h
@@ -154,8 +154,7 @@
   ScreenExitCallback exit_callback_;
   std::unique_ptr<login::NetworkStateHelper> network_state_helper_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   base::WeakPtrFactory<NetworkScreen> weak_ptr_factory_{this};
diff --git a/chrome/browser/ash/login/screens/update_required_screen.h b/chrome/browser/ash/login/screens/update_required_screen.h
index 6055b458..7f9db0d 100644
--- a/chrome/browser/ash/login/screens/update_required_screen.h
+++ b/chrome/browser/ash/login/screens/update_required_screen.h
@@ -121,8 +121,7 @@
   base::RepeatingClosure exit_callback_;
   std::unique_ptr<ErrorScreensHistogramHelper> histogram_helper_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   // Whether the screen is shown.
diff --git a/chrome/browser/ash/login/security_token_login_browsertest.cc b/chrome/browser/ash/login/security_token_login_browsertest.cc
index 4294d2c..9156a0c 100644
--- a/chrome/browser/ash/login/security_token_login_browsertest.cc
+++ b/chrome/browser/ash/login/security_token_login_browsertest.cc
@@ -25,7 +25,9 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/existing_user_controller.h"
+#include "chrome/browser/ash/login/lock/screen_locker_tester.h"
 #include "chrome/browser/ash/login/saml/security_token_saml_test.h"
+#include "chrome/browser/ash/login/security_token_session_controller.h"
 #include "chrome/browser/ash/login/test/cryptohome_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
@@ -244,8 +246,13 @@
 
   void WaitForChromeTerminating() { termination_loop_.Run(); }
 
+  bool is_chrome_terminating() const { return is_chrome_terminating_; }
+
   // SessionObserver
-  void OnChromeTerminating() override { termination_loop_.Quit(); }
+  void OnChromeTerminating() override {
+    is_chrome_terminating_ = true;
+    termination_loop_.Quit();
+  }
 
   void OnSessionStateChanged(session_manager::SessionState state) override {
     if (state == session_manager::SessionState::LOCKED)
@@ -253,6 +260,7 @@
   }
 
  private:
+  bool is_chrome_terminating_ = false;
   base::RunLoop session_locked_loop_;
   base::RunLoop termination_loop_;
 };
@@ -566,6 +574,14 @@
         GetChallengeResponseAccountId());
   }
 
+  void Lock() {
+    ScreenLockerTester().Lock();
+    // Mimic the behavior of real-world smart card middleware extensions, which
+    // stop talking to smart cards in-session while at the lock screen.
+    SetSecurityTokenAvailability(/*available_on_login_screen=*/true,
+                                 /*available_in_session=*/false);
+  }
+
   // Configures and installs the user session certificate provider extension.
   void PrepareUserCertificateProviderExtension() {
     user_extension_mixin_.InitWithMockPolicyProvider(profile(),
@@ -574,13 +590,20 @@
         test_certificate_provider_extension_mixin_.ForceInstall(profile()));
   }
 
-  // Makes the user session extension call certificateProvider.setCertificates()
-  // without providing any certificates, thus simulating the removal of a
-  // security token.
-  void SimulateSecurityTokenRemoval() {
+  // Makes the extensions call certificateProvider.setCertificates(). Depending
+  // on the passed flags, extensions will pass empty or non-empty sets of
+  // certificates.
+  void SetSecurityTokenAvailability(bool available_on_login_screen,
+                                    bool available_in_session) {
+    // Configure the sign-in screen extension.
+    ASSERT_TRUE(certificate_provider_extension());
+    certificate_provider_extension()->set_should_provide_certificates(
+        available_on_login_screen);
+    certificate_provider_extension()->TriggerSetCertificates();
+    // Configure the in-session extension.
     ASSERT_TRUE(user_certificate_provider_extension());
     user_certificate_provider_extension()->set_should_provide_certificates(
-        false);
+        available_in_session);
     user_certificate_provider_extension()->TriggerSetCertificates();
   }
 
@@ -605,6 +628,14 @@
     return has_notification;
   }
 
+  bool GetNotificationDisplayedKnownUserFlag() const {
+    return user_manager::KnownUser(g_browser_process->local_state())
+        .FindBoolPath(GetChallengeResponseAccountId(),
+                      login::SecurityTokenSessionController::
+                          kNotificationDisplayedKnownUserKey)
+        .value_or(false);
+  }
+
   Profile* profile() const { return profile_; }
 
   TestCertificateProviderExtension* user_certificate_provider_extension() {
@@ -623,35 +654,39 @@
 // Tests the SecurityTokenSessionBehavior policy with value "LOCK".
 IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest, Lock) {
   Login();
-  profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
-                                   "LOCK");
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOCK");
   PrepareUserCertificateProviderExtension();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/true);
   ChromeSessionObserver chrome_session_observer;
 
-  SimulateSecurityTokenRemoval();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/false);
   chrome_session_observer.WaitForSessionLocked();
 
   EXPECT_TRUE(ProfileHasNotification(
       profile(), "security_token_session_controller_notification"));
-  EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(
-      prefs::kSecurityTokenSessionNotificationDisplayed));
+  EXPECT_TRUE(GetNotificationDisplayedKnownUserFlag());
 }
 
 // Tests the SecurityTokenSessionBehavior policy with value "LOGOUT".
 IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest, PRE_Logout) {
   Login();
   ChromeSessionObserver chrome_session_observer;
-  profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
-                                   "LOGOUT");
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOGOUT");
   PrepareUserCertificateProviderExtension();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/true);
 
   // Removal of the certificate should lead to the end of the current session.
-  SimulateSecurityTokenRemoval();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/false);
   chrome_session_observer.WaitForChromeTerminating();
 
   // Check login screen notification is scheduled.
-  EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(
-      prefs::kSecurityTokenSessionNotificationDisplayed));
+  EXPECT_TRUE(GetNotificationDisplayedKnownUserFlag());
 }
 
 IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest, Logout) {
@@ -661,12 +696,54 @@
                              "security_token_session_controller_notification"));
 }
 
+// Test that entering the Lock Screen doesn't cause the logout if the policy is
+// set to LOGOUT. This is a regression test for crbug.com/1349140.
+IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest,
+                       LockScreenWhileLogoutPolicy) {
+  Login();
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOGOUT");
+  PrepareUserCertificateProviderExtension();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/true);
+  ChromeSessionObserver chrome_session_observer;
+  Lock();
+
+  // We want to check that the user session doesn't get terminated erroneously
+  // here. There's no good way of testing something not to happen if the exact
+  // timing is unknown, so we're doing a best-effort here:
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(chrome_session_observer.is_chrome_terminating());
+  EXPECT_FALSE(GetNotificationDisplayedKnownUserFlag());
+}
+
+// Test that removing the security token while at the Lock Screen causes the
+// logout if the policy is set to LOGOUT.
+IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest, LogoutFromLockScreen) {
+  Login();
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOGOUT");
+  PrepareUserCertificateProviderExtension();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/true);
+  ChromeSessionObserver chrome_session_observer;
+  Lock();
+
+  // Removal of the certificate should lead to the end of the current session.
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/false);
+  chrome_session_observer.WaitForChromeTerminating();
+
+  // Check login screen notification is scheduled.
+  EXPECT_TRUE(GetNotificationDisplayedKnownUserFlag());
+}
+
 // Tests the SecurityTokenSessionNotificationSeconds policy.
 IN_PROC_BROWSER_TEST_P(SecurityTokenSessionBehaviorTest, NotificationSeconds) {
   Login();
-  profile()->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
-                                   "LOCK");
-  profile()->GetPrefs()->SetInteger(
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOCK");
+  g_browser_process->local_state()->SetInteger(
       prefs::kSecurityTokenSessionNotificationSeconds, 1);
   PrepareUserCertificateProviderExtension();
   ChromeSessionObserver chrome_session_observer;
@@ -675,7 +752,8 @@
       views::test::AnyWidgetTestPasskey{},
       "SecurityTokenSessionRestrictionView");
 
-  SimulateSecurityTokenRemoval();
+  SetSecurityTokenAvailability(/*available_on_login_screen=*/false,
+                               /*available_in_session=*/false);
 
   views::Widget* notification = notification_waiter.WaitIfNeededAndGet();
   views::test::WidgetDestroyedWaiter notification_closing_observer(
@@ -743,8 +821,8 @@
   Profile* profile = ProfileHelper::Get()->GetProfileByUser(
       user_manager::UserManager::Get()->GetActiveUser());
   PrepareUserCertificateProviderExtension(profile);
-  profile->GetPrefs()->SetString(prefs::kSecurityTokenSessionBehavior,
-                                 "LOGOUT");
+  g_browser_process->local_state()->SetString(
+      prefs::kSecurityTokenSessionBehavior, "LOGOUT");
 
   // Removal of the certificate should lead to the end of the current session.
   ChromeSessionObserver chrome_session_observer;
diff --git a/chrome/browser/ash/login/security_token_session_controller.cc b/chrome/browser/ash/login/security_token_session_controller.cc
index 7065d215..cf392d1 100644
--- a/chrome/browser/ash/login/security_token_session_controller.cc
+++ b/chrome/browser/ash/login/security_token_session_controller.cc
@@ -36,6 +36,8 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/session_manager/session_manager_types.h"
 #include "components/user_manager/known_user.h"
 #include "components/user_manager/user.h"
 #include "extensions/common/extension_id.h"
@@ -159,29 +161,34 @@
 
 }  // namespace
 
+const char* const
+    SecurityTokenSessionController::kNotificationDisplayedKnownUserKey =
+        "security_token_session_notification_displayed";
+
 SecurityTokenSessionController::SecurityTokenSessionController(
+    bool is_user_profile,
     PrefService* local_state,
-    PrefService* profile_prefs,
-    const user_manager::User* user,
+    const user_manager::User* primary_user,
     chromeos::CertificateProviderService* certificate_provider_service)
-    : local_state_(local_state),
-      profile_prefs_(profile_prefs),
-      user_(user),
-      certificate_provider_service_(certificate_provider_service) {
+    : is_user_profile_(is_user_profile),
+      local_state_(local_state),
+      primary_user_(primary_user),
+      certificate_provider_service_(certificate_provider_service),
+      session_manager_(session_manager::SessionManager::Get()) {
   DCHECK(local_state_);
-  DCHECK(profile_prefs_);
-  DCHECK(user_);
+  DCHECK(primary_user_);
   DCHECK(certificate_provider_service_);
+  session_manager_observation_.Observe(session_manager_);
   certificate_provider_ =
       certificate_provider_service_->CreateCertificateProvider();
   LoadStoredChallengeResponseSpkiKeysForUser(
-      local_state_, user_->GetAccountId(), &extension_to_spkis_,
+      local_state_, primary_user_->GetAccountId(), &extension_to_spkis_,
       &observed_extensions_);
   UpdateNotificationPref();
-  behavior_ = GetBehaviorFromPref();
-  pref_change_registrar_.Init(profile_prefs_);
+  behavior_ = GetBehaviorFromPrefAndSessionState();
+  pref_change_registrar_.Init(local_state_);
   base::RepeatingClosure behavior_pref_changed_callback =
-      base::BindRepeating(&SecurityTokenSessionController::UpdateBehaviorPref,
+      base::BindRepeating(&SecurityTokenSessionController::UpdateBehavior,
                           weak_ptr_factory_.GetWeakPtr());
   base::RepeatingClosure notification_pref_changed_callback =
       base::BindRepeating(
@@ -206,7 +213,7 @@
   extension_to_spkis_.clear();
   observed_extensions_.clear();
   LoadStoredChallengeResponseSpkiKeysForUser(
-      local_state_, user_->GetAccountId(), &extension_to_spkis_,
+      local_state_, primary_user_->GetAccountId(), &extension_to_spkis_,
       &observed_extensions_);
 }
 
@@ -247,22 +254,33 @@
   }
 }
 
-// static
-void SecurityTokenSessionController::RegisterLocalStatePrefs(
-    PrefRegistrySimple* registry) {
-  registry->RegisterStringPref(
-      prefs::kSecurityTokenSessionNotificationScheduledDomain, "");
+void SecurityTokenSessionController::OnSessionStateChanged() {
+  if (session_manager_->session_state() ==
+      session_manager::SessionState::LOCKED) {
+    had_lock_screen_transition_ = true;
+  }
+
+  // Reset the flag, so that after the certificates are collected from all
+  // extensions we know whether the absence of some should be tolerated.
+  all_required_certificates_were_observed_ = false;
+
+  UpdateBehavior();
 }
 
 // static
-void SecurityTokenSessionController::RegisterProfilePrefs(
+void SecurityTokenSessionController::RegisterLocalStatePrefs(
     PrefRegistrySimple* registry) {
+  // Prefs that contain policy values. We use the Local State for these, so that
+  // the values are available for the controller regardless of the profile it's
+  // attached to (the policy stack has code to automatically copy the primary
+  // profile's policies into the Local State).
   registry->RegisterStringPref(prefs::kSecurityTokenSessionBehavior,
                                kIgnorePrefValue);
   registry->RegisterIntegerPref(prefs::kSecurityTokenSessionNotificationSeconds,
                                 0);
-  registry->RegisterBooleanPref(
-      prefs::kSecurityTokenSessionNotificationDisplayed, false);
+  // Prefs that contain state that needs to be persisted across Chrome restarts.
+  registry->RegisterStringPref(
+      prefs::kSecurityTokenSessionNotificationScheduledDomain, "");
 }
 
 // static
@@ -294,9 +312,9 @@
                                  base::UTF8ToUTF16(sanitized_domain)));
 }
 
-void SecurityTokenSessionController::UpdateBehaviorPref() {
+void SecurityTokenSessionController::UpdateBehavior() {
   Behavior previous_behavior = behavior_;
-  behavior_ = GetBehaviorFromPref();
+  behavior_ = GetBehaviorFromPrefAndSessionState();
   if (behavior_ == Behavior::kIgnore) {
     Reset();
   } else if (previous_behavior == Behavior::kIgnore) {
@@ -307,14 +325,49 @@
 }
 
 void SecurityTokenSessionController::UpdateNotificationPref() {
-  notification_seconds_ = base::Seconds(profile_prefs_->GetInteger(
+  notification_seconds_ = base::Seconds(local_state_->GetInteger(
       prefs::kSecurityTokenSessionNotificationSeconds));
 }
 
+bool SecurityTokenSessionController::ShouldApplyPolicyInCurrentSessionState()
+    const {
+  switch (session_manager_->session_state()) {
+    case session_manager::SessionState::UNKNOWN:
+    case session_manager::SessionState::OOBE:
+    case session_manager::SessionState::LOGIN_PRIMARY:
+    case session_manager::SessionState::LOGGED_IN_NOT_ACTIVE:
+    case session_manager::SessionState::LOGIN_SECONDARY:
+    case session_manager::SessionState::RMA:
+      return false;
+    case session_manager::SessionState::ACTIVE:
+      if (!is_user_profile_) {
+        // Inside the user session, only the controller that's tied to the user
+        // profile should work.
+        return false;
+      }
+      return true;
+    case session_manager::SessionState::LOCKED:
+      if (is_user_profile_) {
+        // On the lock screen, only the controller that's tied to the sign-in
+        // profile should work.
+        return false;
+      }
+      return true;
+  }
+  NOTREACHED();
+  return false;
+}
+
 SecurityTokenSessionController::Behavior
-SecurityTokenSessionController::GetBehaviorFromPref() const {
+SecurityTokenSessionController::GetBehaviorFromPrefAndSessionState() const {
+  // First determine if we're in a session state in which our instance should do
+  // nothing (ignore the policy).
+  if (!ShouldApplyPolicyInCurrentSessionState())
+    return Behavior::kIgnore;
+  // After passing the session state checks, use the policy value as the desired
+  // behavior.
   return ParseBehaviorPrefValue(
-      profile_prefs_->GetString(prefs::kSecurityTokenSessionBehavior));
+      local_state_->GetString(prefs::kSecurityTokenSessionBehavior));
 }
 
 void SecurityTokenSessionController::TriggerAction() {
@@ -341,17 +394,30 @@
 void SecurityTokenSessionController::ExtensionProvidesAllRequiredCertificates(
     const extensions::ExtensionId& extension_id) {
   extensions_missing_required_certificates_.erase(extension_id);
-  if (extensions_missing_required_certificates_.empty())
+  if (extensions_missing_required_certificates_.empty()) {
+    all_required_certificates_were_observed_ = true;
     Reset();
+  }
 }
 
 void SecurityTokenSessionController::ExtensionStopsProvidingCertificate(
     const extensions::ExtensionId& extension_id) {
   extensions_missing_required_certificates_.insert(extension_id);
 
-  if (fullscreen_notification_)
+  if (!all_required_certificates_were_observed_ &&
+      had_lock_screen_transition_) {
+    // When transitioning to/from the Lock Screen, we delay applying the policy
+    // until we saw the full list of the required certificates at least once.
+    // This is needed because the extensions report a spuriously empty list of
+    // certificates shortly after such session state transition, due to the USB
+    // access conflicts between two profiles.
+    return;
+  }
+
+  if (fullscreen_notification_) {
     // There was already a security token missing.
     return;
+  }
 
   // Schedule session lock / logout.
   action_timer_.Start(
@@ -367,24 +433,21 @@
                            weak_ptr_factory_.GetWeakPtr()),
             behavior_,
             chrome::enterprise_util::GetDomainFromEmail(
-                user_->GetDisplayEmail())),
+                primary_user_->GetDisplayEmail())),
         nullptr, nullptr);
     fullscreen_notification_->Show();
   }
 }
 
-void SecurityTokenSessionController::AddLockNotification() const {
+void SecurityTokenSessionController::AddLockNotification() {
   // A user should see the notification only the first time their session is
   // locked.
-  if (profile_prefs_->GetBoolean(
-          prefs::kSecurityTokenSessionNotificationDisplayed)) {
+  if (GetNotificationDisplayedKnownUserFlag())
     return;
-  }
-  profile_prefs_->SetBoolean(prefs::kSecurityTokenSessionNotificationDisplayed,
-                             true);
+  SetNotificationDisplayedKnownUserFlag();
 
-  std::string domain =
-      chrome::enterprise_util::GetDomainFromEmail(user_->GetDisplayEmail());
+  std::string domain = chrome::enterprise_util::GetDomainFromEmail(
+      primary_user_->GetDisplayEmail());
   DisplayNotification(
       l10n_util::GetStringFUTF16(IDS_SECURITY_TOKEN_SESSION_LOCK_MESSAGE_TITLE,
                                  ui::GetChromeOSDeviceName()),
@@ -392,21 +455,18 @@
                                  base::UTF8ToUTF16(domain)));
 }
 
-void SecurityTokenSessionController::ScheduleLogoutNotification() const {
+void SecurityTokenSessionController::ScheduleLogoutNotification() {
   // The notification can not be created directly, since it will not persist
   // after the session is ended. Instead, use local state to schedule the
   // creation of a notification.
-  if (profile_prefs_->GetBoolean(
-          prefs::kSecurityTokenSessionNotificationDisplayed)) {
-    // A user should see the notification only the first time they are logged
-    // out.
+  if (GetNotificationDisplayedKnownUserFlag())
     return;
-  }
-  profile_prefs_->SetBoolean(prefs::kSecurityTokenSessionNotificationDisplayed,
-                             true);
+  SetNotificationDisplayedKnownUserFlag();
+
   local_state_->SetString(
       prefs::kSecurityTokenSessionNotificationScheduledDomain,
-      chrome::enterprise_util::GetDomainFromEmail(user_->GetDisplayEmail()));
+      chrome::enterprise_util::GetDomainFromEmail(
+          primary_user_->GetDisplayEmail()));
 }
 
 void SecurityTokenSessionController::Reset() {
@@ -421,5 +481,23 @@
   }
 }
 
+bool SecurityTokenSessionController::GetNotificationDisplayedKnownUserFlag()
+    const {
+  return user_manager::KnownUser(local_state_)
+      .FindBoolPath(primary_user_->GetAccountId(),
+                    kNotificationDisplayedKnownUserKey)
+      .value_or(false);
+}
+
+void SecurityTokenSessionController::SetNotificationDisplayedKnownUserFlag() {
+  // The reason we use `KnownUser` (i.e., the Local State) here is because the
+  // flag needs to be readable/writable from the instance of our class that's
+  // tied to the sign-in profile. There's no direct/safe way to access a
+  // profile's pref service from a keyed service tied to a different profile.
+  user_manager::KnownUser(local_state_)
+      .SetBooleanPref(primary_user_->GetAccountId(),
+                      kNotificationDisplayedKnownUserKey, true);
+}
+
 }  // namespace login
 }  // namespace ash
diff --git a/chrome/browser/ash/login/security_token_session_controller.h b/chrome/browser/ash/login/security_token_session_controller.h
index b6e89714..d68a72bc 100644
--- a/chrome/browser/ash/login/security_token_session_controller.h
+++ b/chrome/browser/ash/login/security_token_session_controller.h
@@ -11,6 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/certificate_provider/certificate_provider_service.h"
@@ -18,6 +19,8 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/session_manager/core/session_manager_observer.h"
 #include "components/user_manager/user.h"
 #include "extensions/common/extension_id.h"
 
@@ -39,14 +42,18 @@
 // is getting informed what is going to happen when the certificate vanishes.
 class SecurityTokenSessionController
     : public KeyedService,
-      public chromeos::CertificateProviderService::Observer {
+      public chromeos::CertificateProviderService::Observer,
+      public session_manager::SessionManagerObserver {
  public:
   enum class Behavior { kIgnore, kLogout, kLock };
+  // A key in the known_user database that stores the boolean flag: whether the
+  // notification has been shown or not.
+  static const char* const kNotificationDisplayedKnownUserKey;
 
   SecurityTokenSessionController(
+      bool is_user_profile,
       PrefService* local_state,
-      PrefService* profile_prefs,
-      const user_manager::User* user,
+      const user_manager::User* primary_user,
       chromeos::CertificateProviderService* certificate_provider_service);
   SecurityTokenSessionController(const SecurityTokenSessionController& other) =
       delete;
@@ -58,7 +65,6 @@
   void Shutdown() override;
 
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
-  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
   // If this controller logged the user out just before, display a notification
   // explaining why this happened. This is only done the first time this
@@ -71,15 +77,19 @@
   // providing certificates are not yet initialized.
   void OnChallengeResponseKeysUpdated();
 
-  // CertificateProviderService::Observer
+  // CertificateProviderService::Observer:
   void OnCertificatesUpdated(
       const std::string& extension_id,
       const std::vector<chromeos::certificate_provider::CertificateInfo>&
           certificate_infos) override;
 
+  // session_manager::SessionManagerObserver:
+  void OnSessionStateChanged() override;
+
  private:
-  Behavior GetBehaviorFromPref() const;
-  void UpdateBehaviorPref();
+  bool ShouldApplyPolicyInCurrentSessionState() const;
+  Behavior GetBehaviorFromPrefAndSessionState() const;
+  void UpdateBehavior();
   void UpdateNotificationPref();
 
   void ExtensionProvidesAllRequiredCertificates(
@@ -87,13 +97,21 @@
   void ExtensionStopsProvidingCertificate(
       const extensions::ExtensionId& extension_id);
   void TriggerAction();
-  void AddLockNotification() const;
-  void ScheduleLogoutNotification() const;
+  void AddLockNotification();
+  void ScheduleLogoutNotification();
   void Reset();
 
+  bool GetNotificationDisplayedKnownUserFlag() const;
+  void SetNotificationDisplayedKnownUserFlag();
+
+  const bool is_user_profile_;
   PrefService* const local_state_;
-  PrefService* const profile_prefs_;
-  const user_manager::User* const user_;
+  const user_manager::User* const primary_user_;
+  chromeos::CertificateProviderService* certificate_provider_service_ = nullptr;
+  session_manager::SessionManager* const session_manager_;
+  base::ScopedObservation<session_manager::SessionManager,
+                          session_manager::SessionManagerObserver>
+      session_manager_observation_{this};
   PrefChangeRegistrar pref_change_registrar_;
   Behavior behavior_ = Behavior::kIgnore;
   base::TimeDelta notification_seconds_;
@@ -104,8 +122,13 @@
       extensions_missing_required_certificates_;
   views::Widget* fullscreen_notification_ = nullptr;
   base::OneShotTimer action_timer_;
-  chromeos::CertificateProviderService* certificate_provider_service_ = nullptr;
   std::unique_ptr<chromeos::CertificateProvider> certificate_provider_;
+  // Whether all of the user's certificates have been provided at least once by
+  // the extensions. This field is reset every time the session state changes.
+  bool all_required_certificates_were_observed_ = false;
+  // Whether the session state has transitioned into the `LOCKED` session state
+  // at least once.
+  bool had_lock_screen_transition_ = false;
 
   base::WeakPtrFactory<SecurityTokenSessionController> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/login/security_token_session_controller_factory.cc b/chrome/browser/ash/login/security_token_session_controller_factory.cc
index 362aa54..d4d12c1e 100644
--- a/chrome/browser/ash/login/security_token_session_controller_factory.cc
+++ b/chrome/browser/ash/login/security_token_session_controller_factory.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/login/security_token_session_controller_factory.h"
 
+#include "base/check_is_test.h"
 #include "chrome/browser/ash/login/challenge_response_auth_keys_loader.h"
 #include "chrome/browser/ash/login/security_token_session_controller.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -43,24 +44,33 @@
 
 KeyedService* SecurityTokenSessionControllerFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  // The service should only exist for the primary profile.
+  // The service should only exist for the primary and the sign-in profiles.
   Profile* profile = Profile::FromBrowserContext(context);
-  if (!ProfileHelper::IsPrimaryProfile(profile))
+  if (!profile)
+    return nullptr;
+  const bool is_primary_profile = ProfileHelper::IsPrimaryProfile(profile);
+  const bool is_signin_profile = ProfileHelper::IsSigninProfile(profile);
+  if (!is_primary_profile && !is_signin_profile)
     return nullptr;
 
   PrefService* local_state = g_browser_process->local_state();
   if (!local_state) {
     // This can happen in tests that do not have local state.
+    CHECK_IS_TEST();
     return nullptr;
   }
 
-  user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(
-      Profile::FromBrowserContext(context));
+  auto* const user_manager = user_manager::UserManager::Get();
+  DCHECK(user_manager);
+  const user_manager::User* primary_user = user_manager->GetPrimaryUser();
+  DCHECK(primary_user);
+
   chromeos::CertificateProviderService* certificate_provider_service =
       chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
           context);
-  return new SecurityTokenSessionController(local_state, profile->GetPrefs(),
-                                            user, certificate_provider_service);
+  return new SecurityTokenSessionController(is_primary_profile, local_state,
+                                            primary_user,
+                                            certificate_provider_service);
 }
 
 bool SecurityTokenSessionControllerFactory::ServiceIsCreatedWithBrowserContext()
diff --git a/chrome/browser/ash/login/session/user_session_manager.cc b/chrome/browser/ash/login/session/user_session_manager.cc
index 2ccd617d..de08e50 100644
--- a/chrome/browser/ash/login/session/user_session_manager.cc
+++ b/chrome/browser/ash/login/session/user_session_manager.cc
@@ -1694,6 +1694,9 @@
       login::SecurityTokenSessionControllerFactory::GetForBrowserContext(
           profile)
           ->OnChallengeResponseKeysUpdated();
+      login::SecurityTokenSessionControllerFactory::GetForBrowserContext(
+          ProfileHelper::GetSigninProfile())
+          ->OnChallengeResponseKeysUpdated();
     }
 
     if (user_context_.GetSyncTrustedVaultKeys().has_value()) {
diff --git a/chrome/browser/ash/mobile/mobile_activator.h b/chrome/browser/ash/mobile/mobile_activator.h
index 0f170df..83ffe7c 100644
--- a/chrome/browser/ash/mobile/mobile_activator.h
+++ b/chrome/browser/ash/mobile/mobile_activator.h
@@ -255,8 +255,7 @@
   // Cellular plan payment time.
   base::Time cellular_plan_payment_time_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   base::ObserverList<Observer>::Unchecked observers_;
diff --git a/chrome/browser/ash/net/network_portal_detector_impl.h b/chrome/browser/ash/net/network_portal_detector_impl.h
index 0094c185..8286877 100644
--- a/chrome/browser/ash/net/network_portal_detector_impl.h
+++ b/chrome/browser/ash/net/network_portal_detector_impl.h
@@ -229,8 +229,7 @@
 
   content::NotificationRegistrar registrar_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   // Test time ticks used by unit tests.
diff --git a/chrome/browser/ash/net/rollback_network_config/rollback_network_config.cc b/chrome/browser/ash/net/rollback_network_config/rollback_network_config.cc
index d972807..bc589d0 100644
--- a/chrome/browser/ash/net/rollback_network_config/rollback_network_config.cc
+++ b/chrome/browser/ash/net/rollback_network_config/rollback_network_config.cc
@@ -349,17 +349,16 @@
 
 RollbackNetworkConfig::Importer::Importer() {
   DeviceSettingsService::Get()->AddObserver(this);
-  chromeos::NetworkHandler::Get()
-      ->managed_network_configuration_handler()
-      ->AddObserver(this);
+  NetworkHandler::Get()->managed_network_configuration_handler()->AddObserver(
+      this);
 }
 
 RollbackNetworkConfig::Importer::~Importer() {
   if (DeviceSettingsService::Get()) {
     DeviceSettingsService::Get()->RemoveObserver(this);
   }
-  if (chromeos::NetworkHandler::Get()) {
-    chromeos::NetworkHandler::Get()
+  if (NetworkHandler::Get()) {
+    NetworkHandler::Get()
         ->managed_network_configuration_handler()
         ->RemoveObserver(this);
   }
diff --git a/chrome/browser/ash/net/rollback_network_config/rollback_network_config_unittest.cc b/chrome/browser/ash/net/rollback_network_config/rollback_network_config_unittest.cc
index 64229d73b..e3448f1 100644
--- a/chrome/browser/ash/net/rollback_network_config/rollback_network_config_unittest.cc
+++ b/chrome/browser/ash/net/rollback_network_config/rollback_network_config_unittest.cc
@@ -162,21 +162,20 @@
   FAIL();
 }
 
-chromeos::NetworkStateHandler* network_state_handler() {
-  return chromeos::NetworkHandler::Get()->network_state_handler();
+NetworkStateHandler* network_state_handler() {
+  return NetworkHandler::Get()->network_state_handler();
 }
 
 ash::ManagedNetworkConfigurationHandler*
 managed_network_configuration_handler() {
-  return chromeos::NetworkHandler::Get()
-      ->managed_network_configuration_handler();
+  return NetworkHandler::Get()->managed_network_configuration_handler();
 }
 
 ShillServiceClient* shill_service_client() {
   return ShillServiceClient::Get();
 }
 
-const ash::NetworkState* GetNetworkState(const std::string& guid) {
+const NetworkState* GetNetworkState(const std::string& guid) {
   return network_state_handler()->GetNetworkStateFromGuid(guid);
 }
 
diff --git a/chrome/browser/ash/net/secure_dns_manager.cc b/chrome/browser/ash/net/secure_dns_manager.cc
index 0eb0424..9da2b27 100644
--- a/chrome/browser/ash/net/secure_dns_manager.cc
+++ b/chrome/browser/ash/net/secure_dns_manager.cc
@@ -94,9 +94,8 @@
       registrar_.prefs()->GetString(prefs::kDnsOverHttpsMode),
       registrar_.prefs()->GetString(prefs::kDnsOverHttpsTemplates));
 
-  chromeos::NetworkHandler::Get()
-      ->network_configuration_handler()
-      ->SetManagerProperty(shill::kDNSProxyDOHProvidersProperty, doh_providers);
+  NetworkHandler::Get()->network_configuration_handler()->SetManagerProperty(
+      shill::kDNSProxyDOHProvidersProperty, doh_providers);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/net/system_proxy_manager.h b/chrome/browser/ash/net/system_proxy_manager.h
index 797a0f9..d34618a 100644
--- a/chrome/browser/ash/net/system_proxy_manager.h
+++ b/chrome/browser/ash/net/system_proxy_manager.h
@@ -277,8 +277,7 @@
   std::unique_ptr<PrefChangeRegistrar> local_state_pref_change_registrar_;
   std::unique_ptr<PrefChangeRegistrar> profile_pref_change_registrar_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   base::RepeatingClosure send_auth_details_closure_for_test_;
diff --git a/chrome/browser/ash/network_change_manager_client.h b/chrome/browser/ash/network_change_manager_client.h
index 2cadcf3..5730bf0 100644
--- a/chrome/browser/ash/network_change_manager_client.h
+++ b/chrome/browser/ash/network_change_manager_client.h
@@ -114,8 +114,7 @@
   // Service path for the current default network.
   std::string service_path_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
-                          NetworkStateHandlerObserver>
+  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
   net::NetworkChangeNotifierPosix* network_change_notifier_;
diff --git a/chrome/browser/ash/note_taking_helper_unittest.cc b/chrome/browser/ash/note_taking_helper_unittest.cc
index faa98536..ffcfad0 100644
--- a/chrome/browser/ash/note_taking_helper_unittest.cc
+++ b/chrome/browser/ash/note_taking_helper_unittest.cc
@@ -27,6 +27,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -37,6 +38,7 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_test.h"
@@ -173,6 +175,9 @@
   void SetUp() override {
     SessionManagerClient::InitializeFakeInMemory();
     FakeSessionManagerClient::Get()->set_arc_available(true);
+    // `media_router::kMediaRouter` is disabled because it has unmet
+    // dependencies and is unrelated to this unit test.
+    feature_list_.InitAndDisableFeature(media_router::kMediaRouter);
 
     BrowserWithTestWindowTest::SetUp();
     InitExtensionService(profile());
@@ -486,6 +491,7 @@
 
   ArcAppTest arc_test_;
   std::unique_ptr<arc::FakeIntentHelperHost> intent_helper_host_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(NoteTakingHelperTest, PaletteNotEnabled) {
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
index 0d66e62..707cf01 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
@@ -251,9 +251,8 @@
   device_network_configuration_updater_ =
       DeviceNetworkConfigurationUpdaterAsh::CreateForDevicePolicy(
           GetPolicyService(),
-          chromeos::NetworkHandler::Get()
-              ->managed_network_configuration_handler(),
-          chromeos::NetworkHandler::Get()->network_device_handler(),
+          ash::NetworkHandler::Get()->managed_network_configuration_handler(),
+          ash::NetworkHandler::Get()->network_device_handler(),
           ash::CrosSettings::Get(),
           DeviceNetworkConfigurationUpdaterAsh::DeviceAssetIDFetcher());
   // NetworkCertLoader may be not initialized in tests.
@@ -279,7 +278,7 @@
   device_dock_mac_address_source_handler_ =
       std::make_unique<DeviceDockMacAddressHandler>(
           ash::CrosSettings::Get(),
-          chromeos::NetworkHandler::Get()->network_device_handler());
+          ash::NetworkHandler::Get()->network_device_handler());
 
   device_wifi_allowed_handler_ =
       std::make_unique<DeviceWiFiAllowedHandler>(ash::CrosSettings::Get());
@@ -291,7 +290,7 @@
   device_scheduled_update_checker_ =
       std::make_unique<DeviceScheduledUpdateChecker>(
           ash::CrosSettings::Get(),
-          chromeos::NetworkHandler::Get()->network_state_handler(),
+          ash::NetworkHandler::Get()->network_state_handler(),
           std::make_unique<ScheduledTaskExecutorImpl>(
               update_checker_internal::kUpdateCheckTimerTag));
 
diff --git a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.cc b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.cc
index 817ba005..900c67e 100644
--- a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.cc
+++ b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.cc
@@ -39,12 +39,12 @@
     : DeviceNamePolicyHandlerImpl(
           cros_settings,
           chromeos::system::StatisticsProvider::GetInstance(),
-          chromeos::NetworkHandler::Get()->network_state_handler()) {}
+          ash::NetworkHandler::Get()->network_state_handler()) {}
 
 DeviceNamePolicyHandlerImpl::DeviceNamePolicyHandlerImpl(
     ash::CrosSettings* cros_settings,
     chromeos::system::StatisticsProvider* statistics_provider,
-    chromeos::NetworkStateHandler* handler)
+    ash::NetworkStateHandler* handler)
     : cros_settings_(cros_settings),
       statistics_provider_(statistics_provider),
       handler_(handler),
@@ -61,7 +61,7 @@
           weak_factory_.GetWeakPtr()));
 
   network_state_handler_observer_.Observe(
-      chromeos::NetworkHandler::Get()->network_state_handler());
+      ash::NetworkHandler::Get()->network_state_handler());
 
   // Fire it once so we're sure we get an invocation on startup.
   OnDeviceHostnamePropertyChanged();
diff --git a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.h b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.h
index a8e1e418..0d93b20 100644
--- a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.h
+++ b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl.h
@@ -45,7 +45,7 @@
   DeviceNamePolicyHandlerImpl(
       ash::CrosSettings* cros_settings,
       chromeos::system::StatisticsProvider* statistics_provider,
-      chromeos::NetworkStateHandler* handler);
+      ash::NetworkStateHandler* handler);
 
   // NetworkStateHandlerObserver overrides
   void DefaultNetworkChanged(const ash::NetworkState* network) override;
@@ -71,8 +71,8 @@
 
   ash::CrosSettings* cros_settings_;
   chromeos::system::StatisticsProvider* statistics_provider_;
-  chromeos::NetworkStateHandler* handler_;
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  ash::NetworkStateHandler* handler_;
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl_unittest.cc b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl_unittest.cc
index 61428de..46d36fb 100644
--- a/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/device_name_policy_handler_impl_unittest.cc
@@ -99,7 +99,7 @@
 
     handler_ = base::WrapUnique(new DeviceNamePolicyHandlerImpl(
         ash::CrosSettings::Get(), &fake_statistics_provider_,
-        chromeos::NetworkHandler::Get()->network_state_handler()));
+        ash::NetworkHandler::Get()->network_state_handler()));
     handler_->AddObserver(&fake_observer_);
     base::RunLoop().RunUntilIdle();
   }
diff --git a/chrome/browser/ash/policy/handlers/device_wifi_allowed_handler.cc b/chrome/browser/ash/policy/handlers/device_wifi_allowed_handler.cc
index 0faf3e21..4f04745 100644
--- a/chrome/browser/ash/policy/handlers/device_wifi_allowed_handler.cc
+++ b/chrome/browser/ash/policy/handlers/device_wifi_allowed_handler.cc
@@ -40,11 +40,11 @@
   bool wifi_allowed = true;
   cros_settings_->GetBoolean(ash::kDeviceWiFiAllowed, &wifi_allowed);
   if (!wifi_allowed) {
-    chromeos::NetworkHandler::Get()
+    ash::NetworkHandler::Get()
         ->prohibited_technologies_handler()
         ->AddGloballyProhibitedTechnology(shill::kTypeWifi);
   } else {
-    chromeos::NetworkHandler::Get()
+    ash::NetworkHandler::Get()
         ->prohibited_technologies_handler()
         ->RemoveGloballyProhibitedTechnology(shill::kTypeWifi);
   }
diff --git a/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.cc b/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.cc
index e818e61f..4d8f7aa 100644
--- a/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.cc
+++ b/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.cc
@@ -51,8 +51,8 @@
 }
 
 MinimumVersionPolicyHandler::NetworkStatus GetCurrentNetworkStatus() {
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   const ash::NetworkState* current_network =
       network_state_handler->DefaultNetwork();
   if (!current_network || !current_network->IsConnectedState())
@@ -514,8 +514,8 @@
                               std::move(close_callback));
 
   if (!eol_reached_) {
-    chromeos::NetworkStateHandler* network_state_handler =
-        chromeos::NetworkHandler::Get()->network_state_handler();
+    ash::NetworkStateHandler* network_state_handler =
+        ash::NetworkHandler::Get()->network_state_handler();
     if (!network_state_handler->HasObserver(this))
       network_state_handler_observer_.Observe(network_state_handler);
   }
diff --git a/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.h b/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.h
index 37208285..1bc420e 100644
--- a/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.h
+++ b/chrome/browser/ash/policy/handlers/minimum_version_policy_handler.h
@@ -313,7 +313,7 @@
   // current network and time to reach the deadline.
   std::unique_ptr<ash::UpdateRequiredNotification> notification_handler_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc b/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
index 686fcda..1296713 100644
--- a/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
@@ -62,8 +62,8 @@
 
     system_proxy_handler_->SetSystemProxyManagerForTesting(
         system_proxy_manager_.get());
-    chromeos::NetworkHandler::Get()->InitializePrefServices(
-        profile_->GetPrefs(), local_state_.Get());
+    ash::NetworkHandler::Get()->InitializePrefServices(profile_->GetPrefs(),
+                                                       local_state_.Get());
   }
 
   void TearDown() override {
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
index 754f81c..0592968 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader.cc
@@ -84,7 +84,7 @@
       local_state_(local_state),
       is_device_managed_callback_(std::move(is_device_active_callback)),
       retry_entry_(&kBackOffPolicy) {
-  if (!chromeos::NetworkHandler::IsInitialized()) {
+  if (!ash::NetworkHandler::IsInitialized()) {
     LOG(WARNING) << "NetworkHandler is not initialized.";
     return;
   }
@@ -93,7 +93,7 @@
   hermes_euicc_observation_.Observe(ash::HermesEuiccClient::Get());
   cloud_policy_client_observation_.Observe(client_);
 
-  auto* network_handler = chromeos::NetworkHandler::Get();
+  auto* network_handler = ash::NetworkHandler::Get();
   network_handler->managed_cellular_pref_handler()->AddObserver(this);
   managed_network_configuration_handler_ =
       network_handler->managed_network_configuration_handler();
@@ -101,10 +101,9 @@
 }
 
 EuiccStatusUploader::~EuiccStatusUploader() {
-  if (chromeos::NetworkHandler::IsInitialized())
-    chromeos::NetworkHandler::Get()
-        ->managed_cellular_pref_handler()
-        ->RemoveObserver(this);
+  if (ash::NetworkHandler::IsInitialized())
+    ash::NetworkHandler::Get()->managed_cellular_pref_handler()->RemoveObserver(
+        this);
   OnManagedNetworkConfigurationHandlerShuttingDown();
 }
 
@@ -191,7 +190,7 @@
 
   base::Value esim_profiles(base::Value::Type::LIST);
 
-  for (const auto& esim_profile : chromeos::NetworkHandler::Get()
+  for (const auto& esim_profile : ash::NetworkHandler::Get()
                                       ->cellular_esim_profile_handler()
                                       ->GetESimProfiles()) {
     // Do not report non-provisioned cellular networks.
@@ -199,7 +198,7 @@
       continue;
 
     const std::string* smdp_address =
-        chromeos::NetworkHandler::Get()
+        ash::NetworkHandler::Get()
             ->managed_cellular_pref_handler()
             ->GetSmdpAddressFromIccid(esim_profile.iccid());
     // Report only managed profiles with a SMDP address.
diff --git a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
index 569b26b..e1d1057 100644
--- a/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
+++ b/chrome/browser/ash/policy/networking/euicc_status_uploader_unittest.cc
@@ -224,7 +224,7 @@
               kAddProfileWithService);
 
       if (test_profile.managed) {
-        chromeos::NetworkHandler::Get()
+        ash::NetworkHandler::Get()
             ->managed_cellular_pref_handler()
             ->AddIccidSmdpPair(test_profile.iccid, test_profile.smdp_address);
       }
@@ -472,14 +472,14 @@
 
   // NetworkHandler::Shutdown() has already been called before
   // EuiccStatusUploader is deleted
-  chromeos::NetworkHandler::Shutdown();
+  ash::NetworkHandler::Shutdown();
 
   // No requests made as NetworkHandler is not available.
   UpdateUploader(status_uploader.get());
   EXPECT_EQ(GetRequestCount(), 2);
 
   // Need to reinitialize before exiting test.
-  chromeos::NetworkHandler::Initialize();
+  ash::NetworkHandler::Initialize();
 }
 
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
index 2a7565b74..fcd6854 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_reset_euicc_job.cc
@@ -77,7 +77,7 @@
 
   SYSLOG(INFO) << "Executing EUICC reset memory remote command";
   ash::CellularESimUninstallHandler* uninstall_handler =
-      chromeos::NetworkHandler::Get()->cellular_esim_uninstall_handler();
+      ash::NetworkHandler::Get()->cellular_esim_uninstall_handler();
   uninstall_handler->ResetEuiccMemory(
       *euicc_path,
       base::BindOnce(
diff --git a/chrome/browser/ash/policy/reporting/install_event_log_collector_base.cc b/chrome/browser/ash/policy/reporting/install_event_log_collector_base.cc
index 7606870..1a1a364 100644
--- a/chrome/browser/ash/policy/reporting/install_event_log_collector_base.cc
+++ b/chrome/browser/ash/policy/reporting/install_event_log_collector_base.cc
@@ -30,12 +30,10 @@
 }
 
 bool InstallEventLogCollectorBase::GetOnlineState() {
-  chromeos::NetworkStateHandler::NetworkStateList network_state_list;
-  chromeos::NetworkHandler::Get()
-      ->network_state_handler()
-      ->GetNetworkListByType(
-          ash::NetworkTypePattern::Default(), true /* configured_only */,
-          false /* visible_only */, 0 /* limit */, &network_state_list);
+  ash::NetworkStateHandler::NetworkStateList network_state_list;
+  ash::NetworkHandler::Get()->network_state_handler()->GetNetworkListByType(
+      ash::NetworkTypePattern::Default(), true /* configured_only */,
+      false /* visible_only */, 0 /* limit */, &network_state_list);
 
   for (const ash::NetworkState* network_state : network_state_list) {
     if (network_state->connection_state() == shill::kStateOnline) {
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.cc b/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.cc
index 5f8246c..6ba3d7b 100644
--- a/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.cc
+++ b/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.cc
@@ -46,7 +46,7 @@
 // so it's safe to use "this" with any callbacks.
 DeviceScheduledUpdateChecker::DeviceScheduledUpdateChecker(
     ash::CrosSettings* cros_settings,
-    chromeos::NetworkStateHandler* network_state_handler,
+    ash::NetworkStateHandler* network_state_handler,
     std::unique_ptr<ScheduledTaskExecutor> update_check_executor)
     : cros_settings_(cros_settings),
       cros_settings_subscription_(cros_settings_->AddSettingsObserver(
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.h b/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.h
index 32a1963..56feb96 100644
--- a/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.h
+++ b/chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_update_checker.h
@@ -29,7 +29,7 @@
  public:
   DeviceScheduledUpdateChecker(
       ash::CrosSettings* cros_settings,
-      chromeos::NetworkStateHandler* network_state_handler,
+      ash::NetworkStateHandler* network_state_handler,
       std::unique_ptr<ScheduledTaskExecutor> update_check_executor);
 
   DeviceScheduledUpdateChecker(const DeviceScheduledUpdateChecker&) = delete;
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.cc b/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.cc
index d53e6f0..601cfec 100644
--- a/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.cc
+++ b/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.cc
@@ -15,7 +15,7 @@
 namespace policy {
 
 OsAndPoliciesUpdateChecker::OsAndPoliciesUpdateChecker(
-    chromeos::NetworkStateHandler* network_state_handler)
+    ash::NetworkStateHandler* network_state_handler)
     : network_state_handler_(network_state_handler),
       update_check_task_executor_(
           update_checker_internal::
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.h b/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.h
index f86fcd6..a2e0ce9 100644
--- a/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.h
+++ b/chrome/browser/ash/policy/scheduled_task_handler/os_and_policies_update_checker.h
@@ -41,7 +41,7 @@
                                    public ash::NetworkStateHandlerObserver {
  public:
   explicit OsAndPoliciesUpdateChecker(
-      chromeos::NetworkStateHandler* network_state_handler);
+      ash::NetworkStateHandler* network_state_handler);
 
   OsAndPoliciesUpdateChecker(const OsAndPoliciesUpdateChecker&) = delete;
   OsAndPoliciesUpdateChecker& operator=(const OsAndPoliciesUpdateChecker&) =
@@ -116,8 +116,8 @@
   UpdateCheckCompletionCallback update_check_completion_cb_;
 
   // Not owned.
-  chromeos::NetworkStateHandler* const network_state_handler_;
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  ash::NetworkStateHandler* const network_state_handler_;
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/policy/scheduled_task_handler/test/device_scheduled_update_checker_unittest.cc b/chrome/browser/ash/policy/scheduled_task_handler/test/device_scheduled_update_checker_unittest.cc
index 06ab501..771cbd2 100644
--- a/chrome/browser/ash/policy/scheduled_task_handler/test/device_scheduled_update_checker_unittest.cc
+++ b/chrome/browser/ash/policy/scheduled_task_handler/test/device_scheduled_update_checker_unittest.cc
@@ -73,7 +73,7 @@
  public:
   DeviceScheduledUpdateCheckerForTest(
       ash::CrosSettings* cros_settings,
-      chromeos::NetworkStateHandler* network_state_handler,
+      ash::NetworkStateHandler* network_state_handler,
       std::unique_ptr<ScheduledTaskExecutor> task_executor)
       : DeviceScheduledUpdateChecker(cros_settings,
                                      network_state_handler,
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector.cc b/chrome/browser/ash/policy/status_collector/device_status_collector.cc
index 26094b7..4039276 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector.cc
@@ -2316,13 +2316,13 @@
       },
   };
 
-  chromeos::NetworkStateHandler::DeviceStateList device_list;
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler::DeviceStateList device_list;
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   network_state_handler->GetDeviceList(&device_list);
 
   bool anything_reported = false;
-  chromeos::NetworkStateHandler::DeviceStateList::const_iterator device;
+  ash::NetworkStateHandler::DeviceStateList::const_iterator device;
   for (device = device_list.begin(); device != device_list.end(); ++device) {
     // Determine the type enum constant for |device|.
     size_t type_idx = 0;
@@ -2391,8 +2391,8 @@
   };
 
   bool anything_reported = false;
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
 
   user_manager::UserManager* user_manager = user_manager::UserManager::Get();
   const user_manager::User* const primary_user = user_manager->GetPrimaryUser();
@@ -2403,7 +2403,7 @@
   }
 
   // Walk the various networks and store their state in the status report.
-  chromeos::NetworkStateHandler::NetworkStateList state_list;
+  ash::NetworkStateHandler::NetworkStateList state_list;
   network_state_handler->GetNetworkListByType(
       ash::NetworkTypePattern::Default(),
       true,   // configured_only
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
index b2a0842..7395127 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
@@ -3954,9 +3954,9 @@
     // Flush out pending state updates.
     base::RunLoop().RunUntilIdle();
 
-    chromeos::NetworkStateHandler::NetworkStateList state_list;
-    chromeos::NetworkStateHandler* network_state_handler =
-        chromeos::NetworkHandler::Get()->network_state_handler();
+    ash::NetworkStateHandler::NetworkStateList state_list;
+    ash::NetworkStateHandler* network_state_handler =
+        ash::NetworkHandler::Get()->network_state_handler();
     network_state_handler->GetNetworkListByType(
         ash::NetworkTypePattern::Default(),
         true,   // configured_only
diff --git a/chrome/browser/ash/tether/fake_tether_service.cc b/chrome/browser/ash/tether/fake_tether_service.cc
index b441f3dc9..3cbcbfa 100644
--- a/chrome/browser/ash/tether/fake_tether_service.cc
+++ b/chrome/browser/ash/tether/fake_tether_service.cc
@@ -21,7 +21,7 @@
     device_sync::DeviceSyncClient* device_sync_client,
     secure_channel::SecureChannelClient* secure_channel_client,
     multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
-    chromeos::NetworkStateHandler* network_state_handler,
+    NetworkStateHandler* network_state_handler,
     session_manager::SessionManager* session_manager)
     : TetherService(profile,
                     power_manager_client,
@@ -33,7 +33,7 @@
 
 void FakeTetherService::StartTetherIfPossible() {
   if (GetTetherTechnologyState() !=
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
     return;
   }
 
diff --git a/chrome/browser/ash/tether/fake_tether_service.h b/chrome/browser/ash/tether/fake_tether_service.h
index 8abd186..c04b1d0 100644
--- a/chrome/browser/ash/tether/fake_tether_service.h
+++ b/chrome/browser/ash/tether/fake_tether_service.h
@@ -21,7 +21,7 @@
       device_sync::DeviceSyncClient* device_sync_client,
       secure_channel::SecureChannelClient* secure_channel_client,
       multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
-      chromeos::NetworkStateHandler* network_state_handler,
+      NetworkStateHandler* network_state_handler,
       session_manager::SessionManager* session_manager);
   FakeTetherService(const FakeTetherService&) = delete;
   FakeTetherService& operator=(const FakeTetherService&) = delete;
diff --git a/chrome/browser/ash/tether/tether_service.cc b/chrome/browser/ash/tether/tether_service.cc
index b6ff1b2e..11521e7 100644
--- a/chrome/browser/ash/tether/tether_service.cc
+++ b/chrome/browser/ash/tether/tether_service.cc
@@ -96,7 +96,7 @@
     device_sync::DeviceSyncClient* device_sync_client,
     secure_channel::SecureChannelClient* secure_channel_client,
     multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
-    chromeos::NetworkStateHandler* network_state_handler,
+    NetworkStateHandler* network_state_handler,
     session_manager::SessionManager* session_manager)
     : profile_(profile),
       power_manager_client_(power_manager_client),
@@ -141,7 +141,7 @@
 
 void TetherService::StartTetherIfPossible() {
   if (GetTetherTechnologyState() !=
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
     return;
   }
 
@@ -155,9 +155,9 @@
       notification_presenter_.get(),
       gms_core_notifications_state_tracker_.get(), profile_->GetPrefs(),
       network_state_handler_,
-      chromeos::NetworkHandler::Get()->managed_network_configuration_handler(),
+      NetworkHandler::Get()->managed_network_configuration_handler(),
       NetworkConnect::Get(),
-      chromeos::NetworkHandler::Get()->network_connection_handler(), adapter_,
+      NetworkHandler::Get()->network_connection_handler(), adapter_,
       session_manager_);
 }
 
@@ -286,20 +286,20 @@
 
 void TetherService::UpdateEnabledState() {
   bool was_pref_enabled = IsEnabledByPreference();
-  chromeos::NetworkStateHandler::TechnologyState tether_technology_state =
+  NetworkStateHandler::TechnologyState tether_technology_state =
       network_state_handler_->GetTechnologyState(NetworkTypePattern::Tether());
 
   // If |was_pref_enabled| differs from the new Tether TechnologyState, the
   // settings toggle has been changed. Update the kInstantTetheringEnabled user
   // pref accordingly.
   bool is_enabled;
-  if (was_pref_enabled && tether_technology_state ==
-                              chromeos::NetworkStateHandler::TechnologyState::
-                                  TECHNOLOGY_AVAILABLE) {
+  if (was_pref_enabled &&
+      tether_technology_state ==
+          NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE) {
     is_enabled = false;
-  } else if (!was_pref_enabled && tether_technology_state ==
-                                      chromeos::NetworkStateHandler::
-                                          TechnologyState::TECHNOLOGY_ENABLED) {
+  } else if (!was_pref_enabled &&
+             tether_technology_state ==
+                 NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
     is_enabled = true;
   } else {
     is_enabled = was_pref_enabled;
@@ -367,11 +367,11 @@
   if (!adapter_)
     return;
 
-  chromeos::NetworkStateHandler::TechnologyState new_tether_technology_state =
+  NetworkStateHandler::TechnologyState new_tether_technology_state =
       GetTetherTechnologyState();
 
   if (new_tether_technology_state ==
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
+      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED) {
     // If Tether should be enabled, notify NetworkStateHandler before starting
     // up the component. This ensures that it is not possible to add Tether
     // networks before the network stack is ready for them.
@@ -389,8 +389,7 @@
   }
 }
 
-chromeos::NetworkStateHandler::TechnologyState
-TetherService::GetTetherTechnologyState() {
+NetworkStateHandler::TechnologyState TetherService::GetTetherTechnologyState() {
   TetherFeatureState new_feature_state = GetTetherFeatureState();
   if (new_feature_state != previous_feature_state_) {
     PA_LOG(INFO) << "Tether state has changed. New state: "
@@ -410,27 +409,22 @@
     case NO_AVAILABLE_HOSTS:
     case CELLULAR_DISABLED:
     case BETTER_TOGETHER_SUITE_DISABLED:
-      return chromeos::NetworkStateHandler::TechnologyState::
-          TECHNOLOGY_UNAVAILABLE;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE;
 
     case PROHIBITED:
-      return chromeos::NetworkStateHandler::TechnologyState::
-          TECHNOLOGY_PROHIBITED;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED;
 
     case BLUETOOTH_DISABLED:
-      return chromeos::NetworkStateHandler::TechnologyState::
-          TECHNOLOGY_UNINITIALIZED;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED;
 
     case USER_PREFERENCE_DISABLED:
-      return chromeos::NetworkStateHandler::TechnologyState::
-          TECHNOLOGY_AVAILABLE;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE;
 
     case ENABLED:
-      return chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED;
 
     default:
-      return chromeos::NetworkStateHandler::TechnologyState::
-          TECHNOLOGY_UNAVAILABLE;
+      return NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE;
   }
 }
 
diff --git a/chrome/browser/ash/tether/tether_service.h b/chrome/browser/ash/tether/tether_service.h
index 1d00d83..20a6011 100644
--- a/chrome/browser/ash/tether/tether_service.h
+++ b/chrome/browser/ash/tether/tether_service.h
@@ -79,7 +79,7 @@
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
   // Attempt to start the Tether module. Only succeeds if all conditions to
-  // reach chromeos::NetworkStateHandler::TechnologyState::ENABLED are reached.
+  // reach NetworkStateHandler::TechnologyState::ENABLED are reached.
   // Should only be called once a user is logged in.
   virtual void StartTetherIfPossible();
 
diff --git a/chrome/browser/ash/tether/tether_service_factory.cc b/chrome/browser/ash/tether/tether_service_factory.cc
index 1178fdec..b2f37d3 100644
--- a/chrome/browser/ash/tether/tether_service_factory.cc
+++ b/chrome/browser/ash/tether/tether_service_factory.cc
@@ -58,7 +58,7 @@
 
 KeyedService* TetherServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  DCHECK(chromeos::NetworkHandler::IsInitialized());
+  DCHECK(NetworkHandler::IsInitialized());
 
   if (!IsFeatureAllowed(context))
     return nullptr;
@@ -73,7 +73,7 @@
         secure_channel::SecureChannelClientProvider::GetInstance()->GetClient(),
         multidevice_setup::MultiDeviceSetupClientFactory::GetForProfile(
             Profile::FromBrowserContext(context)),
-        chromeos::NetworkHandler::Get()->network_state_handler(),
+        NetworkHandler::Get()->network_state_handler(),
         session_manager::SessionManager::Get());
 
     int num_tether_networks = 0;
@@ -91,7 +91,7 @@
       secure_channel::SecureChannelClientProvider::GetInstance()->GetClient(),
       multidevice_setup::MultiDeviceSetupClientFactory::GetForProfile(
           Profile::FromBrowserContext(context)),
-      chromeos::NetworkHandler::Get()->network_state_handler(),
+      NetworkHandler::Get()->network_state_handler(),
       session_manager::SessionManager::Get());
 }
 
diff --git a/chrome/browser/ash/tether/tether_service_unittest.cc b/chrome/browser/ash/tether/tether_service_unittest.cc
index 11a9101..a8776ce 100644
--- a/chrome/browser/ash/tether/tether_service_unittest.cc
+++ b/chrome/browser/ash/tether/tether_service_unittest.cc
@@ -94,7 +94,7 @@
       device_sync::DeviceSyncClient* device_sync_client,
       secure_channel::SecureChannelClient* secure_channel_client,
       multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
-      chromeos::NetworkStateHandler* network_state_handler,
+      NetworkStateHandler* network_state_handler,
       session_manager::SessionManager* session_manager)
       : TetherService(profile,
                       power_manager_client,
@@ -153,7 +153,7 @@
       GmsCoreNotificationsStateTrackerImpl*
           gms_core_notifications_state_tracker,
       PrefService* pref_service,
-      chromeos::NetworkStateHandler* network_state_handler,
+      NetworkStateHandler* network_state_handler,
       ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
       NetworkConnect* network_connect,
       NetworkConnectionHandler* network_connection_handler,
@@ -425,10 +425,9 @@
 
     // Ensure that TetherService does not prematurely update its
     // TechnologyState before it fetches the BluetoothAdapter.
-    EXPECT_EQ(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-        network_state_handler()->GetTechnologyState(
-            NetworkTypePattern::Tether()));
+    EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+              network_state_handler()->GetTechnologyState(
+                  NetworkTypePattern::Tether()));
     VerifyTetherActiveStatus(false /* expected_active */);
 
     if (!fake_device_sync_client_->is_ready()) {
@@ -446,10 +445,8 @@
 
   void SetTetherTechnologyStateEnabled(bool enabled) {
     network_state_handler()->SetTetherTechnologyState(
-        enabled
-            ? chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED
-            : chromeos::NetworkStateHandler::TechnologyState::
-                  TECHNOLOGY_AVAILABLE);
+        enabled ? NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED
+                : NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE);
   }
 
   void SetCellularTechnologyStateEnabled(bool enabled) {
@@ -510,8 +507,8 @@
     return network_handler_test_helper_.manager_test();
   }
 
-  chromeos::NetworkStateHandler* network_state_handler() {
-    return chromeos::NetworkHandler::Get()->network_state_handler();
+  NetworkStateHandler* network_state_handler() {
+    return NetworkHandler::Get()->network_state_handler();
   }
 
   const multidevice::RemoteDeviceRefList test_devices_;
@@ -568,7 +565,7 @@
   // The TechnologyState should not have changed due to Shutdown() being called.
   // If it had changed, any settings UI that was previously open would have
   // shown visual jank.
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
@@ -580,7 +577,7 @@
   CreateTetherService();
 
   // Tether should be ENABLED, and there should be no AsyncShutdownTask.
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -600,10 +597,9 @@
       test_tether_component_factory_->active_tether_component()->status());
 
   // Tether should be AVAILABLE.
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
 
   // Complete the shutdown process; TetherService should delete its
   // TetherComponent instance.
@@ -620,15 +616,14 @@
   chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
       power_manager::SuspendImminent_Reason_OTHER);
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -650,7 +645,7 @@
   fake_device_sync_client_->NotifyReady();
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -667,10 +662,9 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   fake_tether_host_fetcher_factory_->last_created()->set_tether_hosts(
@@ -679,7 +673,7 @@
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kEnabledByUser);
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -691,7 +685,7 @@
 TEST_F(TetherServiceTest, TestMultiDeviceSetupClientLosesVerifiedHost) {
   CreateTetherService();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -702,10 +696,9 @@
       multidevice_setup::mojom::FeatureState::
           kUnavailableNoVerifiedHost_HostExistsButNotSetAndVerified);
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   mock_timer_->Fire();
@@ -723,17 +716,16 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kEnabledByUser);
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -745,7 +737,7 @@
 TEST_F(TetherServiceTest, TestBetterTogetherSuiteBecomesDisabled) {
   CreateTetherService();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -754,10 +746,9 @@
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kUnavailableSuiteDisabled);
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   ShutdownTetherService();
@@ -850,10 +841,9 @@
   fake_tether_host_fetcher_factory_->SetNoInitialDevices();
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   // Simulate this being the final state of Tether by passing time.
@@ -871,10 +861,9 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   VerifyTetherFeatureStateRecorded(
@@ -886,10 +875,9 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   // Simulate this being the final state of Tether by passing time.
@@ -908,17 +896,16 @@
   set_is_adapter_present(true);
   SetIsBluetoothPowered(true);
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   fake_tether_host_fetcher_factory_->last_created()->set_tether_hosts(
       test_devices_);
   fake_tether_host_fetcher_factory_->last_created()->NotifyTetherHostsUpdated();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -946,10 +933,9 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
 
   VerifyTetherFeatureStateRecorded(
       TetherService::TetherFeatureState::WIFI_NOT_PRESENT,
@@ -961,25 +947,23 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   SetIsBluetoothPowered(true);
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
 
   SetIsBluetoothPowered(false);
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   VerifyTetherFeatureStateRecorded(
@@ -991,23 +975,21 @@
 // TODO(https://crbug.com/893878): Fix disabled test.
 TEST_F(TetherServiceTest, DISABLED_TestCellularIsUnavailable) {
   manager_test()->RemoveTechnology(shill::kTypeCellular);
-  ASSERT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Cellular()));
+  ASSERT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Cellular()));
 
   CreateTetherService();
 
   SetTetherTechnologyStateEnabled(false);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
   VerifyLastShutdownReason(TetherComponent::ShutdownReason::PREF_DISABLED);
 
   SetTetherTechnologyStateEnabled(true);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -1027,42 +1009,38 @@
 
   // Cellular disabled
   SetCellularTechnologyStateEnabled(false);
-  ASSERT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Cellular()));
+  ASSERT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Cellular()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   SetTetherTechnologyStateEnabled(false);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   SetTetherTechnologyStateEnabled(true);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_UNAVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   // Cellular enabled
   SetCellularTechnologyStateEnabled(true);
-  ASSERT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  ASSERT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Cellular()));
   VerifyTetherActiveStatus(true /* expected_active */);
 
   SetTetherTechnologyStateEnabled(false);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
 
   SetTetherTechnologyStateEnabled(true);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -1082,10 +1060,9 @@
 
   CreateTetherService();
 
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   EXPECT_FALSE(profile_->GetPrefs()->GetBoolean(
       multidevice_setup::kInstantTetheringEnabledPrefName));
   VerifyTetherActiveStatus(false /* expected_active */);
@@ -1099,16 +1076,15 @@
 TEST_F(TetherServiceTest, DISABLED_TestEnabled) {
   CreateTetherService();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
 
   SetTetherTechnologyStateEnabled(false);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   EXPECT_FALSE(profile_->GetPrefs()->GetBoolean(
       multidevice_setup::kInstantTetheringEnabledPrefName));
   VerifyTetherActiveStatus(false /* expected_active */);
@@ -1117,7 +1093,7 @@
       1u /* expected_count */);
 
   SetTetherTechnologyStateEnabled(true);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   EXPECT_TRUE(profile_->GetPrefs()->GetBoolean(
@@ -1146,7 +1122,7 @@
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kEnabledByUser);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -1162,10 +1138,9 @@
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kDisabledByUser);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
   VerifyTetherFeatureStateRecorded(
       TetherService::TetherFeatureState::USER_PREFERENCE_DISABLED,
@@ -1180,7 +1155,7 @@
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kEnabledByUser);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -1196,7 +1171,7 @@
 TEST_F(TetherServiceTest, TestUserPrefChangesViaTechnologyStateChange) {
   CreateTetherService();
 
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
@@ -1211,10 +1186,9 @@
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kDisabledByUser);
-  EXPECT_EQ(
-      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
-      network_state_handler()->GetTechnologyState(
-          NetworkTypePattern::Tether()));
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_AVAILABLE,
+            network_state_handler()->GetTechnologyState(
+                NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(false /* expected_active */);
   histogram_tester_.ExpectBucketCount(
       "InstantTethering.UserPreference.OnToggle", false,
@@ -1230,7 +1204,7 @@
   fake_multidevice_setup_client_->SetFeatureState(
       multidevice_setup::mojom::Feature::kInstantTethering,
       multidevice_setup::mojom::FeatureState::kEnabledByUser);
-  EXPECT_EQ(chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
+  EXPECT_EQ(NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED,
             network_state_handler()->GetTechnologyState(
                 NetworkTypePattern::Tether()));
   VerifyTetherActiveStatus(true /* expected_active */);
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 0136943..2bced6c 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -204,6 +204,7 @@
 #include "components/live_caption/caption_util.h"
 #include "components/media_router/browser/presentation/presentation_service_delegate_impl.h"
 #include "components/media_router/browser/presentation/receiver_presentation_service_delegate_impl.h"
+#include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
 #include "components/metrics/client_info.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
 #include "components/net_log/chrome_net_log.h"
@@ -4357,6 +4358,24 @@
   return nullptr;
 }
 
+void ChromeContentBrowserClient::AddPresentationObserver(
+    content::PresentationObserver* observer,
+    content::WebContents* web_contents) {
+  if (media_router::MediaRouterEnabled(web_contents->GetBrowserContext())) {
+    media_router::WebContentsPresentationManager::Get(web_contents)
+        ->AddObserver(observer);
+  }
+}
+
+void ChromeContentBrowserClient::RemovePresentationObserver(
+    content::PresentationObserver* observer,
+    content::WebContents* web_contents) {
+  if (media_router::MediaRouterEnabled(web_contents->GetBrowserContext())) {
+    media_router::WebContentsPresentationManager::Get(web_contents)
+        ->RemoveObserver(observer);
+  }
+}
+
 std::vector<std::unique_ptr<content::NavigationThrottle>>
 ChromeContentBrowserClient::CreateThrottlesForNavigation(
     content::NavigationHandle* handle) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 489dc30..8ad42c8 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -484,6 +484,10 @@
   content::ReceiverPresentationServiceDelegate*
   GetReceiverPresentationServiceDelegate(
       content::WebContents* web_contents) override;
+  void AddPresentationObserver(content::PresentationObserver* observer,
+                               content::WebContents* web_contents) override;
+  void RemovePresentationObserver(content::PresentationObserver* observer,
+                                  content::WebContents* web_contents) override;
   std::vector<std::unique_ptr<content::NavigationThrottle>>
   CreateThrottlesForNavigation(content::NavigationHandle* handle) override;
   std::vector<std::unique_ptr<content::CommitDeferringCondition>>
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/BUILD.gn
index 09f2cd2..8dbeabc 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/BUILD.gn
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/BUILD.gn
@@ -6,6 +6,7 @@
   public = [
     "secure_enclave_client.h",
     "secure_enclave_helper.h",
+    "secure_enclave_signing_key.h",
   ]
 
   sources = [
@@ -15,9 +16,14 @@
     "secure_enclave_helper.cc",
     "secure_enclave_helper_impl.h",
     "secure_enclave_helper_impl.mm",
+    "secure_enclave_signing_key.cc",
   ]
 
-  public_deps = [ "//base" ]
+  public_deps = [
+    "//base",
+    "//crypto",
+    "//third_party/abseil-cpp:absl",
+  ]
 
   deps = [
     "//chrome/browser/enterprise/connectors/device_trust/key_management/core:constants",
@@ -54,7 +60,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "secure_enclave_client_unittest.mm" ]
+  sources = [
+    "secure_enclave_client_unittest.mm",
+    "secure_enclave_signing_key_unittest.mm",
+  ]
 
   deps = [
     ":mac",
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
index baa2de43e..cd77c6c 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
@@ -28,6 +28,10 @@
               CreateTemporaryKey,
               (),
               (override));
+  MOCK_METHOD(base::ScopedCFTypeRef<SecKeyRef>,
+              CopyStoredKey,
+              (KeyType),
+              (override));
   MOCK_METHOD(bool, MoveTemporaryKeyToPermanent, (), (override));
   MOCK_METHOD(bool, DeleteKey, (KeyType), (override));
   MOCK_METHOD(bool,
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_helper.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_helper.h
index 4d08c8a..f65586c3 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_helper.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_helper.h
@@ -25,9 +25,12 @@
               CreateSecureKey,
               (CFDictionaryRef),
               (override));
+  MOCK_METHOD(base::ScopedCFTypeRef<SecKeyRef>,
+              CopyKey,
+              (CFDictionaryRef),
+              (override));
   MOCK_METHOD(bool, Update, (CFDictionaryRef, CFDictionaryRef), (override));
   MOCK_METHOD(bool, Delete, (CFDictionaryRef), (override));
-  MOCK_METHOD(bool, CheckExists, (CFDictionaryRef), (override));
   MOCK_METHOD(bool, CheckKeychainUnlocked, (), (override));
   MOCK_METHOD(bool, IsSecureEnclaveSupported, (), (override));
 };
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
index 46357c6..dbaa1f4 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
@@ -39,6 +39,10 @@
   // Creates a new Secure Enclave private key with a temporary key label.
   virtual base::ScopedCFTypeRef<SecKeyRef> CreateTemporaryKey() = 0;
 
+  // Queries for the secure key using its label determined by the key `type`.
+  // Returns the secure key reference or a nullptr if no key was found.
+  virtual base::ScopedCFTypeRef<SecKeyRef> CopyStoredKey(KeyType type) = 0;
+
   // Updates the private key label from the temporary key label to the
   // non-temporary label.
   virtual bool MoveTemporaryKeyToPermanent() = 0;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
index 1683bf6f..2f4e5e5 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
@@ -28,6 +28,7 @@
 
   // SecureEnclaveClient:
   base::ScopedCFTypeRef<SecKeyRef> CreateTemporaryKey() override;
+  base::ScopedCFTypeRef<SecKeyRef> CopyStoredKey(KeyType type) override;
   bool MoveTemporaryKeyToPermanent() override;
   bool DeleteKey(KeyType type) override;
   bool GetStoredKeyLabel(KeyType type, std::vector<uint8_t>& output) override;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
index 58ba9cc..367af64 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
@@ -14,6 +14,7 @@
 #include "base/containers/span.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h"
@@ -28,12 +29,25 @@
 
 namespace {
 
+// Returns the key label based on the key `type` if the key type is not
+// supported an empty string is returned.
+base::StringPiece GetLabelFromKeyType(SecureEnclaveClient::KeyType type) {
+  if (type == SecureEnclaveClient::KeyType::kTemporary)
+    return constants::kTemporaryDeviceTrustSigningKeyLabel;
+  if (type == SecureEnclaveClient::KeyType::kPermanent)
+    return constants::kDeviceTrustSigningKeyLabel;
+  return base::StringPiece();
+}
+
 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
 // Removal of its use is tracked in https://crbug.com/1348251 but deprecation
 // warnings are disabled in the meanwhile.
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
+// TODO(http://b/241261382): Look for alternatives in ACL creation and validate
+// the new key is stored in the data protection keychain.
+
 // Issues the SecAccessCreate API to create the ACL for the secure key.
 // This ACL allows all Chrome applications access to modify this key
 // so all Chrome applications can perform the mac key rotation process.
@@ -88,13 +102,14 @@
 // Creates the query used for querying the keychain for the secure key
 // reference.
 base::ScopedCFTypeRef<CFMutableDictionaryRef> CreateQueryForKey(
-    const std::string& label) {
+    SecureEnclaveClient::KeyType type) {
   base::ScopedCFTypeRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(
       kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
       &kCFTypeDictionaryValueCallBacks));
   CFDictionarySetValue(query, kSecClass, kSecClassKey);
   CFDictionarySetValue(query, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
-  CFDictionarySetValue(query, kSecAttrLabel, base::SysUTF8ToCFStringRef(label));
+  CFDictionarySetValue(query, kSecAttrLabel,
+                       base::SysUTF8ToCFStringRef(GetLabelFromKeyType(type)));
   CFDictionarySetValue(query, kSecReturnRef, @YES);
   return query;
 }
@@ -119,6 +134,11 @@
   return helper_->CreateSecureKey(attributes);
 }
 
+base::ScopedCFTypeRef<SecKeyRef> SecureEnclaveClientImpl::CopyStoredKey(
+    KeyType type) {
+  return helper_->CopyKey(CreateQueryForKey(type));
+}
+
 bool SecureEnclaveClientImpl::MoveTemporaryKeyToPermanent() {
   // Deletes an old Secure Enclave key if it exists.
   DeleteKey(SecureEnclaveClient::KeyType::kPermanent);
@@ -131,26 +151,20 @@
       attributes_to_update, kSecAttrLabel,
       base::SysUTF8ToCFStringRef(constants::kDeviceTrustSigningKeyLabel));
 
-  return helper_->Update(
-      CreateQueryForKey(constants::kTemporaryDeviceTrustSigningKeyLabel),
-      attributes_to_update);
+  return helper_->Update(CreateQueryForKey(KeyType::kTemporary),
+                         attributes_to_update);
 }
 
 bool SecureEnclaveClientImpl::DeleteKey(KeyType type) {
-  auto* label = (type == KeyType::kTemporary)
-                    ? constants::kTemporaryDeviceTrustSigningKeyLabel
-                    : constants::kDeviceTrustSigningKeyLabel;
-  return helper_->Delete(CreateQueryForKey(label));
+  return helper_->Delete(CreateQueryForKey(type));
 }
 
 bool SecureEnclaveClientImpl::GetStoredKeyLabel(KeyType type,
                                                 std::vector<uint8_t>& output) {
-  std::string label = (type == KeyType::kTemporary)
-                          ? constants::kTemporaryDeviceTrustSigningKeyLabel
-                          : constants::kDeviceTrustSigningKeyLabel;
-  if (!helper_->CheckExists(CreateQueryForKey(label)))
+  if (!helper_->CopyKey(CreateQueryForKey(type)))
     return false;
 
+  auto label = GetLabelFromKeyType(type);
   output.assign(label.begin(), label.end());
   return true;
 }
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
index 000fdd0..4453395 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
@@ -112,6 +112,34 @@
   EXPECT_EQ(secure_enclave_client_->CreateTemporaryKey(), test_key_);
 }
 
+// Tests when the CopyStoredKey method invokes the SE helper's CopyKey method
+// and a key is found using both a permanent and a temporary key type.
+TEST_F(SecureEnclaveClientTest, CopyStoredKey_KeyFound) {
+  EXPECT_CALL(*mock_secure_enclave_helper_, CopyKey(_))
+      .Times(2)
+      .WillRepeatedly([this](CFDictionaryRef query) { return test_key_; });
+  EXPECT_EQ(secure_enclave_client_->CopyStoredKey(
+                SecureEnclaveClient::KeyType::kPermanent),
+            test_key_);
+  EXPECT_EQ(secure_enclave_client_->CopyStoredKey(
+                SecureEnclaveClient::KeyType::kTemporary),
+            test_key_);
+}
+
+// Tests when the CopyStoredKey method invokes the SE helper's CopyKey method
+// and a key is not found using both a permanent and a temporary key type.
+TEST_F(SecureEnclaveClientTest, CopyStoredKey_KeyNotFound) {
+  EXPECT_CALL(*mock_secure_enclave_helper_, CopyKey(_))
+      .Times(2)
+      .WillRepeatedly([](CFDictionaryRef query) {
+        return base::ScopedCFTypeRef<SecKeyRef>();
+      });
+  EXPECT_FALSE(secure_enclave_client_->CopyStoredKey(
+      SecureEnclaveClient::KeyType::kPermanent));
+  EXPECT_FALSE(secure_enclave_client_->CopyStoredKey(
+      SecureEnclaveClient::KeyType::kTemporary));
+}
+
 // Tests that the MoveTemporaryKeyToPermanent method invokes the SE helper's
 // Update method and that the key attributes and query are set correctly.
 TEST_F(SecureEnclaveClientTest, MoveTemporaryKeyToPermanent) {
@@ -160,16 +188,16 @@
       SecureEnclaveClient::KeyType::kPermanent));
 }
 
-// Tests that the GetStoredKeyLabel method invokes the SE helper's CheckExists
+// Tests that the GetStoredKeyLabel method invokes the SE helper's CopyKey
 // method and that the query and output is correct for a temporary key.
-TEST_F(SecureEnclaveClientTest, GetStoredKeyLabel_TempKeyLabel) {
+TEST_F(SecureEnclaveClientTest, GetStoredKeyLabel_TempKeyLabelFound) {
   std::vector<uint8_t> output;
   std::string temp_label = constants::kTemporaryDeviceTrustSigningKeyLabel;
-  EXPECT_CALL(*mock_secure_enclave_helper_, CheckExists(_))
+  EXPECT_CALL(*mock_secure_enclave_helper_, CopyKey(_))
       .Times(1)
       .WillOnce([this, &temp_label](CFDictionaryRef query) {
         VerifyQuery(query, base::SysUTF8ToCFStringRef(temp_label));
-        return true;
+        return test_key_;
       });
 
   EXPECT_TRUE(secure_enclave_client_->GetStoredKeyLabel(
@@ -179,16 +207,16 @@
   EXPECT_EQ(expected_output, output);
 }
 
-// Tests that the GetStoredKeyLabel method invokes the SE helper's CheckExists
+// Tests that the GetStoredKeyLabel method invokes the SE helper's CopyKey
 // method and that the query and output is correct for a permanent key.
-TEST_F(SecureEnclaveClientTest, GetStoredKeyLabel_PermanentKeyLabel) {
+TEST_F(SecureEnclaveClientTest, GetStoredKeyLabel_PermanentKeyLabelFound) {
   std::vector<uint8_t> output;
   std::string permanent_label = constants::kDeviceTrustSigningKeyLabel;
-  EXPECT_CALL(*mock_secure_enclave_helper_, CheckExists(_))
+  EXPECT_CALL(*mock_secure_enclave_helper_, CopyKey(_))
       .Times(1)
       .WillOnce([this, &permanent_label](CFDictionaryRef query) {
         VerifyQuery(query, base::SysUTF8ToCFStringRef(permanent_label));
-        return true;
+        return test_key_;
       });
   EXPECT_TRUE(secure_enclave_client_->GetStoredKeyLabel(
       SecureEnclaveClient::KeyType::kPermanent, output));
@@ -197,13 +225,15 @@
   EXPECT_EQ(expected_output, output);
 }
 
-// Tests that the GetStoredKeyLabel method invokes the SE helper's CheckExists
+// Tests that the GetStoredKeyLabel method invokes the SE helper's CopyKey
 // method and that the query search returns false.
 TEST_F(SecureEnclaveClientTest, GetStoredKeyLabel_KeyNotFound) {
   std::vector<uint8_t> output;
-  EXPECT_CALL(*mock_secure_enclave_helper_, CheckExists(_))
+  EXPECT_CALL(*mock_secure_enclave_helper_, CopyKey(_))
       .Times(1)
-      .WillOnce([](CFDictionaryRef query) { return false; });
+      .WillOnce([](CFDictionaryRef query) {
+        return base::ScopedCFTypeRef<SecKeyRef>();
+      });
   EXPECT_FALSE(secure_enclave_client_->GetStoredKeyLabel(
       SecureEnclaveClient::KeyType::kPermanent, output));
   std::vector<uint8_t> expected_output;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h
index 6fba910..f0c705e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h
@@ -25,26 +25,27 @@
 
   static std::unique_ptr<SecureEnclaveHelper> Create();
 
-  // Issues the SecKeyCreateRandomKey API to create the secure key with its key
+  // Uses the SecKeyCreateRandomKey API to create the secure key with its key
   // `attributes`. Returns the key or a nullptr on failure.
   virtual base::ScopedCFTypeRef<SecKeyRef> CreateSecureKey(
       CFDictionaryRef attributes) = 0;
 
-  // Issues the SecItemUpdate API to update the the key retrieved with the
+  // Uses the SecItemCopyMatching API to search the keychain using the
+  // `query` dictionary. Returns the reference to the secure key or a nullptr
+  // if the key is not found.
+  virtual base::ScopedCFTypeRef<SecKeyRef> CopyKey(CFDictionaryRef query) = 0;
+
+  // Uses the SecItemUpdate API to update the the key retrieved with the
   // `query` with its `attributes_to_update`. Returns true if the key
   // attributes were updated successfully and false otherwise.
   virtual bool Update(CFDictionaryRef query,
                       CFDictionaryRef attributes_to_update) = 0;
 
-  // Issues the SecItemDelete API to delete the key retrieved with the `query`.
+  // Uses the SecItemDelete API to delete the key retrieved with the `query`.
   // Returns true if the key was deleted and false otherwise.
   virtual bool Delete(CFDictionaryRef query) = 0;
 
-  // Issues the SecItemCopyMatching API to search the keychain using the
-  // `query` dictionary. Returns true if the key exists and false otherwise.
-  virtual bool CheckExists(CFDictionaryRef query) = 0;
-
-  // Issues the SecKeychainCopyDefault API to check if the keychain is unlocked.
+  // Uses the SecKeychainCopyDefault API to check if the keychain is unlocked.
   // Returns true when the keychain is unlocked and false otherwise.
   virtual bool CheckKeychainUnlocked() = 0;
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.h
index f9e1a56..0d656c7 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.h
@@ -22,10 +22,10 @@
   // SecureEnclaveHelper:
   base::ScopedCFTypeRef<SecKeyRef> CreateSecureKey(
       CFDictionaryRef attributes) override;
+  base::ScopedCFTypeRef<SecKeyRef> CopyKey(CFDictionaryRef query) override;
   bool Update(CFDictionaryRef query,
               CFDictionaryRef attributes_to_update) override;
   bool Delete(CFDictionaryRef query) override;
-  bool CheckExists(CFDictionaryRef query) override;
   bool CheckKeychainUnlocked() override;
   bool IsSecureEnclaveSupported() override;
 };
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
index f8bab1a..88d7958 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper_impl.mm
@@ -35,10 +35,13 @@
   return SecItemDelete(query) == errSecSuccess;
 }
 
-bool SecureEnclaveHelperImpl::CheckExists(CFDictionaryRef query) {
-  base::ScopedCFTypeRef<CFTypeRef> key;
-  SecItemCopyMatching(query, key.InitializeInto());
-  return key != nullptr;
+base::ScopedCFTypeRef<SecKeyRef> SecureEnclaveHelperImpl::CopyKey(
+    CFDictionaryRef query) {
+  base::ScopedCFTypeRef<SecKeyRef> key;
+  SecItemCopyMatching(
+      query, const_cast<CFTypeRef*>(
+                 reinterpret_cast<const CFTypeRef*>(key.InitializeInto())));
+  return key;
 }
 
 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc
new file mode 100644
index 0000000..fe73436b
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc
@@ -0,0 +1,166 @@
+// 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/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "base/containers/span.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/notreached.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/shared_command_constants.h"
+#include "crypto/signature_verifier.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace enterprise_connectors {
+
+namespace {
+
+// Returns the result of the comparison of the key `label` and the
+// `wrapped_key_label`.
+bool CheckEqual(const std::string& label,
+                base::span<const uint8_t> wrapped_key_label) {
+  auto label_span = base::as_bytes(base::make_span(label));
+  return std::equal(wrapped_key_label.begin(), wrapped_key_label.end(),
+                    label_span.begin(), label_span.end());
+}
+
+// Returns the key type from the `wrapped_key_label` if the `wrapped_key_label`
+// matches any of the supported key labels. Otherwise a nullptr is returned.
+absl::optional<SecureEnclaveClient::KeyType> GetTypeFromWrappedKey(
+    base::span<const uint8_t> wrapped_key_label) {
+  if (CheckEqual(constants::kDeviceTrustSigningKeyLabel, wrapped_key_label)) {
+    return SecureEnclaveClient::KeyType::kPermanent;
+  }
+
+  if (CheckEqual(constants::kTemporaryDeviceTrustSigningKeyLabel,
+                 wrapped_key_label)) {
+    return SecureEnclaveClient::KeyType::kTemporary;
+  }
+
+  NOTREACHED();
+  return absl::nullopt;
+}
+
+// An implementation of crypto::UnexportableSigningKey.
+class SecureEnclaveSigningKey : public crypto::UnexportableSigningKey {
+ public:
+  SecureEnclaveSigningKey(base::ScopedCFTypeRef<SecKeyRef> key,
+                          std::unique_ptr<SecureEnclaveClient> client,
+                          SecureEnclaveClient::KeyType type);
+  ~SecureEnclaveSigningKey() override;
+
+  // crypto::UnexportableSigningKey:
+  crypto::SignatureVerifier::SignatureAlgorithm Algorithm() const override;
+  std::vector<uint8_t> GetSubjectPublicKeyInfo() const override;
+  std::vector<uint8_t> GetWrappedKey() const override;
+  absl::optional<std::vector<uint8_t>> SignSlowly(
+      base::span<const uint8_t> data) override;
+
+ private:
+  base::ScopedCFTypeRef<SecKeyRef> key_;
+  std::unique_ptr<SecureEnclaveClient> client_;
+  SecureEnclaveClient::KeyType key_type_;
+};
+
+SecureEnclaveSigningKey::SecureEnclaveSigningKey(
+    base::ScopedCFTypeRef<SecKeyRef> key,
+    std::unique_ptr<SecureEnclaveClient> client,
+    SecureEnclaveClient::KeyType type)
+    : key_(std::move(key)), client_(std::move(client)), key_type_(type) {
+  DCHECK(key_);
+  DCHECK(client_);
+}
+
+SecureEnclaveSigningKey::~SecureEnclaveSigningKey() = default;
+
+crypto::SignatureVerifier::SignatureAlgorithm
+SecureEnclaveSigningKey::Algorithm() const {
+  return crypto::SignatureVerifier::ECDSA_SHA256;
+}
+
+std::vector<uint8_t> SecureEnclaveSigningKey::GetSubjectPublicKeyInfo() const {
+  std::vector<uint8_t> pubkey;
+  client_->ExportPublicKey(key_, pubkey);
+  return pubkey;
+}
+
+std::vector<uint8_t> SecureEnclaveSigningKey::GetWrappedKey() const {
+  std::vector<uint8_t> wrapped;
+  client_->GetStoredKeyLabel(key_type_, wrapped);
+  return wrapped;
+}
+
+absl::optional<std::vector<uint8_t>> SecureEnclaveSigningKey::SignSlowly(
+    base::span<const uint8_t> data) {
+  std::vector<uint8_t> signature;
+  if (!client_->SignDataWithKey(key_, data, signature)) {
+    return absl::nullopt;
+  }
+
+  return signature;
+}
+
+}  // namespace
+
+SecureEnclaveSigningKeyProvider::SecureEnclaveSigningKeyProvider(
+    SecureEnclaveClient::KeyType type)
+    : provider_key_type_(type) {}
+SecureEnclaveSigningKeyProvider::~SecureEnclaveSigningKeyProvider() = default;
+
+absl::optional<crypto::SignatureVerifier::SignatureAlgorithm>
+SecureEnclaveSigningKeyProvider::SelectAlgorithm(
+    base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
+        acceptable_algorithms) {
+  const auto kAlgorithm = crypto::SignatureVerifier::ECDSA_SHA256;
+  if (base::Contains(acceptable_algorithms, kAlgorithm))
+    return kAlgorithm;
+
+  return absl::nullopt;
+}
+
+std::unique_ptr<crypto::UnexportableSigningKey>
+SecureEnclaveSigningKeyProvider::GenerateSigningKeySlowly(
+    base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
+        acceptable_algorithms) {
+  if (provider_key_type_ != SecureEnclaveClient::KeyType::kTemporary)
+    return nullptr;
+
+  auto algo = SelectAlgorithm(acceptable_algorithms);
+  if (!algo)
+    return nullptr;
+
+  DCHECK_EQ(crypto::SignatureVerifier::ECDSA_SHA256, *algo);
+
+  auto client = SecureEnclaveClient::Create();
+  auto key = client->CreateTemporaryKey();
+  if (!key)
+    return nullptr;
+
+  return std::make_unique<SecureEnclaveSigningKey>(
+      std::move(key), std::move(client), provider_key_type_);
+}
+
+std::unique_ptr<crypto::UnexportableSigningKey>
+SecureEnclaveSigningKeyProvider::FromWrappedSigningKeySlowly(
+    base::span<const uint8_t> wrapped_key_label) {
+  // Verifying wrapped key label matches the provider key type.
+  if (provider_key_type_ != GetTypeFromWrappedKey(wrapped_key_label))
+    return nullptr;
+
+  auto client = SecureEnclaveClient::Create();
+  auto key = client->CopyStoredKey(provider_key_type_);
+  if (!key)
+    return nullptr;
+
+  return std::make_unique<SecureEnclaveSigningKey>(
+      std::move(key), std::move(client), provider_key_type_);
+}
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h
new file mode 100644
index 0000000..ba0a015
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h
@@ -0,0 +1,43 @@
+// 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 CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_KEY_MANAGEMENT_CORE_MAC_SECURE_ENCLAVE_SIGNING_KEY_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_KEY_MANAGEMENT_CORE_MAC_SECURE_ENCLAVE_SIGNING_KEY_H_
+
+#include <memory>
+
+#include "base/containers/span.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h"
+#include "crypto/unexportable_key.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace enterprise_connectors {
+
+// An implementation of crypto::UnexportableKeyProvider for mac using the
+// Secure Enclave key.
+class SecureEnclaveSigningKeyProvider : public crypto::UnexportableKeyProvider {
+ public:
+  // Takes a parameter of key `type` (Permanent or Temporary) that the key
+  // provider will represent.
+  explicit SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType type);
+  ~SecureEnclaveSigningKeyProvider() override;
+
+  // crypto::UnexportableKeyProvider:
+  absl::optional<crypto::SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
+      base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
+          acceptable_algorithms) override;
+  std::unique_ptr<crypto::UnexportableSigningKey> GenerateSigningKeySlowly(
+      base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
+          acceptable_algorithms) override;
+  std::unique_ptr<crypto::UnexportableSigningKey> FromWrappedSigningKeySlowly(
+      base::span<const uint8_t> wrapped_key_label) override;
+
+ private:
+  // The key type (Permanent or Temporary) the provider represents.
+  SecureEnclaveClient::KeyType provider_key_type_;
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_KEY_MANAGEMENT_CORE_MAC_SECURE_ENCLAVE_SIGNING_KEY_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
new file mode 100644
index 0000000..ac0bd94
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
@@ -0,0 +1,226 @@
+// 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/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h"
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h"
+#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/shared_command_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using ::testing::InSequence;
+
+namespace enterprise_connectors {
+
+using test::MockSecureEnclaveClient;
+
+class SecureEnclaveSigningKeyTest : public testing::Test {
+ public:
+  SecureEnclaveSigningKeyTest() {
+    CreateTestKey();
+    auto mock_secure_enclave_client =
+        std::make_unique<MockSecureEnclaveClient>();
+    mock_secure_enclave_client_ = mock_secure_enclave_client.get();
+    SecureEnclaveClient::SetInstanceForTesting(
+        std::move(mock_secure_enclave_client));
+  }
+
+ protected:
+  // Creates a temporary key.
+  void CreateTestKey() {
+    base::ScopedCFTypeRef<CFMutableDictionaryRef> test_attributes(
+        CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+                                  &kCFTypeDictionaryKeyCallBacks,
+                                  &kCFTypeDictionaryValueCallBacks));
+    CFDictionarySetValue(test_attributes, kSecAttrLabel,
+                         base::SysUTF8ToNSString("fake-label"));
+    CFDictionarySetValue(test_attributes, kSecAttrKeyType,
+                         kSecAttrKeyTypeECSECPrimeRandom);
+    CFDictionarySetValue(test_attributes, kSecAttrKeySizeInBits, @256);
+    base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
+        CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+                                  &kCFTypeDictionaryKeyCallBacks,
+                                  &kCFTypeDictionaryValueCallBacks));
+    CFDictionarySetValue(private_key_params, kSecAttrIsPermanent, @NO);
+    CFDictionarySetValue(test_attributes, kSecPrivateKeyAttrs,
+                         private_key_params);
+    test_key_ = base::ScopedCFTypeRef<SecKeyRef>(
+        SecKeyCreateRandomKey(test_attributes, nullptr));
+  }
+
+  // Sets the unexportable key using the test key.
+  void SetUnexportableKey() {
+    auto provider = SecureEnclaveSigningKeyProvider(
+        SecureEnclaveClient::KeyType::kTemporary);
+    EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey())
+        .Times(1)
+        .WillOnce([this]() { return test_key_; });
+    auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
+    key_ = provider.GenerateSigningKeySlowly(acceptable_algorithms);
+  }
+
+  MockSecureEnclaveClient* mock_secure_enclave_client_ = nullptr;
+  std::unique_ptr<crypto::UnexportableSigningKey> key_;
+  base::ScopedCFTypeRef<SecKeyRef> test_key_;
+};
+
+// Tests that the GenerateSigningKeySlowly method invokes the SE client's
+// CreateTemporaryKey method only when the provider is a temporary key provider.
+TEST_F(SecureEnclaveSigningKeyTest, GenerateSigningKeySlowly) {
+  auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
+
+  InSequence s;
+
+  auto provider =
+      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kTemporary);
+  EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey())
+      .Times(1)
+      .WillOnce([this]() { return test_key_; });
+  auto unexportable_key =
+      provider.GenerateSigningKeySlowly(acceptable_algorithms);
+  EXPECT_TRUE(unexportable_key);
+  EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
+            unexportable_key->Algorithm());
+
+  provider =
+      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kPermanent);
+  EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey()).Times(0);
+  unexportable_key = provider.GenerateSigningKeySlowly(acceptable_algorithms);
+  EXPECT_FALSE(unexportable_key);
+}
+
+// Tests that the FromWrappedSigningKeySlowly invokes the SE client's
+// CopyStoredKey method only when the wrapped key label matches the key
+// provider type and the provider is a permanent key provider.
+TEST_F(SecureEnclaveSigningKeyTest,
+       FromWrappedSigningKeySlowly_PermanentKeyProvider) {
+  auto permanent_key_provider = SecureEnclaveSigningKeyProvider(
+      (SecureEnclaveClient::KeyType::kPermanent));
+  std::unique_ptr<crypto::UnexportableSigningKey> unexportable_key;
+
+  InSequence s;
+
+  // Permanent provider key type with a wrapped permanent key label.
+  EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_))
+      .Times(1)
+      .WillOnce([this](SecureEnclaveClient::KeyType type) {
+        EXPECT_EQ(SecureEnclaveClient::KeyType::kPermanent, type);
+        return test_key_;
+      });
+  unexportable_key = permanent_key_provider.FromWrappedSigningKeySlowly(
+      base::as_bytes(base::make_span(
+          std::string(constants::kDeviceTrustSigningKeyLabel))));
+  EXPECT_TRUE(unexportable_key);
+  EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
+            unexportable_key->Algorithm());
+
+  // Permanent key provider with a wrapped temporary key label.
+  EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_)).Times(0);
+  unexportable_key = permanent_key_provider.FromWrappedSigningKeySlowly(
+      base::as_bytes(base::make_span(
+          std::string(constants::kTemporaryDeviceTrustSigningKeyLabel))));
+  EXPECT_FALSE(unexportable_key);
+}
+
+// Tests that the FromWrappedSigningKeySlowly invokes the SE client's
+// CopyStoredKey method only when the wrapped key label matches the key
+// provider type and the provider is a temporary key provider.
+TEST_F(SecureEnclaveSigningKeyTest,
+       FromWrappedSigningKeySlowly_TemporaryKeyProvider) {
+  auto temp_key_provider = SecureEnclaveSigningKeyProvider(
+      (SecureEnclaveClient::KeyType::kTemporary));
+  std::unique_ptr<crypto::UnexportableSigningKey> unexportable_key;
+
+  InSequence s;
+
+  // Temporary key provider with a wrapped temporary key label.
+  EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_))
+      .Times(1)
+      .WillOnce([this](SecureEnclaveClient::KeyType type) {
+        EXPECT_EQ(SecureEnclaveClient::KeyType::kTemporary, type);
+        return test_key_;
+      });
+  unexportable_key = temp_key_provider.FromWrappedSigningKeySlowly(
+      base::as_bytes(base::make_span(
+          std::string(constants::kTemporaryDeviceTrustSigningKeyLabel))));
+  EXPECT_TRUE(unexportable_key);
+  EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
+            unexportable_key->Algorithm());
+
+  // Temporary provider key type with a wrapped permanent key label.
+  EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_)).Times(0);
+  unexportable_key = temp_key_provider.FromWrappedSigningKeySlowly(
+      base::as_bytes(base::make_span(
+          std::string(constants::kDeviceTrustSigningKeyLabel))));
+  EXPECT_FALSE(unexportable_key);
+}
+
+// Tests that the GetSubjectPublicKeyInfo method invokes the SE client's
+// ExportPublicKey method and that the public key information gotten from
+// this method is correct.
+TEST_F(SecureEnclaveSigningKeyTest, GetSubjectPublicKeyInfo) {
+  SetUnexportableKey();
+  std::string test_data = "data";
+  EXPECT_CALL(*mock_secure_enclave_client_, ExportPublicKey(_, _))
+      .WillOnce([&test_data](SecKeyRef key, std::vector<uint8_t>& output) {
+        output.assign(test_data.begin(), test_data.end());
+        return true;
+      });
+
+  EXPECT_EQ(std::vector<uint8_t>(test_data.begin(), test_data.end()),
+            key_->GetSubjectPublicKeyInfo());
+}
+
+// Tests that the GetWrappedKey method invokes the SE client's
+// GetStoredKeyLabel method and that the wrapped key label is the temporary
+// key label since the SecureEnclaveSigningKey is currently a temporary key.
+TEST_F(SecureEnclaveSigningKeyTest, GetWrappedKey) {
+  SetUnexportableKey();
+  EXPECT_CALL(*mock_secure_enclave_client_,
+              GetStoredKeyLabel(SecureEnclaveClient::KeyType::kTemporary, _))
+      .WillOnce(
+          [](SecureEnclaveClient::KeyType type, std::vector<uint8_t>& output) {
+            std::string label =
+                (type == SecureEnclaveClient::KeyType::kTemporary)
+                    ? constants::kTemporaryDeviceTrustSigningKeyLabel
+                    : constants::kDeviceTrustSigningKeyLabel;
+            output.assign(label.begin(), label.end());
+            return true;
+          });
+  auto wrapped = key_->GetWrappedKey();
+  EXPECT_EQ(constants::kTemporaryDeviceTrustSigningKeyLabel,
+            std::string(wrapped.begin(), wrapped.end()));
+}
+
+// Tests that the SignSlowly method invokes the SE client's SignDataWithKey
+// method and that the signature is correct.
+TEST_F(SecureEnclaveSigningKeyTest, SignSlowly) {
+  SetUnexportableKey();
+  std::string test_data = "data";
+  EXPECT_CALL(*mock_secure_enclave_client_, SignDataWithKey(_, _, _))
+      .Times(1)
+      .WillOnce([&test_data](SecKeyRef key, base::span<const uint8_t> data,
+                             std::vector<uint8_t>& output) {
+        output.assign(test_data.begin(), test_data.end());
+        return true;
+      });
+  EXPECT_EQ(
+      std::vector<uint8_t>(test_data.begin(), test_data.end()),
+      key_->SignSlowly(base::as_bytes(base::make_span("data to be sign"))));
+}
+
+}  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.cc b/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.cc
index 1fd28e0..cfe3696 100644
--- a/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.cc
+++ b/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.cc
@@ -13,6 +13,8 @@
 
 const base::Feature kExtensionEventsEnabled{"ExtensionEventsEnabled",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kBrowserCrashEventsEnabled{
+    "BrowserCrashEventsEnabled", base::FEATURE_DISABLED_BY_DEFAULT};
 namespace {
 
 constexpr char kReportingConnectorUrlFlag[] = "reporting-connector-url";
diff --git a/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h b/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h
index 79ba5b1..ccd953f 100644
--- a/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h
+++ b/chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h
@@ -20,6 +20,7 @@
 
 // Feature flags for individual event types.
 extern const base::Feature kExtensionEventsEnabled;
+extern const base::Feature kBrowserCrashEventsEnabled;
 
 // The settings for a report service obtained from a connector policy.
 class ReportingServiceSettings {
@@ -38,6 +39,7 @@
 
   static constexpr char kExtensionInstallEvent[] =
       "browserExtensionInstallEvent";
+  static constexpr char kBrowserCrashEvent[] = "browserCrashEvent";
 
   // All events that the reporting connector supports.
   static const constexpr char* kAllReportingEvents[] = {
@@ -50,6 +52,7 @@
       extensions::SafeBrowsingPrivateEventRouter::kKeyLoginEvent,
       extensions::SafeBrowsingPrivateEventRouter::kKeyPasswordBreachEvent,
       kExtensionInstallEvent,
+      kBrowserCrashEvent,
   };
 
  private:
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index c87c4e06..b91ea09 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1219,6 +1219,8 @@
       "api/file_browser_handler/file_browser_handler_flow_lacros.h",
       "api/file_manager/file_browser_handler_api_lacros.cc",
       "api/file_manager/file_browser_handler_api_lacros.h",
+      "api/file_system/chrome_file_system_delegate_lacros.cc",
+      "api/file_system/chrome_file_system_delegate_lacros.h",
       "api/file_system/volume_list_provider_lacros.cc",
       "api/file_system/volume_list_provider_lacros.h",
       "api/image_writer_private/image_writer_controller_lacros.cc",
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
index 69b42a3..edb9bfa 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
@@ -72,6 +72,7 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h"
 #include "chrome/browser/extensions/api/virtual_keyboard_private/lacros_virtual_keyboard_delegate.h"
 #endif
 
@@ -391,6 +392,8 @@
 FileSystemDelegate* ChromeExtensionsAPIClient::GetFileSystemDelegate() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegateAsh;
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegateLacros;
 #else
   using ChromeFileSystemDelegate_Use = ChromeFileSystemDelegate;
 #endif
diff --git a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc
index d34e8da..e974799 100644
--- a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc
+++ b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.cc
@@ -35,15 +35,15 @@
 #include "base/mac/foundation_util.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 #include "extensions/browser/event_router.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace extensions {
 
 namespace file_system = api::file_system;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 using file_system_api::ConsentProvider;
 using file_system_api::ConsentProviderDelegate;
 
@@ -72,7 +72,7 @@
 }
 
 }  // namespace file_system_api
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 /******** ChromeFileSystemDelegate ********/
 
@@ -165,7 +165,7 @@
   return 0;
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 bool ChromeFileSystemDelegate::IsGrantable(
     content::BrowserContext* browser_context,
     const Extension& extension) {
@@ -189,7 +189,7 @@
     content::BrowserContext* browser_context,
     VolumeListCallback success_callback,
     ErrorCallback error_callback) {}
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 SavedFilesServiceInterface* ChromeFileSystemDelegate::GetSavedFilesService(
     content::BrowserContext* browser_context) {
diff --git a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h
index 07fba404..cde2f69 100644
--- a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h
+++ b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h
@@ -16,9 +16,9 @@
 #include "extensions/browser/extension_function.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/extensions/api/file_system/consent_provider.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace content {
 class BrowserContext;
@@ -26,7 +26,7 @@
 
 namespace extensions {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 namespace file_system_api {
 
 extern const char kConsentImpossible[];
@@ -39,7 +39,7 @@
 const char* ConsentResultToError(ConsentProvider::Consent result);
 
 }  // namespace file_system_api
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 class ChromeFileSystemDelegate : public FileSystemDelegate {
  public:
@@ -68,7 +68,7 @@
                                        base::OnceClosure on_accept,
                                        base::OnceClosure on_cancel) override;
   int GetDescriptionIdForAcceptType(const std::string& accept_type) override;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   bool IsGrantable(content::BrowserContext* browser_context,
                    const Extension& extension) override;
   void RequestFileSystem(content::BrowserContext* browser_context,
@@ -81,7 +81,7 @@
   void GetVolumeList(content::BrowserContext* browser_context,
                      VolumeListCallback success_callback,
                      ErrorCallback error_callback) override;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
   SavedFilesServiceInterface* GetSavedFilesService(
       content::BrowserContext* browser_context) override;
 };
diff --git a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc
new file mode 100644
index 0000000..0898fa9
--- /dev/null
+++ b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.cc
@@ -0,0 +1,349 @@
+// 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/extensions/api/file_system/chrome_file_system_delegate_lacros.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/memory/raw_ptr.h"
+#include "base/path_service.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/extensions/api/file_system/consent_provider.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_observer.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "extensions/browser/api/file_handlers/app_file_handler_util.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/granted_file_entry.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace file_system = api::file_system;
+
+using extensions::app_file_handler_util::CreateFileEntryWithPermissions;
+using file_system_api::ConsentProvider;
+using file_system_api::ConsentProviderDelegate;
+
+namespace {
+
+const char kApiUnavailableError[] = "API unavailable.";
+const char kProfileGoneError[] = "Profile gone.";
+const char kRenderFrameHostGoneError[] = "Render frame host gone.";
+const char kRequestAbortedError[] = "Request aborted.";
+
+// Volume list converter that excludes volumes unsupported by lacros-chrome.
+void ConvertAndFilterMojomToVolumeList(
+    const std::vector<crosapi::mojom::VolumePtr>& src_volume_list,
+    std::vector<file_system::Volume>* dst_volume_list) {
+  DCHECK(dst_volume_list->empty());
+  for (auto& src_volume : src_volume_list) {
+    if (src_volume->is_available_to_lacros) {
+      file_system::Volume dst_volume;
+      dst_volume.volume_id = src_volume->volume_id;
+      dst_volume.writable = src_volume->writable;
+      dst_volume_list->emplace_back(std::move(dst_volume));
+    }
+  }
+}
+
+}  // namespace
+
+namespace file_system_api {
+
+void DispatchVolumeListChangeEventLacros(
+    content::BrowserContext* browser_context,
+    const std::vector<crosapi::mojom::VolumePtr>& volume_list) {
+  DCHECK(browser_context);
+  EventRouter* const event_router = EventRouter::Get(browser_context);
+  if (!event_router)  // Possible on shutdown.
+    return;
+
+  ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context);
+  if (!registry)  // Possible on shutdown.
+    return;
+
+  // TODO(crbug.com/1351493): Simplify usage for IsGrantable().
+  ConsentProviderDelegate consent_provider_delegate(
+      Profile::FromBrowserContext(browser_context));
+  ConsentProvider consent_provider(&consent_provider_delegate);
+
+  file_system::VolumeListChangedEvent event_args;
+  // Note: Events are still fired even if:
+  // * The *filtered* volume list does not change.
+  // * The filtered volume list is empty.
+  // This is done for simplicy: Detecting change in filtered volume list will
+  // requires caching volume list on Lacros side; preventing empty filtered
+  // volume list from triggering an event will lead to inconsistencies compared
+  // to polling via getVolumeList().
+  ConvertAndFilterMojomToVolumeList(volume_list, &event_args.volumes);
+  for (const auto& extension : registry->enabled_extensions()) {
+    if (!consent_provider.IsGrantable(*extension))
+      continue;
+
+    event_router->DispatchEventToExtension(
+        extension->id(),
+        std::make_unique<Event>(
+            events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
+            file_system::OnVolumeListChanged::kEventName,
+            file_system::OnVolumeListChanged::Create(event_args)));
+  }
+}
+
+}  // namespace file_system_api
+
+namespace {
+
+/******** RequestFileSystemExecutor ********/
+
+// Executor for chrome.requestFileSystem(), with async steps:
+// 1. Crosapi call to get volume info.
+// 2. (Potentially) request consent via dialog.
+// Sources of complexity:
+// * Lifetime: Instances are ref counted, and are kept alive via callback
+//   binding.
+// * Profile: (2) requires |profile_|, which may disappear while awaiting (1)!
+//   This is handled by observing |profile_|: If it is destroyed then abort
+//   before (2); else proceeds with (2) and unobserve ASAP.
+// * Fulfillment: To ensure the request is fulfilled, one of |success_callback|
+//   or |error_callback| gets called eventually (via FinishWith*()).
+class RequestFileSystemExecutor
+    : public base::RefCountedThreadSafe<RequestFileSystemExecutor>,
+      public ProfileObserver {
+ public:
+  RequestFileSystemExecutor(
+      Profile* profile,
+      scoped_refptr<ExtensionFunction> requester,
+      const std::string& volume_id,
+      bool writable,
+      ChromeFileSystemDelegate::FileSystemCallback success_callback,
+      ChromeFileSystemDelegate::ErrorCallback error_callback);
+  RequestFileSystemExecutor(const RequestFileSystemExecutor&) = delete;
+  RequestFileSystemExecutor& operator=(const RequestFileSystemExecutor&) =
+      delete;
+
+  // Entry point for executor flow.
+  void Run(chromeos::LacrosService* lacros_service);
+
+ private:
+  friend class base::RefCountedThreadSafe<RequestFileSystemExecutor>;
+  ~RequestFileSystemExecutor() override;
+
+  // ProfileObserver:
+  void OnProfileWillBeDestroyed(Profile* profile) override;
+
+  // Callback for (1), on receiving volume info from crosapi.
+  void OnCrosapiGetVolumeMountInfo(crosapi::mojom::VolumePtr crosapi_volume);
+
+  // Callback for (2), on consent granting or denial.
+  void OnConsentReceived(base::FilePath mount_path,
+                         ConsentProvider::Consent result);
+
+  // Consumes |error_callback_| to pass |error| on error.
+  void FinishWithError(const std::string& error);
+
+  // Consumes |success_callback_| to pass results on success.
+  void FinishWithResponse(const std::string& filesystem_id,
+                          const std::string& registered_name);
+
+  // |profile_| can be a raw pointer since its destruction is observed.
+  base::raw_ptr<Profile> profile_;
+  base::ScopedObservation<Profile, ProfileObserver> profile_observation_{this};
+  scoped_refptr<ExtensionFunction> requester_;
+  const std::string volume_id_;
+  const bool want_writable_;
+  ChromeFileSystemDelegate::FileSystemCallback success_callback_;
+  ChromeFileSystemDelegate::ErrorCallback error_callback_;
+  bool responded_ = false;
+};
+
+RequestFileSystemExecutor::RequestFileSystemExecutor(
+    Profile* profile,
+    scoped_refptr<ExtensionFunction> requester,
+    const std::string& volume_id,
+    bool want_writable,
+    ChromeFileSystemDelegate::FileSystemCallback success_callback,
+    ChromeFileSystemDelegate::ErrorCallback error_callback)
+    : profile_(profile),
+      requester_(requester),
+      volume_id_(volume_id),
+      want_writable_(want_writable),
+      success_callback_(std::move(success_callback)),
+      error_callback_(std::move(error_callback)) {
+  profile_observation_.Observe(profile_);
+}
+
+void RequestFileSystemExecutor::Run(chromeos::LacrosService* lacros_service) {
+  // All code path from here must lead to either |success_callback_| or
+  // |error_callback_| getting called.
+  lacros_service->GetRemote<crosapi::mojom::VolumeManager>()
+      ->GetVolumeMountInfo(
+          volume_id_,
+          base::BindOnce(
+              &RequestFileSystemExecutor::OnCrosapiGetVolumeMountInfo, this));
+}
+
+RequestFileSystemExecutor::~RequestFileSystemExecutor() {
+  if (!responded_)
+    FinishWithError(kRequestAbortedError);
+}
+
+void RequestFileSystemExecutor::OnProfileWillBeDestroyed(Profile* profile) {
+  DCHECK_EQ(profile_, profile);
+  profile_observation_.Reset();
+  profile_ = nullptr;
+}
+
+void RequestFileSystemExecutor::OnCrosapiGetVolumeMountInfo(
+    crosapi::mojom::VolumePtr crosapi_volume) {
+  // Profile can be gone before this callback executes, while awaiting crosapi.
+  if (!profile_) {
+    FinishWithError(kProfileGoneError);
+    return;
+  }
+  if (!crosapi_volume || !crosapi_volume->is_available_to_lacros) {
+    FinishWithError(file_system_api::kVolumeNotFoundError);
+    return;
+  }
+  if (want_writable_ && !crosapi_volume->writable) {
+    FinishWithError(file_system_api::kSecurityError);
+    return;
+  }
+
+  // TODO(crbug.com/1351493): Simplify usage for RequestConsent().
+  ConsentProviderDelegate consent_provider_delegate(profile_);
+  ConsentProvider consent_provider(&consent_provider_delegate);
+
+  ConsentProvider::ConsentCallback callback =
+      base::BindOnce(&RequestFileSystemExecutor::OnConsentReceived, this,
+                     crosapi_volume->mount_path);
+
+  consent_provider.RequestConsent(
+      requester_->render_frame_host(), *requester_->extension(),
+      crosapi_volume->volume_id, crosapi_volume->volume_label, want_writable_,
+      std::move(callback));
+
+  // Done with |profile_|, so stop observing.
+  profile_observation_.Reset();
+  profile_ = nullptr;
+}
+
+void RequestFileSystemExecutor::OnConsentReceived(
+    base::FilePath mount_path,
+    ConsentProvider::Consent result) {
+  // Render frame host can be gone before this callback executes.
+  if (!requester_->render_frame_host()) {
+    FinishWithError(kRenderFrameHostGoneError);
+    return;
+  }
+
+  const char* consent_err_msg = file_system_api::ConsentResultToError(result);
+  if (consent_err_msg) {
+    FinishWithError(consent_err_msg);
+    return;
+  }
+
+  const auto process_id = requester_->source_process_id();
+  extensions::GrantedFileEntry granted_file_entry =
+      CreateFileEntryWithPermissions(process_id, mount_path,
+                                     /*can_write=*/want_writable_,
+                                     /*can_create=*/want_writable_,
+                                     /*can_delete=*/want_writable_);
+  FinishWithResponse(granted_file_entry.filesystem_id,
+                     granted_file_entry.registered_name);
+}
+
+void RequestFileSystemExecutor::FinishWithError(const std::string& error) {
+  std::move(error_callback_).Run(error);
+  responded_ = true;
+}
+
+void RequestFileSystemExecutor::FinishWithResponse(
+    const std::string& filesystem_id,
+    const std::string& registered_name) {
+  std::move(success_callback_).Run(filesystem_id, registered_name);
+  responded_ = true;
+}
+
+}  // namespace
+
+/******** ChromeFileSystemDelegateLacros ********/
+
+ChromeFileSystemDelegateLacros::ChromeFileSystemDelegateLacros() = default;
+
+ChromeFileSystemDelegateLacros::~ChromeFileSystemDelegateLacros() = default;
+
+void ChromeFileSystemDelegateLacros::RequestFileSystem(
+    content::BrowserContext* browser_context,
+    scoped_refptr<ExtensionFunction> requester,
+    const Extension& extension,
+    std::string volume_id,
+    bool writable,
+    FileSystemCallback success_callback,
+    ErrorCallback error_callback) {
+  Profile* profile = Profile::FromBrowserContext(browser_context);
+  // TODO(crbug.com/1351493): Simplify usage for IsGrantable().
+  ConsentProviderDelegate consent_provider_delegate(profile);
+  ConsentProvider consent_provider(&consent_provider_delegate);
+
+  if (writable &&
+      !app_file_handler_util::HasFileSystemWritePermission(&extension)) {
+    std::move(error_callback)
+        .Run(file_system_api::kRequiresFileSystemWriteError);
+    return;
+  }
+
+  if (!consent_provider.IsGrantable(extension)) {
+    std::move(error_callback)
+        .Run(file_system_api::kNotSupportedOnNonKioskSessionError);
+    return;
+  }
+
+  auto* lacros_service = chromeos::LacrosService::Get();
+  DCHECK(lacros_service);
+  if (!lacros_service->IsAvailable<crosapi::mojom::VolumeManager>()) {
+    std::move(error_callback).Run(kApiUnavailableError);
+    return;
+  }
+
+  // The executor object is kept alive by its presence in callbacks, and
+  // deleted when callbacks are invoked or cleared.
+  scoped_refptr<RequestFileSystemExecutor> executor =
+      new RequestFileSystemExecutor(profile, requester, volume_id, writable,
+                                    std::move(success_callback),
+                                    std::move(error_callback));
+  executor->Run(lacros_service);
+}
+
+void ChromeFileSystemDelegateLacros::GetVolumeList(
+    content::BrowserContext* /*browser_context*/,
+    VolumeListCallback success_callback,
+    ErrorCallback error_callback) {
+  auto* lacros_service = chromeos::LacrosService::Get();
+  DCHECK(lacros_service);
+  if (!lacros_service->IsAvailable<crosapi::mojom::VolumeManager>()) {
+    std::move(error_callback).Run(kApiUnavailableError);
+    return;
+  }
+
+  lacros_service->GetRemote<crosapi::mojom::VolumeManager>()->GetFullVolumeList(
+      base::BindOnce(
+          [](VolumeListCallback success_callback,
+             std::vector<crosapi::mojom::VolumePtr> src_volume_list) {
+            std::vector<file_system::Volume> filtered_volume_list;
+            ConvertAndFilterMojomToVolumeList(src_volume_list,
+                                              &filtered_volume_list);
+            std::move(success_callback).Run(filtered_volume_list);
+          },
+          std::move(success_callback)));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h
new file mode 100644
index 0000000..8fb3fbe6
--- /dev/null
+++ b/chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h
@@ -0,0 +1,61 @@
+// 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 CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_
+#define CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_
+
+#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "chromeos/crosapi/mojom/volume_manager.mojom.h"
+#include "extensions/browser/extension_function.h"
+
+namespace content {
+class BrowserContext;
+}  // namespace content
+
+namespace extensions {
+
+class Extension;
+
+namespace file_system_api {
+
+// Dispatches an event about a mounted or unmounted volume in the system to
+// each extension which can request it.
+void DispatchVolumeListChangeEventLacros(
+    content::BrowserContext* browser_context,
+    const std::vector<crosapi::mojom::VolumePtr>& volume_list);
+
+}  // namespace file_system_api
+
+class ChromeFileSystemDelegateLacros : public ChromeFileSystemDelegate {
+ public:
+  ChromeFileSystemDelegateLacros();
+
+  ChromeFileSystemDelegateLacros(const ChromeFileSystemDelegateLacros&) =
+      delete;
+  ChromeFileSystemDelegateLacros& operator=(
+      const ChromeFileSystemDelegateLacros&) = delete;
+
+  ~ChromeFileSystemDelegateLacros() override;
+
+  // ChromeFileSystemDelegate:
+  void RequestFileSystem(content::BrowserContext* browser_context,
+                         scoped_refptr<ExtensionFunction> requester,
+                         const Extension& extension,
+                         std::string volume_id,
+                         bool writable,
+                         FileSystemCallback success_callback,
+                         ErrorCallback error_callback) override;
+  void GetVolumeList(content::BrowserContext* browser_context,
+                     VolumeListCallback success_callback,
+                     ErrorCallback error_callback) override;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_FILE_SYSTEM_CHROME_FILE_SYSTEM_DELEGATE_LACROS_H_
diff --git a/chrome/browser/extensions/api/file_system/consent_provider.h b/chrome/browser/extensions/api/file_system/consent_provider.h
index 7ec3a81..1293e803 100644
--- a/chrome/browser/extensions/api/file_system/consent_provider.h
+++ b/chrome/browser/extensions/api/file_system/consent_provider.h
@@ -29,8 +29,8 @@
 // TestingConsentProviderDelegate.
 // This class may post callbacks given to it, but does not asynchronously call
 // itself. It is generally safe to use a temporary ConsentProvider.
-// TODO(michaelpg): Make this easier to use by replacing member functions with
-// static methods.
+// TODO(crbug.com/1351493): Make this easier to use, perhaps by replacing member
+// functions with static methods.
 class ConsentProvider {
  public:
   enum Consent { CONSENT_GRANTED, CONSENT_REJECTED, CONSENT_IMPOSSIBLE };
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 04c1dbc..43c4f0f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -9,7 +9,6 @@
 #include "base/scoped_observation.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_paths.h"
@@ -731,13 +730,13 @@
       << message_;
 }
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS)
 IN_PROC_BROWSER_TEST_F(FileSystemApiTest, RequestFileSystem_NotChromeOS) {
   ASSERT_TRUE(RunExtensionTest(
       "api_test/file_system/request_file_system_not_chromeos",
       {.launch_as_platform_app = true}, {.ignore_manifest_warnings = true}))
       << message_;
 }
-#endif
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc b/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc
index 896ce28..fcfe9c61 100644
--- a/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc
+++ b/chrome/browser/extensions/api/file_system/volume_list_provider_lacros.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/extensions/api/file_system/volume_list_provider_lacros.h"
 
 #include "base/logging.h"
+#include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate_lacros.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/lacros/lacros_service.h"
 
@@ -26,9 +27,7 @@
 void VolumeListProviderLacros::OnVolumeListChanged(
     std::vector<crosapi::mojom::VolumePtr> volume_list) {
   DCHECK(profile_);
-
-  // TODO(crbug.com/1263204): Add and call
-  // file_system_api::DispatchVolumeListChangeEventLacros().
+  file_system_api::DispatchVolumeListChangeEventLacros(profile_, volume_list);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index cf2a1e5..a18afaf 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -122,9 +122,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void InitNetwork() {
-  const ash::NetworkState* default_network = chromeos::NetworkHandler::Get()
-                                                 ->network_state_handler()
-                                                 ->DefaultNetwork();
+  const ash::NetworkState* default_network =
+      ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
 
   auto* portal_detector = new ash::NetworkPortalDetectorTestImpl();
   portal_detector->SetDefaultNetworkForTesting(default_network->guid());
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 280b012c..31cfe8f 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -335,10 +335,10 @@
   }
 
   void SetupTether() {
-    chromeos::NetworkStateHandler* network_state_handler =
-        chromeos::NetworkHandler::Get()->network_state_handler();
+    ash::NetworkStateHandler* network_state_handler =
+        ash::NetworkHandler::Get()->network_state_handler();
     network_state_handler->SetTetherTechnologyState(
-        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+        ash::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
     network_state_handler->AddTetherNetworkState(
         "tetherGuid1", "tetherName1", "tetherCarrier1",
         50 /* battery_percentage */, 75 /* signal_strength */,
@@ -942,7 +942,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetErrorState) {
-  chromeos::NetworkHandler::Get()->network_state_handler()->SetErrorForTest(
+  ash::NetworkHandler::Get()->network_state_handler()->SetErrorForTest(
       kWifi1ServicePath, "TestErrorState");
   EXPECT_TRUE(RunNetworkingSubtest("getErrorState")) << message_;
 }
@@ -1007,7 +1007,7 @@
                        OnCertificateListsChangedEvent) {
   ExtensionTestMessageListener listener("eventListenerReady");
   listener.SetOnSatisfied(base::BindOnce([](const std::string& message) {
-    chromeos::NetworkHandler::Get()
+    ash::NetworkHandler::Get()
         ->network_certificate_handler()
         ->AddAuthorityCertificateForTest("authority_cert");
   }));
@@ -1117,7 +1117,7 @@
       ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
       base::Value(false));
   global_config.SetKey("SomeNewGlobalPolicy", base::Value(false));
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->managed_network_configuration_handler()
       ->SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY,
                   std::string() /* no username hash */, base::ListValue(),
@@ -1153,7 +1153,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest, GetCertificateLists) {
-  chromeos::NetworkHandler::Get()
+  ash::NetworkHandler::Get()
       ->network_certificate_handler()
       ->AddAuthorityCertificateForTest("authority_cert");
   EXPECT_TRUE(RunNetworkingSubtest("getCertificateLists")) << message_;
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index fd805ae..c2cd6118 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -578,8 +578,8 @@
       "borderedkey", base::FeatureList::IsEnabled(
                          chromeos::features::kVirtualKeyboardBorderedKey)));
   features.Append(GenerateFeatureFlag(
-      "multitouch",
-      base::FeatureList::IsEnabled(features::kVirtualKeyboardMultitouch)));
+      "multitouch", base::FeatureList::IsEnabled(
+                        chromeos::features::kVirtualKeyboardMultitouch)));
   features.Append(GenerateFeatureFlag(
       "roundCorners", base::FeatureList::IsEnabled(
                           chromeos::features::kVirtualKeyboardRoundCorners)));
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index fc516d8c..00c1b61 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1744,11 +1744,6 @@
     "Enable an entry point to Google Lens to allow users to search what they "
     "see using their mobile camera.";
 
-const char kLocationBarModelOptimizationsName[] =
-    "LocationBarModel optimizations";
-const char kLocationBarModelOptimizationsDescription[] =
-    "Cache commonly used data in LocationBarModel to improve performance";
-
 const char kLogJsConsoleMessagesName[] =
     "Log JS console messages in system logs";
 const char kLogJsConsoleMessagesDescription[] =
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index a27fa642..2e81877f 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -249,7 +249,6 @@
     &kKitKatSupported,
     &kLensCameraAssistedSearch,
     &kLensOnQuickActionSearchWidget,
-    &kLocationBarModelOptimizations,
     &kMostRecentTabOnBackgroundCloseTab,
     &kNewInstanceFromDraggedLink,
     &kNewTabPageTilesTitleWrapAround,
@@ -547,7 +546,7 @@
     "CCTResizable90MaximumHeight", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kCCTResizableAllowResizeByUserGesture{
-    "CCTResizableAllowResizeByUserGesture", base::FEATURE_DISABLED_BY_DEFAULT};
+    "CCTResizableAllowResizeByUserGesture", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kCCTResizableForFirstParties{
     "CCTResizableForFirstParties", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -700,9 +699,6 @@
 const base::Feature kKitKatSupported{"KitKatSupported",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kLocationBarModelOptimizations{
-    "LocationBarModelOptimizations", base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kSearchEnginePromoExistingDevice{
     "SearchEnginePromo.ExistingDevice", base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index b725687..cc937bd 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -393,7 +393,6 @@
     public static final String LEAK_DETECTION_UNAUTHENTICATED = "LeakDetectionUnauthenticated";
     public static final String LENS_ON_QUICK_ACTION_SEARCH_WIDGET = "LensOnQuickActionSearchWidget";
     public static final String LIGHTWEIGHT_REACTIONS = "LightweightReactions";
-    public static final String LOCATION_BAR_MODEL_OPTIMIZATIONS = "LocationBarModelOptimizations";
     public static final String LOOKALIKE_NAVIGATION_URL_SUGGESTIONS_UI =
             "LookalikeUrlNavigationSuggestionsUI";
     public static final String MARK_HTTP_AS = "MarkHttpAs";
diff --git a/chrome/browser/lens/OWNERS b/chrome/browser/lens/OWNERS
index be4c83e..7c3e4cc 100644
--- a/chrome/browser/lens/OWNERS
+++ b/chrome/browser/lens/OWNERS
@@ -1,4 +1,5 @@
 benwgold@google.com
 juanmojica@google.com
 sinansahin@google.com
+stanfield@google.com
 twellington@chromium.org
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index 37d6cc9..20395dc2 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -39,7 +39,6 @@
     "//content/public/common",
     "//crypto",
     "//media",
-    "//ui/base:buildflags",
   ]
 }
 
@@ -69,7 +68,6 @@
     "//components/media_router/browser",
     "//components/media_router/common",
     "//components/media_router/common/mojom:media_router",
-    "//components/sessions:sessions",
   ]
   sources = [
     "chrome_media_router_factory.cc",
@@ -80,27 +78,22 @@
 
   if (!is_android) {
     deps += [
+      # We can't depend on //chrome/browser/ui due to introducing a cyclic
+      # dependency. Remove this target from the `allow_circular_includes_from`
+      # list in chrome/browser/ui/BUILD.gn once the issues is resolved.
+      # TODO(crbug.com/1030821): Resolve circular dependencies.
+
       "discovery:discovery",
-      "//build:chromeos_buildflags",
       "//chrome/browser:browser_process",
       "//chrome/browser/profiles:profile",
       "//components/embedder_support:browser_util",
       "//components/mirroring/mojom:host",
       "//components/mirroring/mojom:service",
-
-      # We can't depend on //chrome/browser/ui due to introducing a cyclic
-      # dependency, so we have to depend on this directly to fix include
-      # resolution for browser.h, which is used in media_router_mojo_impl.cc.
-      # TODO(crbug.com/1030821): Resolve circular dependencies
-      "//components/paint_preview/buildflags",
-      "//components/signin/public/base:signin_buildflags",
-      "//components/translate/content/common",
       "//components/ukm/content:content",
       "//components/version_info:version_info",
       "//mojo/public/cpp/bindings",
       "//services/metrics/public/cpp:ukm_builders",
       "//third_party/openscreen/src/cast/common/channel/proto:channel_proto",
-      "//ui/base:buildflags",
     ]
 
     public_deps += [ "//components/media_router/common/mojom:logger" ]
diff --git a/chrome/browser/media/router/discovery/access_code/BUILD.gn b/chrome/browser/media/router/discovery/access_code/BUILD.gn
index 6364bc52..fc29662 100644
--- a/chrome/browser/media/router/discovery/access_code/BUILD.gn
+++ b/chrome/browser/media/router/discovery/access_code/BUILD.gn
@@ -67,6 +67,7 @@
       "//components/signin/public/base:base",
       "//components/signin/public/identity_manager:identity_manager",
       "//components/user_manager:user_manager",
+      "//content/public/browser",
       "//services/preferences/public/cpp:cpp",
     ]
   }
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
index 67ff2c0d6..c59641d1 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
@@ -250,13 +250,10 @@
   // the sink if a new route wasn't established during the pause.
   auto route = GetActiveRoute(sink->id());
 
-  // If a sink is pending expiration that means we can
-  // remove it from the media router.
-  if (!route.has_value() && pending_expirations_.count(sink->id())) {
-    RemoveSinkIdFromAllEntries(sink->id());
-    RemoveAndDisconnectMediaSinkFromRouter(sink);
-    pending_expirations_.erase(sink->id());
-  }
+  // If there is no active route, check manually if the device should be
+  // instantly expired.
+  if (!route.has_value())
+    CheckMediaSinkForExpiration(sink->id());
 }
 
 void AccessCodeCastSinkService::DiscoverSink(const std::string& access_code,
@@ -666,7 +663,6 @@
                 "route has "
                 "ended.",
             sink.id());
-    pending_expirations_.insert(sink.id());
     return;
   }
   RemoveSinkIdFromAllEntries(sink.id());
@@ -814,7 +810,6 @@
   if (!GetAccessCodeCastEnabledPref(prefs_)) {
     RemoveAndDisconnectExistingSinksOnNetwork();
     ResetExpirationTimers();
-    pending_expirations_.clear();
     pref_updater_->ClearDevicesDict();
     pref_updater_->ClearDeviceAddedTimeDict();
   }
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
index 73444ec..2da0c1d 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
@@ -309,21 +309,11 @@
 
   net::BackoffEntry::Policy backoff_policy_;
 
-  // Map of callbacks that we are currently waiting to alert callers about the
-  // completion of discovery. This cannot be done until all routes on any given
-  // sink are terminated.
-  std::map<MediaSink::Id, AddSinkResultCallback> pending_callbacks_;
-
   // Map of sink_ids keyed to a value of expiration timers for the current
   // session (this is updated when the profile session or network is changed).
   std::map<MediaSink::Id, std::unique_ptr<base::OneShotTimer>>
       current_session_expiration_timers_;
 
-  // Set of devices that have expired but still have an open route. These
-  // devices are removed from the media router AND removed from the pref
-  // service.
-  std::set<MediaSink::Id> pending_expirations_;
-
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
   // Raw pointer to DiscoveryNetworkMonitor, which is a global leaky singleton
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
index 41e68a2..cb64d99 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
@@ -219,6 +219,7 @@
   // Test to see that an AccessCode cast sink will be removed after the session
   // is ended.
   mock_time_task_runner()->FastForwardUntilNoTasksRemain();
+  SetDeviceDurationPrefForTest(base::Seconds(10));
 
   // Add a non-access code cast sink to media router and route list.
   MediaSinkInternal cast_sink1 = CreateCastSink(1);
@@ -240,6 +241,11 @@
 
   route_list.push_back(media_route_access);
 
+  mock_cast_media_sink_service_impl()->AddSinkForTest(access_code_sink2);
+  access_code_cast_sink_service_->SetExpirationTimer(&access_code_sink2);
+  access_code_cast_sink_service_->StoreSinkInPrefs(&access_code_sink2);
+  mock_time_task_runner()->FastForwardUntilNoTasksRemain();
+
   // Expect that the removed_route_id_ member variable has not changes since no
   // route was removed.
   access_code_cast_sink_service_->media_routes_observer_->OnRoutesUpdated(
@@ -278,14 +284,15 @@
       media_route_access.media_route_id());
   mock_time_task_runner()->FastForwardUntilNoTasksRemain();
 
+  // Expire the access code cast sink while the route is still active.
+  task_environment_.AdvanceClock(base::Seconds(100));
+
   access_code_cast_sink_service_->HandleMediaRouteRemovedByAccessCode(
       &access_code_sink2);
-  // Expect that there is a pending attempt to examine the sink to see if it
-  // should be expired.
+
   EXPECT_CALL(*mock_cast_media_sink_service_impl(),
               DisconnectAndRemoveSink(access_code_sink2));
-  access_code_cast_sink_service_->pending_expirations_.insert(
-      access_code_sink2.id());
+
   mock_time_task_runner()->FastForwardUntilNoTasksRemain();
 }
 
@@ -856,7 +863,7 @@
   EXPECT_TRUE(access_code_cast_sink_service_
                   ->current_session_expiration_timers_[cast_sink3.id()]
                   ->IsRunning());
-  EXPECT_TRUE(access_code_cast_sink_service_->pending_expirations_.empty());
+
   FastForwardUiAndIoTasks();
   content::RunAllTasksUntilIdle();
 }
diff --git a/chrome/browser/media/router/discovery/dial/dial_service_impl.cc b/chrome/browser/media/router/discovery/dial/dial_service_impl.cc
index 841e6cc..0819bed2 100644
--- a/chrome/browser/media/router/discovery/dial/dial_service_impl.cc
+++ b/chrome/browser/media/router/discovery/dial/dial_service_impl.cc
@@ -139,7 +139,7 @@
 // ChromeOS version can prioritize wifi and ethernet interfaces.
 void InsertBestBindAddressChromeOS(const ash::NetworkTypePattern& type,
                                    net::IPAddressList* bind_address_list) {
-  const ash::NetworkState* state = chromeos::NetworkHandler::Get()
+  const ash::NetworkState* state = ash::NetworkHandler::Get()
                                        ->network_state_handler()
                                        ->ConnectedNetworkByType(type);
   if (!state)
@@ -156,7 +156,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   net::IPAddressList bind_address_list;
-  if (chromeos::NetworkHandler::IsInitialized()) {
+  if (ash::NetworkHandler::IsInitialized()) {
     InsertBestBindAddressChromeOS(ash::NetworkTypePattern::Ethernet(),
                                   &bind_address_list);
     InsertBestBindAddressChromeOS(ash::NetworkTypePattern::WiFi(),
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 0b89d4b..53639fd 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -9,24 +9,14 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/check_op.h"
-#include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
-#include "base/guid.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
 #include "base/observer_list.h"
 #include "base/strings/stringprintf.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/media/cast_mirroring_service_host.h"
 #include "chrome/browser/media/cast_remoting_connector.h"
-#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/router/mojo/media_router_mojo_metrics.h"
 #include "chrome/browser/media/router/mojo/media_sink_service_status.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/grit/chromium_strings.h"
 #include "components/media_router/browser/issues_observer.h"
 #include "components/media_router/browser/media_router_metrics.h"
@@ -35,7 +25,6 @@
 #include "components/media_router/browser/route_message_observer.h"
 #include "components/media_router/common/media_source.h"
 #include "components/media_router/common/providers/cast/cast_media_source.h"
-#include "components/sessions/content/session_tab_helper.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/desktop_streams_registry.h"
@@ -54,37 +43,7 @@
 namespace media_router {
 namespace {
 
-// Get the WebContents associated with the given tab id. Returns nullptr if the
-// tab id is invalid, or if the searching fails.
-// TODO(xjz): Move this to SessionTabHelper to allow it being used by
-// extensions::ExtensionTabUtil::GetTabById() as well.
-content::WebContents* GetWebContentsFromId(
-    int32_t tab_id,
-    content::BrowserContext* browser_context,
-    bool include_incognito) {
-  if (tab_id < 0)
-    return nullptr;
-  Profile* profile = Profile::FromBrowserContext(browser_context);
-  Profile* incognito_profile =
-      include_incognito && profile->HasPrimaryOTRProfile()
-          ? profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
-          : nullptr;
-  for (auto* target_browser : *BrowserList::GetInstance()) {
-    if (target_browser->profile() == profile ||
-        target_browser->profile() == incognito_profile) {
-      TabStripModel* target_tab_strip = target_browser->tab_strip_model();
-      for (int i = 0; i < target_tab_strip->count(); ++i) {
-        content::WebContents* target_contents =
-            target_tab_strip->GetWebContentsAt(i);
-        if (sessions::SessionTabHelper::IdForTab(target_contents).id() ==
-            tab_id) {
-          return target_contents;
-        }
-      }
-    }
-  }
-  return nullptr;
-}
+const int kDefaultFrameTreeNodeId = -1;
 
 DesktopMediaPickerController::Params MakeDesktopPickerParams(
     content::WebContents* web_contents) {
@@ -298,12 +257,12 @@
                        presentation_id, origin, web_contents, timeout,
                        off_the_record, std::move(mr_callback)));
   } else {
-    // Previously the tab ID was set to -1 for non-mirroring sessions, which
-    // mostly works, but it breaks auto-joining.
-    const int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
+    const int frame_tree_node_id =
+        web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
+                     : kDefaultFrameTreeNodeId;
     media_route_providers_[provider_id]->CreateRoute(
-        source_id, sink_id, presentation_id, origin, tab_id, timeout,
-        off_the_record, std::move(mr_callback));
+        source_id, sink_id, presentation_id, origin, frame_tree_node_id,
+        timeout, off_the_record, std::move(mr_callback));
   }
 }
 
@@ -327,13 +286,15 @@
     return;
   }
 
-  int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
+  const int frame_tree_node_id =
+      web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
+                   : kDefaultFrameTreeNodeId;
   auto mr_callback = base::BindOnce(
       &MediaRouterMojoImpl::RouteResponseReceived, weak_factory_.GetWeakPtr(),
       presentation_id, *provider_id, off_the_record, std::move(callback), true);
   media_route_providers_[*provider_id]->JoinRoute(
-      source_id, presentation_id, origin, tab_id, timeout, off_the_record,
-      std::move(mr_callback));
+      source_id, presentation_id, origin, frame_tree_node_id, timeout,
+      off_the_record, std::move(mr_callback));
 }
 
 void MediaRouterMojoImpl::TerminateRoute(const MediaRoute::Id& route_id) {
@@ -784,11 +745,10 @@
 }
 
 void MediaRouterMojoImpl::GetMirroringServiceHostForTab(
-    int32_t target_tab_id,
+    int32_t frame_tree_node_id,
     mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost> receiver) {
   mirroring::CastMirroringServiceHost::GetForTab(
-      GetWebContentsFromId(target_tab_id, context_,
-                           true /* include_incognito */),
+      content::WebContents::FromFrameTreeNodeId(frame_tree_node_id),
       std::move(receiver));
 }
 
@@ -796,7 +756,6 @@
 // but eventually it won't be.  When that happens, change the sigature so it can
 // report errors.  Also remove the |initiator_tab_id| parameter.
 void MediaRouterMojoImpl::GetMirroringServiceHostForDesktop(
-    int32_t initiator_tab_id,
     const std::string& desktop_stream_id,
     mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost> receiver) {
   if (!pending_stream_request_ ||
@@ -1000,7 +959,8 @@
 
   media_route_providers_[provider_id]->CreateRoute(
       MediaSource::ForDesktop(request.stream_id, media_id.audio_share).id(),
-      sink_id, presentation_id, origin, -1, timeout, off_the_record,
+      sink_id, presentation_id, origin, kDefaultFrameTreeNodeId, timeout,
+      off_the_record,
       base::BindOnce(
           [](mojom::MediaRouteProvider::CreateRouteCallback inner_callback,
              base::WeakPtr<MediaRouterMojoImpl> self,
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.h b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
index b05f0e17..af09b14 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.h
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
@@ -327,11 +327,10 @@
   void GetMediaSinkServiceStatus(
       mojom::MediaRouter::GetMediaSinkServiceStatusCallback callback) override;
   void GetMirroringServiceHostForTab(
-      int32_t target_tab_id,
+      int32_t frame_tree_node_id,
       mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost> receiver)
       override;
   void GetMirroringServiceHostForDesktop(
-      int32_t initiator_tab_id,
       const std::string& desktop_stream_id,
       mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost> receiver)
       override;
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
index e9f3b50..f55392a 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
@@ -83,7 +83,7 @@
 const char kSinkName[] = "sinkName";
 const char kPresentationId[] = "presentationId";
 const char kOrigin[] = "http://origin/";
-const int kInvalidTabId = -1;
+const int kInvalidFrameNodeId = -1;
 const int kTimeoutMillis = 5 * 1000;
 
 IssueInfo CreateIssueInfo(const std::string& title) {
@@ -265,16 +265,13 @@
   EXPECT_CALL(mock_cast_provider_,
               CreateRouteInternal(kSource, kSinkId, _,
                                   url::Origin::Create(GURL(kOrigin)),
-                                  kInvalidTabId, _, _, _))
-      .WillOnce(Invoke([&expected_route](
-                           const std::string& source, const std::string& sink,
-                           const std::string& presentation_id,
-                           const url::Origin& origin, int tab_id,
-                           base::TimeDelta timeout, bool off_the_record,
-                           mojom::MediaRouteProvider::CreateRouteCallback& cb) {
-        std::move(cb).Run(expected_route, nullptr, std::string(),
-                          mojom::RouteRequestResultCode::OK);
-      }));
+                                  kInvalidFrameNodeId, _, _, _))
+      .WillOnce(WithArg<7>(
+          Invoke([&expected_route](
+                     mojom::MediaRouteProvider::CreateRouteCallback& cb) {
+            std::move(cb).Run(expected_route, nullptr, std::string(),
+                              mojom::RouteRequestResultCode::OK);
+          })));
 
   base::RunLoop run_loop;
   RouteResponseCallbackHandler handler;
@@ -294,17 +291,14 @@
 TEST_F(MediaRouterMojoImplTest, CreateRouteFails) {
   ProvideTestSink(mojom::MediaRouteProviderId::CAST, kSinkId);
   EXPECT_CALL(mock_cast_provider_,
-              CreateRouteInternal(
-                  kSource, kSinkId, _, url::Origin::Create(GURL(kOrigin)),
-                  kInvalidTabId, base::Milliseconds(kTimeoutMillis), _, _))
-      .WillOnce(Invoke([](const std::string& source, const std::string& sink,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool off_the_record,
-                          mojom::MediaRouteProvider::CreateRouteCallback& cb) {
-        std::move(cb).Run(absl::nullopt, nullptr, std::string(kError),
-                          mojom::RouteRequestResultCode::TIMED_OUT);
-      }));
+              CreateRouteInternal(kSource, kSinkId, _,
+                                  url::Origin::Create(GURL(kOrigin)),
+                                  kInvalidFrameNodeId, _, _, _))
+      .WillOnce(WithArg<7>(
+          Invoke([](mojom::MediaRouteProvider::CreateRouteCallback& cb) {
+            std::move(cb).Run(absl::nullopt, nullptr, std::string(kError),
+                              mojom::RouteRequestResultCode::TIMED_OUT);
+          })));
 
   RouteResponseCallbackHandler handler;
   base::RunLoop run_loop;
@@ -324,17 +318,14 @@
 TEST_F(MediaRouterMojoImplTest, CreateRouteIncognitoMismatchFails) {
   ProvideTestSink(mojom::MediaRouteProviderId::CAST, kSinkId);
   EXPECT_CALL(mock_cast_provider_,
-              CreateRouteInternal(
-                  kSource, kSinkId, _, url::Origin::Create(GURL(kOrigin)),
-                  kInvalidTabId, base::Milliseconds(kTimeoutMillis), true, _))
-      .WillOnce(Invoke([](const std::string& source, const std::string& sink,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool off_the_record,
-                          mojom::MediaRouteProvider::CreateRouteCallback& cb) {
-        std::move(cb).Run(CreateMediaRoute(), nullptr, std::string(),
-                          mojom::RouteRequestResultCode::OK);
-      }));
+              CreateRouteInternal(kSource, kSinkId, _,
+                                  url::Origin::Create(GURL(kOrigin)),
+                                  kInvalidFrameNodeId, _, _, _))
+      .WillOnce(WithArg<7>(
+          Invoke([](mojom::MediaRouteProvider::CreateRouteCallback& cb) {
+            std::move(cb).Run(CreateMediaRoute(), nullptr, std::string(),
+                              mojom::RouteRequestResultCode::OK);
+          })));
 
   RouteResponseCallbackHandler handler;
   base::RunLoop run_loop;
@@ -384,18 +375,16 @@
   UpdateRoutes(mojom::MediaRouteProviderId::CAST, routes);
   EXPECT_TRUE(router()->HasJoinableRoute());
 
-  EXPECT_CALL(mock_cast_provider_,
-              JoinRouteInternal(
-                  kSource, kPresentationId, url::Origin::Create(GURL(kOrigin)),
-                  kInvalidTabId, base::Milliseconds(kTimeoutMillis), _, _))
-      .WillOnce(Invoke([](const std::string& source,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool off_the_record,
-                          mojom::MediaRouteProvider::JoinRouteCallback& cb) {
-        std::move(cb).Run(absl::nullopt, nullptr, std::string(kError),
-                          mojom::RouteRequestResultCode::TIMED_OUT);
-      }));
+  EXPECT_CALL(
+      mock_cast_provider_,
+      JoinRouteInternal(kSource, kPresentationId,
+                        url::Origin::Create(GURL(kOrigin)), kInvalidFrameNodeId,
+                        base::Milliseconds(kTimeoutMillis), _, _))
+      .WillOnce(WithArg<6>(
+          Invoke([](mojom::MediaRouteProvider::JoinRouteCallback& cb) {
+            std::move(cb).Run(absl::nullopt, nullptr, std::string(kError),
+                              mojom::RouteRequestResultCode::TIMED_OUT);
+          })));
 
   RouteResponseCallbackHandler handler;
   base::RunLoop run_loop;
@@ -424,19 +413,16 @@
   // Use a lambda function as an invocation target here to work around
   // a limitation with GMock::Invoke that prevents it from using move-only types
   // in runnable parameter lists.
-  EXPECT_CALL(mock_cast_provider_,
-              JoinRouteInternal(
-                  kSource, kPresentationId, url::Origin::Create(GURL(kOrigin)),
-                  kInvalidTabId, base::Milliseconds(kTimeoutMillis), true, _))
-      .WillOnce(
-          Invoke([&route](const std::string& source,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool off_the_record,
-                          mojom::MediaRouteProvider::JoinRouteCallback& cb) {
+  EXPECT_CALL(
+      mock_cast_provider_,
+      JoinRouteInternal(kSource, kPresentationId,
+                        url::Origin::Create(GURL(kOrigin)), kInvalidFrameNodeId,
+                        base::Milliseconds(kTimeoutMillis), _, _))
+      .WillOnce(WithArg<6>(
+          Invoke([&route](mojom::MediaRouteProvider::JoinRouteCallback& cb) {
             std::move(cb).Run(route, nullptr, std::string(),
                               mojom::RouteRequestResultCode::OK);
-          }));
+          })));
 
   RouteResponseCallbackHandler handler;
   base::RunLoop run_loop;
diff --git a/chrome/browser/media/router/providers/cast/app_activity.cc b/chrome/browser/media/router/providers/cast/app_activity.cc
index 492450b..fdc1457 100644
--- a/chrome/browser/media/router/providers/cast/app_activity.cc
+++ b/chrome/browser/media/router/providers/cast/app_activity.cc
@@ -160,12 +160,13 @@
 
 bool AppActivity::HasJoinableClient(AutoJoinPolicy policy,
                                     const url::Origin& origin,
-                                    int tab_id) const {
+                                    int frame_tree_node_id) const {
   return std::any_of(connected_clients_.begin(), connected_clients_.end(),
-                     [policy, &origin, tab_id](const auto& client) {
-                       return IsAutoJoinAllowed(policy, origin, tab_id,
-                                                client.second->origin(),
-                                                client.second->tab_id());
+                     [policy, &origin, frame_tree_node_id](const auto& client) {
+                       return IsAutoJoinAllowed(
+                           policy, origin, frame_tree_node_id,
+                           client.second->origin(),
+                           client.second->frame_tree_node_id());
                      });
 }
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity.cc b/chrome/browser/media/router/providers/cast/cast_activity.cc
index 3665226..c4c47cd 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity.cc
@@ -30,15 +30,16 @@
 mojom::RoutePresentationConnectionPtr CastActivity::AddClient(
     const CastMediaSource& source,
     const url::Origin& origin,
-    int tab_id) {
+    int frame_tree_node_id) {
   const std::string& client_id = source.client_id();
   DCHECK(!base::Contains(connected_clients_, client_id));
   std::unique_ptr<CastSessionClient> client =
       client_factory_for_test_
-          ? client_factory_for_test_->MakeClientForTest(client_id, origin,
-                                                        tab_id)
+          ? client_factory_for_test_->MakeClientForTest(  // IN-TEST
+                client_id, origin, frame_tree_node_id)
           : std::make_unique<CastSessionClientImpl>(
-                client_id, origin, tab_id, source.auto_join_policy(), this);
+                client_id, origin, frame_tree_node_id,
+                source.auto_join_policy(), this);
   auto presentation_connection = client->Init();
   connected_clients_.emplace(client_id, std::move(client));
 
@@ -167,7 +168,8 @@
   auto& client = *client_it->second;
   std::vector<std::string> leaving_client_ids;
   for (const auto& pair : connected_clients_) {
-    if (pair.second->MatchesAutoJoinPolicy(client.origin(), client.tab_id()))
+    if (pair.second->MatchesAutoJoinPolicy(client.origin(),
+                                           client.frame_tree_node_id()))
       leaving_client_ids.push_back(pair.first);
   }
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity.h b/chrome/browser/media/router/providers/cast/cast_activity.h
index 604261c..ec308a04 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity.h
@@ -55,7 +55,6 @@
   const MediaRoute& route() const { return route_; }
   const std::string& app_id() const { return app_id_; }
   const absl::optional<std::string>& session_id() const { return session_id_; }
-  absl::optional<int> mirroring_tab_id() const { return mirroring_tab_id_; }
   const MediaSinkInternal sink() const { return sink_; }
 
   void SetRouteIsConnecting(bool is_connecting);
@@ -66,7 +65,7 @@
   virtual mojom::RoutePresentationConnectionPtr AddClient(
       const CastMediaSource& source,
       const url::Origin& origin,
-      int tab_id);
+      int frame_tree_node_id);
 
   virtual void RemoveClient(const std::string& client_id);
 
@@ -174,7 +173,6 @@
 
   MediaRoute route_;
   std::string app_id_;
-  absl::optional<int> mirroring_tab_id_;
 
   // TODO(https://crbug.com/809249): Consider wrapping CastMessageHandler with
   // known parameters (sink, client ID, session transport ID) and passing them
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
index c7a10818..02a61d6 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
@@ -87,21 +87,21 @@
     const MediaSinkInternal& sink,
     const std::string& presentation_id,
     const url::Origin& origin,
-    int tab_id,
+    int frame_tree_node_id,
     bool off_the_record,
     mojom::MediaRouteProvider::CreateRouteCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (cast_source.app_params().empty()) {
-    LaunchSessionParsed(cast_source, sink, presentation_id, origin, tab_id,
-                        off_the_record, std::move(callback),
+    LaunchSessionParsed(cast_source, sink, presentation_id, origin,
+                        frame_tree_node_id, off_the_record, std::move(callback),
                         data_decoder::DataDecoder::ValueOrError());
   } else {
     GetDataDecoder().ParseJson(
         cast_source.app_params(),
         base::BindOnce(&CastActivityManager::LaunchSessionParsed,
                        weak_ptr_factory_.GetWeakPtr(), cast_source, sink,
-                       presentation_id, origin, tab_id, off_the_record,
-                       std::move(callback)));
+                       presentation_id, origin, frame_tree_node_id,
+                       off_the_record, std::move(callback)));
   }
 }
 
@@ -110,7 +110,7 @@
     const MediaSinkInternal& sink,
     const std::string& presentation_id,
     const url::Origin& origin,
-    int tab_id,
+    int frame_tree_node_id,
     bool off_the_record,
     mojom::MediaRouteProvider::CreateRouteCallback callback,
     data_decoder::DataDecoder::ValueOrError result) {
@@ -151,8 +151,9 @@
   if (result.has_value() && !result->is_none())
     opt_result = std::move(*result);
 
-  DoLaunchSessionParams params(route, cast_source, sink, origin, tab_id,
-                               std::move(opt_result), std::move(callback));
+  DoLaunchSessionParams params(route, cast_source, sink, origin,
+                               frame_tree_node_id, std::move(opt_result),
+                               std::move(callback));
 
   // If there is currently a session on the sink, it must be terminated before
   // the new session can be launched.
@@ -182,7 +183,7 @@
   const MediaRoute::Id& route_id = route.media_route_id();
   const CastMediaSource& cast_source = params.cast_source;
   const MediaSinkInternal& sink = params.sink;
-  const int tab_id = params.tab_id;
+  const int frame_tree_node_id = params.frame_tree_node_id;
   std::string app_id = ChooseAppId(cast_source, params.sink);
   auto app_params = std::move(params.app_params);
 
@@ -194,17 +195,18 @@
       cast_source.supported_app_types());
 
   cast_source.ContainsStreamingApp()
-      ? AddMirroringActivity(route, app_id, tab_id, sink.cast_data())
+      ? AddMirroringActivity(route, app_id, frame_tree_node_id,
+                             sink.cast_data())
       : AddAppActivity(route, app_id);
 
-  if (tab_id != -1) {
-    // If there is a route from this tab already, stop it.
-    auto route_it = routes_by_tab_.find(tab_id);
-    if (route_it != routes_by_tab_.end()) {
+  if (frame_tree_node_id != -1) {
+    // If there is a route from this frame already, stop it.
+    auto route_it = routes_by_frame_.find(frame_tree_node_id);
+    if (route_it != routes_by_frame_.end()) {
       TerminateSession(route_it->second, base::DoNothing());
     }
 
-    routes_by_tab_[tab_id] = route_id;
+    routes_by_frame_[frame_tree_node_id] = route_id;
   }
 
   NotifyAllOnRoutesUpdated();
@@ -261,7 +263,7 @@
 AppActivity* CastActivityManager::FindActivityForAutoJoin(
     const CastMediaSource& cast_source,
     const url::Origin& origin,
-    int tab_id) {
+    int frame_tree_node_id) {
   switch (cast_source.auto_join_policy()) {
     case AutoJoinPolicy::kTabAndOriginScoped:
     case AutoJoinPolicy::kOriginScoped:
@@ -270,17 +272,17 @@
       return nullptr;
   }
 
-  auto it =
-      std::find_if(app_activities_.begin(), app_activities_.end(),
-                   [&cast_source, &origin, tab_id](const auto& pair) {
-                     AutoJoinPolicy policy = cast_source.auto_join_policy();
-                     const AppActivity* activity = pair.second;
-                     if (!activity->route().is_local())
-                       return false;
-                     if (!cast_source.ContainsApp(activity->app_id()))
-                       return false;
-                     return activity->HasJoinableClient(policy, origin, tab_id);
-                   });
+  auto it = std::find_if(
+      app_activities_.begin(), app_activities_.end(),
+      [&cast_source, &origin, frame_tree_node_id](const auto& pair) {
+        AutoJoinPolicy policy = cast_source.auto_join_policy();
+        const AppActivity* activity = pair.second;
+        if (!activity->route().is_local())
+          return false;
+        if (!cast_source.ContainsApp(activity->app_id()))
+          return false;
+        return activity->HasJoinableClient(policy, origin, frame_tree_node_id);
+      });
   return it == app_activities_.end() ? nullptr : it->second;
 }
 
@@ -288,18 +290,18 @@
     const CastMediaSource& cast_source,
     const std::string& presentation_id,
     const url::Origin& origin,
-    int tab_id,
+    int frame_tree_node_id,
     bool off_the_record,
     mojom::MediaRouteProvider::JoinRouteCallback callback) {
   AppActivity* activity = nullptr;
   if (presentation_id == kAutoJoinPresentationId) {
-    activity = FindActivityForAutoJoin(cast_source, origin, tab_id);
+    activity = FindActivityForAutoJoin(cast_source, origin, frame_tree_node_id);
     if (!activity && cast_source.default_action_policy() !=
                          DefaultActionPolicy::kCastThisTab) {
-      auto sink = ConvertMirrorToCast(tab_id);
+      auto sink = ConvertMirrorToCast(frame_tree_node_id);
       if (sink) {
-        LaunchSession(cast_source, *sink, presentation_id, origin, tab_id,
-                      off_the_record, std::move(callback));
+        LaunchSession(cast_source, *sink, presentation_id, origin,
+                      frame_tree_node_id, off_the_record, std::move(callback));
         return;
       }
     }
@@ -328,7 +330,7 @@
   }
 
   mojom::RoutePresentationConnectionPtr presentation_connection =
-      activity->AddClient(cast_source, origin, tab_id);
+      activity->AddClient(cast_source, origin, frame_tree_node_id);
 
   if (!activity->session_id()) {
     // This should never happen, but it looks like maybe it does.  See
@@ -405,7 +407,7 @@
       DLOG(ERROR) << "Invalid state: " << state;
   }
 
-  base::EraseIf(routes_by_tab_, [activity_it](const auto& pair) {
+  base::EraseIf(routes_by_frame_, [activity_it](const auto& pair) {
     return pair.second == activity_it->first;
   });
   app_activities_.erase(activity_it->first);
@@ -439,8 +441,8 @@
   // There is no session associated with the route, e.g. the launch request is
   // still pending.
   if (!session_id) {
-    // |route_id| might be a reference to the item in |routes_by_tab_|.
-    // RemoveActivity() deletes this item in |routes_by_tab_| and invalidates
+    // |route_id| might be a reference to the item in |routes_by_frame_|.
+    // RemoveActivity() deletes this item in |routes_by_frame_| and invalidates
     // |route_id|.
     RemoveActivity(activity_it, PresentationConnectionState::TERMINATED,
                    PresentationConnectionCloseReason::CLOSED);
@@ -511,7 +513,7 @@
 CastActivity* CastActivityManager::AddMirroringActivity(
     const MediaRoute& route,
     const std::string& app_id,
-    const int tab_id,
+    const int frame_tree_node_id,
     const CastSinkExtraData& cast_data) {
   // We could theoretically use base::Unretained() below instead of
   // GetWeakPtr(), but that seems like an unnecessary optimization here.
@@ -523,7 +525,7 @@
                             route, app_id, std::move(on_stop))
                       : std::make_unique<MirroringActivity>(
                             route, app_id, message_handler_, session_tracker_,
-                            tab_id, cast_data, std::move(on_stop));
+                            frame_tree_node_id, cast_data, std::move(on_stop));
   activity->CreateMojoBindings(media_router_);
   auto* const activity_ptr = activity.get();
   activities_.emplace(route.media_route_id(), std::move(activity));
@@ -638,19 +640,6 @@
                         std::move(callback));
 }
 
-const MediaRoute* CastActivityManager::FindMirroringRouteForTab(
-    int32_t tab_id) {
-  for (const auto& entry : activities_) {
-    const std::string& route_id = entry.first;
-    const CastActivity& activity = *entry.second;
-    if (activity.mirroring_tab_id() == tab_id &&
-        !base::Contains(app_activities_, route_id)) {
-      return &activity.route();
-    }
-  }
-  return nullptr;
-}
-
 void CastActivityManager::SendRouteMessage(const std::string& media_route_id,
                                            const std::string& message) {
   GetDataDecoder().ParseJson(
@@ -812,7 +801,7 @@
 
   if (MediaSource(cast_source.source_id()).IsCastPresentationUrl()) {
     presentation_connection = activity_it->second->AddClient(
-        cast_source, params.origin, params.tab_id);
+        cast_source, params.origin, params.frame_tree_node_id);
     if (!client_id.empty()) {
       activity_it->second->SendMessageToClient(
           client_id,
@@ -880,8 +869,8 @@
       MediaRoute::GetPresentationIdFromMediaRouteId(route_id);
 
   if (result == cast_channel::Result::kOk) {
-    // |route_id| might be a reference to the item in |routes_by_tab_|.
-    // RemoveActivity() deletes this item in |routes_by_tab_| and invalidates
+    // |route_id| might be a reference to the item in |routes_by_frame_|.
+    // RemoveActivity() deletes this item in |routes_by_frame_| and invalidates
     // |route_id|.
     RemoveActivity(activity_it, PresentationConnectionState::TERMINATED,
                    PresentationConnectionCloseReason::CLOSED);
@@ -947,13 +936,17 @@
 }
 
 absl::optional<MediaSinkInternal> CastActivityManager::ConvertMirrorToCast(
-    int tab_id) {
-  for (const auto& pair : activities_) {
-    if (pair.second->mirroring_tab_id() == tab_id) {
-      return pair.second->sink();
-    }
+    int frame_tree_node_id) {
+  auto route_it = routes_by_frame_.find(frame_tree_node_id);
+  if (route_it == routes_by_frame_.end()) {
+    return absl::nullopt;
   }
 
+  const MediaRoute::Id& route_id = route_it->second;
+  if (activities_.find(route_id) != activities_.end() &&
+      app_activities_.find(route_id) == app_activities_.end()) {
+    return activities_.find(route_id)->second->sink();
+  }
   return absl::nullopt;
 }
 
@@ -994,14 +987,14 @@
     const CastMediaSource& cast_source,
     const MediaSinkInternal& sink,
     const url::Origin& origin,
-    int tab_id,
+    int frame_tree_node_id,
     const absl::optional<base::Value> app_params,
     mojom::MediaRouteProvider::CreateRouteCallback callback)
     : route(route),
       cast_source(cast_source),
       sink(sink),
       origin(origin),
-      tab_id(tab_id),
+      frame_tree_node_id(frame_tree_node_id),
       callback(std::move(callback)) {
   if (app_params)
     this->app_params = app_params->Clone();
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.h b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
index cd18c2127..3a38cf9 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
@@ -80,14 +80,14 @@
                      const MediaSinkInternal& sink,
                      const std::string& presentation_id,
                      const url::Origin& origin,
-                     int tab_id,
+                     int frame_tree_node_id,
                      bool incognito,
                      mojom::MediaRouteProvider::CreateRouteCallback callback);
 
   void JoinSession(const CastMediaSource& cast_source,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int tab_id,
+                   int frame_tree_node_id,
                    bool incognito,
                    mojom::MediaRouteProvider::JoinRouteCallback callback);
 
@@ -128,8 +128,6 @@
       const std::string& route_id,
       mojom::MediaRouteProvider::TerminateRouteCallback callback) override;
 
-  const MediaRoute* FindMirroringRouteForTab(int32_t tab_id);
-
   void SendRouteMessage(const std::string& media_route_id,
                         const std::string& message);
 
@@ -156,7 +154,7 @@
       const MediaSinkInternal& sink,
       const std::string& presentation_id,
       const url::Origin& origin,
-      int tab_id,
+      int frame_tree_node_id,
       bool incognito,
       mojom::MediaRouteProvider::CreateRouteCallback callback,
       data_decoder::DataDecoder::ValueOrError result);
@@ -171,7 +169,7 @@
         const CastMediaSource& cast_source,
         const MediaSinkInternal& sink,
         const url::Origin& origin,
-        int tab_id,
+        int frame_tree_node_id,
         const absl::optional<base::Value> app_params,
         mojom::MediaRouteProvider::CreateRouteCallback callback);
     DoLaunchSessionParams(const DoLaunchSessionParams& other) = delete;
@@ -192,8 +190,9 @@
     // The origin of the Cast SDK client. Used for auto-join.
     url::Origin origin;
 
-    // The tab ID of the Cast SDK client. Used for auto-join.
-    int tab_id;
+    // The FrameTreeNodeId of the WebContents of the Cast SDK client. Used for
+    // Mirroring and auto-join.
+    int frame_tree_node_id;
 
     // The JSON object sent from the Cast SDK.
     absl::optional<base::Value> app_params;
@@ -241,7 +240,7 @@
 
   AppActivity* FindActivityForAutoJoin(const CastMediaSource& cast_source,
                                        const url::Origin& origin,
-                                       int tab_id);
+                                       int frame_tree_node_id);
   bool CanJoinSession(const AppActivity& activity,
                       const CastMediaSource& cast_source,
                       bool incognito) const;
@@ -264,12 +263,12 @@
                               const std::string& app_id);
   CastActivity* AddMirroringActivity(const MediaRoute& route,
                                      const std::string& app_id,
-                                     int tab_id,
+                                     const int frame_tree_node_id,
                                      const CastSinkExtraData& cast_data);
 
   // Returns a sink used to convert a mirroring activity to a cast activity.
   // If no conversion should occur, returns absl::nullopt.
-  absl::optional<MediaSinkInternal> ConvertMirrorToCast(int tab_id);
+  absl::optional<MediaSinkInternal> ConvertMirrorToCast(int frame_tree_node_id);
 
   std::string ChooseAppId(const CastMediaSource& source,
                           const MediaSinkInternal& sink) const;
@@ -286,12 +285,13 @@
   // there is a AppActivity.
   AppActivityMap app_activities_;
 
-  // Mapping from tab IDs to the active route for that tab.  This map is used to
-  // ensure that there is at most one active route for each tab.  Removing this
-  // map and the code that uses it will allow a tab to be cast to multiple
-  // receivers, but there may be unintended consequences, such as confusing
-  // users or causing performance problems on low-end devices.
-  base::flat_map<int, MediaRoute::Id> routes_by_tab_;
+  // Mapping from FrameTreeNode IDs to the active routes for that main frame.
+  // This map is used to ensure that there is at most one active route for each
+  // main frame. Removing this map and the code that uses it will allow a
+  // main frame to be cast to multiple receivers, but there may be unintended
+  // consequences, such as confusing users or causing performance problems on
+  // low-end devices.
+  base::flat_map<int, MediaRoute::Id> routes_by_frame_;
 
   // Information for a session that will be launched once |this| is notified
   // that the existing session on the receiver has been removed. We only store
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
index 5053dc1..cefd8f0 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
@@ -63,8 +63,8 @@
 constexpr int kChannelId2 = 43;
 constexpr char kClientId[] = "theClientId";
 constexpr char kOrigin[] = "https://google.com";
-constexpr int kTabId = 1;
-constexpr int kTabId2 = 2;
+constexpr int kFrameTreeNodeId = 1;
+constexpr int kFrameTreeNodeId2 = 2;
 constexpr char kAppId1[] = "ABCDEFGH";
 constexpr char kAppId2[] = "BBBBBBBB";
 constexpr char kAppParams[] = R"(
@@ -263,7 +263,8 @@
     ASSERT_TRUE(source);
 
     // Callback needs to be invoked by running |launch_session_callback_|.
-    manager_->LaunchSession(*source, sink_, kPresentationId, origin_, kTabId,
+    manager_->LaunchSession(*source, sink_, kPresentationId, origin_,
+                            kFrameTreeNodeId,
                             /*incognito*/ false, std::move(callback));
 
     RunUntilIdle();
@@ -585,7 +586,7 @@
 
   // Callback will be invoked synchronously.
   manager_->LaunchSession(
-      *source, sink_, kPresentationId, origin_, kTabId,
+      *source, sink_, kPresentationId, origin_, kFrameTreeNodeId,
       /*incognito*/ false,
       base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionFailure,
                      base::Unretained(this)));
@@ -615,7 +616,8 @@
   // LaunchSessionParsed() is called asynchronously and will fail the test.
   manager_->LaunchSessionParsed(
       // TODO(crbug.com/1291744): Verify that presentation ID is used correctly.
-      *source, sink_, kPresentationId2, origin_, kTabId2, /*incognito*/
+      *source, sink_, kPresentationId2, origin_,
+      kFrameTreeNodeId2, /*incognito*/
       false,
       base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionSuccess,
                      base::Unretained(this)),
@@ -644,7 +646,8 @@
   // Use LaunchSessionParsed() instead of LaunchSession() here because
   // LaunchSessionParsed() is called asynchronously and will fail the test.
   manager_->LaunchSessionParsed(
-      *source, sink2_, kPresentationId2, origin_, kTabId, /*incognito*/
+      *source, sink2_, kPresentationId2, origin_,
+      kFrameTreeNodeId, /*incognito*/
       false,
       base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionSuccess,
                      base::Unretained(this)),
@@ -661,7 +664,8 @@
   // Use LaunchSessionParsed() instead of LaunchSession() here because
   // LaunchSessionParsed() is called asynchronously and will fail the test.
   manager_->LaunchSessionParsed(
-      *source, sink2_, kPresentationId2, origin_, kTabId, /*incognito*/
+      *source, sink2_, kPresentationId2, origin_,
+      kFrameTreeNodeId, /*incognito*/
       false,
       base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionSuccess,
                      base::Unretained(this)),
@@ -803,7 +807,8 @@
         EXPECT_EQ(mojom::RouteRequestResultCode::CANCELLED, code);
       }));
   for (int i = 0; i < 2; i++) {
-    manager_->LaunchSession(*source, sink_, kPresentationId, origin_, kTabId,
+    manager_->LaunchSession(*source, sink_, kPresentationId, origin_,
+                            kFrameTreeNodeId,
                             /*incognito*/ false,
                             base::BindOnce(&MockLaunchSessionCallback::Run,
                                            base::Unretained(&callback)));
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc b/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
index fd1afef5..97ed863 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
@@ -106,7 +106,7 @@
                                          const std::string& sink_id,
                                          const std::string& presentation_id,
                                          const url::Origin& origin,
-                                         int32_t tab_id,
+                                         int32_t frame_tree_node_id,
                                          base::TimeDelta timeout,
                                          bool incognito,
                                          CreateRouteCallback callback) {
@@ -136,15 +136,15 @@
         mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER);
     return;
   }
-
   activity_manager_->LaunchSession(*cast_source, *sink, presentation_id, origin,
-                                   tab_id, incognito, std::move(callback));
+                                   frame_tree_node_id, incognito,
+                                   std::move(callback));
 }
 
 void CastMediaRouteProvider::JoinRoute(const std::string& media_source,
                                        const std::string& presentation_id,
                                        const url::Origin& origin,
-                                       int32_t tab_id,
+                                       int32_t frame_tree_node_id,
                                        base::TimeDelta timeout,
                                        bool incognito,
                                        JoinRouteCallback callback) {
@@ -175,9 +175,9 @@
                             mojom::RouteRequestResultCode::UNKNOWN_ERROR);
     return;
   }
-
-  activity_manager_->JoinSession(*cast_source, presentation_id, origin, tab_id,
-                                 incognito, std::move(callback));
+  activity_manager_->JoinSession(*cast_source, presentation_id, origin,
+                                 frame_tree_node_id, incognito,
+                                 std::move(callback));
 }
 
 void CastMediaRouteProvider::TerminateRoute(const std::string& route_id,
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
index 76d6953..8411d2c 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
@@ -57,14 +57,14 @@
                    const std::string& sink_id,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int32_t tab_id,
+                   int32_t frame_tree_node_id,
                    base::TimeDelta timeout,
                    bool incognito,
                    CreateRouteCallback callback) override;
   void JoinRoute(const std::string& media_source,
                  const std::string& presentation_id,
                  const url::Origin& origin,
-                 int32_t tab_id,
+                 int32_t frame_tree_node_id,
                  base::TimeDelta timeout,
                  bool incognito,
                  JoinRouteCallback callback) override;
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider_unittest.cc b/chrome/browser/media/router/providers/cast/cast_media_route_provider_unittest.cc
index 520c64f..1fa7724e 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider_unittest.cc
@@ -39,7 +39,7 @@
     "\"mobile\"}";
 static constexpr char kPresentationId[] = "presentationId";
 static constexpr char kOrigin[] = "https://www.youtube.com";
-static constexpr int kTabId = 1;
+static constexpr int kFrameTreeNodeId = 1;
 static constexpr base::TimeDelta kRouteTimeout = base::Seconds(30);
 
 base::Value MakeReceiverStatus() {
@@ -194,7 +194,8 @@
 TEST_F(CastMediaRouteProviderTest, CreateRouteFailsInvalidSink) {
   // Sink does not exist.
   provider_->CreateRoute(
-      kCastSource, "sinkId", kPresentationId, origin_, kTabId, kRouteTimeout,
+      kCastSource, "sinkId", kPresentationId, origin_, kFrameTreeNodeId,
+      kRouteTimeout,
       /* incognito */ false,
       base::BindOnce(&CastMediaRouteProviderTest::ExpectCreateRouteFailure,
                      base::Unretained(this),
@@ -206,8 +207,8 @@
   media_sink_service_.AddOrUpdateSink(sink);
 
   provider_->CreateRoute(
-      "invalidSource", sink.sink().id(), kPresentationId, origin_, kTabId,
-      kRouteTimeout, /* incognito */ false,
+      "invalidSource", sink.sink().id(), kPresentationId, origin_,
+      kFrameTreeNodeId, kRouteTimeout, /* incognito */ false,
       base::BindOnce(&CastMediaRouteProviderTest::ExpectCreateRouteFailure,
                      base::Unretained(this),
                      mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER));
@@ -226,7 +227,7 @@
         launch_session_callback_ = std::move(callback);
       }));
   provider_->CreateRoute(
-      kCastSource, sink.sink().id(), kPresentationId, origin_, kTabId,
+      kCastSource, sink.sink().id(), kPresentationId, origin_, kFrameTreeNodeId,
       kRouteTimeout, /* incognito */ false,
       base::BindOnce(
           &CastMediaRouteProviderTest::ExpectCreateRouteSuccessAndSetRoute,
@@ -245,7 +246,7 @@
         launch_session_callback_ = std::move(callback);
       }));
   provider_->CreateRoute(
-      kCastSource, sink.sink().id(), kPresentationId, origin_, kTabId,
+      kCastSource, sink.sink().id(), kPresentationId, origin_, kFrameTreeNodeId,
       kRouteTimeout, /* incognito */ false,
       base::BindOnce(
           &CastMediaRouteProviderTest::ExpectCreateRouteSuccessAndSetRoute,
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.cc b/chrome/browser/media/router/providers/cast/cast_session_client.cc
index c3f10545..4fcb8a09 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client.cc
+++ b/chrome/browser/media/router/providers/cast/cast_session_client.cc
@@ -8,8 +8,10 @@
 
 CastSessionClient::CastSessionClient(const std::string& client_id,
                                      const url::Origin& origin,
-                                     int tab_id)
-    : client_id_(client_id), origin_(origin), tab_id_(tab_id) {}
+                                     int frame_tree_node_id)
+    : client_id_(client_id),
+      origin_(origin),
+      frame_tree_node_id_(frame_tree_node_id) {}
 
 CastSessionClient::~CastSessionClient() = default;
 
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.h b/chrome/browser/media/router/providers/cast/cast_session_client.h
index 22ae7869..4eb3037 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client.h
+++ b/chrome/browser/media/router/providers/cast/cast_session_client.h
@@ -27,7 +27,7 @@
  public:
   CastSessionClient(const std::string& client_id,
                     const url::Origin& origin,
-                    int tab_id);
+                    int frame_tree_node_id);
   CastSessionClient(const CastSessionClient&) = delete;
   CastSessionClient& operator=(const CastSessionClient&) = delete;
   virtual ~CastSessionClient();
@@ -35,7 +35,7 @@
   const std::string& client_id() const { return client_id_; }
   const absl::optional<std::string>& session_id() const { return session_id_; }
   const url::Origin& origin() const { return origin_; }
-  int tab_id() const { return tab_id_; }
+  int frame_tree_node_id() const { return frame_tree_node_id_; }
 
   // Initializes the PresentationConnection Mojo message pipes and returns the
   // handles of the two pipes to be held by Blink. Also transitions the
@@ -59,17 +59,18 @@
       blink::mojom::PresentationConnectionCloseReason close_reason) = 0;
   virtual void TerminateConnection() = 0;
 
-  // Tests whether the specified origin and tab ID match this session's origin
-  // and tab ID to the extent required by this sesssion's auto-join policy.
-  // Depending on the value of |auto_join_policy_|, |origin|, |tab_id|, or both
-  // may be ignored.
+  // Tests whether the specified origin and FrameTreeNode ID match this
+  // session's origin and FrameTreeNode ID to the extent required by this
+  // sesssion's auto-join policy. Depending on the value of |auto_join_policy_|,
+  // |origin|, |frame_tree_node_id_|, or both may be ignored.
   //
   // TODO(crbug.com/1291742): It appears the purpose of this method is to detect
   // whether this session was created by an auto-join request.  It might make
   // more sense to record at session creation time whether a particular session
   // was created by an auto-join request, in which case this method would no
   // longer be needed.
-  virtual bool MatchesAutoJoinPolicy(url::Origin origin, int tab_id) const = 0;
+  virtual bool MatchesAutoJoinPolicy(url::Origin origin,
+                                     int frame_tree_node_id) const = 0;
 
   virtual void SendErrorCodeToClient(
       int sequence_number,
@@ -85,10 +86,10 @@
   std::string client_id_;
   absl::optional<std::string> session_id_;
 
-  // The origin and tab ID parameters originally passed to the CreateRoute
-  // method of the MediaRouteProvider Mojo interface.
+  // The origin and FrameTreeNode ID parameters originally passed to the
+  // CreateRoute method of the MediaRouteProvider Mojo interface.
   url::Origin origin_;
-  int tab_id_;
+  int frame_tree_node_id_;
 };
 
 class CastSessionClientFactoryForTest {
@@ -96,7 +97,7 @@
   virtual std::unique_ptr<CastSessionClient> MakeClientForTest(
       const std::string& client_id,
       const url::Origin& origin,
-      int tab_id) = 0;
+      int frame_tree_node_id) = 0;
 };
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client_impl.cc b/chrome/browser/media/router/providers/cast/cast_session_client_impl.cc
index 71ca9b1..00552639 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client_impl.cc
+++ b/chrome/browser/media/router/providers/cast/cast_session_client_impl.cc
@@ -51,10 +51,10 @@
 
 CastSessionClientImpl::CastSessionClientImpl(const std::string& client_id,
                                              const url::Origin& origin,
-                                             int tab_id,
+                                             int frame_tree_node_id,
                                              AutoJoinPolicy auto_join_policy,
                                              CastActivity* activity)
-    : CastSessionClient(client_id, origin, tab_id),
+    : CastSessionClient(client_id, origin, frame_tree_node_id),
       auto_join_policy_(auto_join_policy),
       activity_(activity) {}
 
@@ -98,13 +98,15 @@
       CreateV2Message(client_id(), media_status, sequence_number));
 }
 
-bool CastSessionClientImpl::MatchesAutoJoinPolicy(url::Origin other_origin,
-                                                  int other_tab_id) const {
+bool CastSessionClientImpl::MatchesAutoJoinPolicy(
+    url::Origin other_origin,
+    int other_frame_tree_node_id) const {
   switch (auto_join_policy_) {
     case AutoJoinPolicy::kPageScoped:
       return false;
     case AutoJoinPolicy::kTabAndOriginScoped:
-      return other_origin == origin() && other_tab_id == tab_id();
+      return other_origin == origin() &&
+             other_frame_tree_node_id == frame_tree_node_id();
     case AutoJoinPolicy::kOriginScoped:
       return other_origin == origin();
   }
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client_impl.h b/chrome/browser/media/router/providers/cast/cast_session_client_impl.h
index 150fa79..27a46225 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client_impl.h
+++ b/chrome/browser/media/router/providers/cast/cast_session_client_impl.h
@@ -20,7 +20,7 @@
  public:
   CastSessionClientImpl(const std::string& client_id,
                         const url::Origin& origin,
-                        int tab_id,
+                        int frame_tree_node_id,
                         AutoJoinPolicy auto_join_policy,
                         CastActivity* activity);
   ~CastSessionClientImpl() override;
@@ -36,7 +36,8 @@
   void CloseConnection(
       blink::mojom::PresentationConnectionCloseReason close_reason) override;
   void TerminateConnection() override;
-  bool MatchesAutoJoinPolicy(url::Origin origin, int tab_id) const override;
+  bool MatchesAutoJoinPolicy(url::Origin origin,
+                             int frame_tree_node_id) const override;
   void SendErrorCodeToClient(int sequence_number,
                              CastInternalMessage::ErrorCode error_code,
                              absl::optional<std::string> description) override;
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.cc b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
index edd0a7a..f08cecc 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
@@ -93,13 +93,8 @@
   }
 }
 
-// Get the mirroring type for a media route.  Note that |target_tab_id| is
-// usually ignored here, because mirroring typically only happens with a special
-// URL that includes the tab ID it needs, which should be the same as the tab ID
-// selected by the media router.
 absl::optional<MirroringActivity::MirroringType> GetMirroringType(
-    const MediaRoute& route,
-    int target_tab_id) {
+    const MediaRoute& route) {
   if (!route.is_local())
     return absl::nullopt;
 
@@ -109,28 +104,23 @@
   if (source.IsDesktopMirroringSource())
     return MirroringActivity::MirroringType::kDesktop;
 
-  if (source.url().is_valid()) {
-    if (source.IsCastPresentationUrl()) {
-      const auto cast_source = CastMediaSource::FromMediaSource(source);
-      if (cast_source && cast_source->ContainsStreamingApp()) {
-        // This is a weird case.  Normally if the source is a presentation URL,
-        // we use 2-UA mode rather than mirroring, but if the app ID it
-        // specifies is one of the special streaming app IDs, we activate
-        // mirroring instead. This only happens when a Cast SDK client requests
-        // a mirroring app ID, which causes its own tab to be mirrored.  This is
-        // a strange thing to do and it's not officially supported, but some
-        // apps, like, Google Slides rely on it.  Unlike a proper tab-based
-        // MediaSource, this kind of MediaSource doesn't specify a tab in the
-        // URL, so we choose the tab that was active when the request was made.
-        DCHECK_GE(target_tab_id, 0);
-        return MirroringActivity::MirroringType::kTab;
-      } else {
-        NOTREACHED() << "Non-mirroring Cast app: " << source;
-        return absl::nullopt;
-      }
-    } else if (source.url().SchemeIsHTTPOrHTTPS()) {
-      return MirroringActivity::MirroringType::kOffscreenTab;
+  if (!source.url().is_valid()) {
+    NOTREACHED() << "Invalid source: " << source;
+    return absl::nullopt;
+  }
+
+  if (source.IsCastPresentationUrl()) {
+    const auto cast_source = CastMediaSource::FromMediaSource(source);
+    if (cast_source && cast_source->ContainsStreamingApp()) {
+      // Site-initiated Mirroring has a Cast Presentatino URL and contains
+      // StreamingApp. We should return Tab Mirroring here.
+      return MirroringActivity::MirroringType::kTab;
+    } else {
+      NOTREACHED() << "Non-mirroring Cast app: " << source;
+      return absl::nullopt;
     }
+  } else if (source.url().SchemeIsHTTPOrHTTPS()) {
+    return MirroringActivity::MirroringType::kOffscreenTab;
   }
 
   NOTREACHED() << "Invalid source: " << source;
@@ -144,16 +134,14 @@
     const std::string& app_id,
     cast_channel::CastMessageHandler* message_handler,
     CastSessionTracker* session_tracker,
-    int target_tab_id,
+    int frame_tree_node_id,
     const CastSinkExtraData& cast_data,
     OnStopCallback callback)
     : CastActivity(route, app_id, message_handler, session_tracker),
-      mirroring_type_(GetMirroringType(route, target_tab_id)),
+      mirroring_type_(GetMirroringType(route)),
+      frame_tree_node_id_(frame_tree_node_id),
       cast_data_(cast_data),
-      on_stop_(std::move(callback)) {
-  if (target_tab_id != -1)
-    mirroring_tab_id_ = target_tab_id;
-}
+      on_stop_(std::move(callback)) {}
 
 MirroringActivity::~MirroringActivity() {
   if (!did_start_mirroring_timestamp_) {
@@ -198,13 +186,12 @@
       auto stream_id = route_.media_source().DesktopStreamId();
       DCHECK(stream_id);
       media_router->GetMirroringServiceHostForDesktop(
-          /* tab_id */ -1, *stream_id, host_.BindNewPipeAndPassReceiver());
+          *stream_id, host_.BindNewPipeAndPassReceiver());
       break;
     }
     case MirroringType::kTab:
-      DCHECK(mirroring_tab_id_.has_value());
       media_router->GetMirroringServiceHostForTab(
-          *mirroring_tab_id_, host_.BindNewPipeAndPassReceiver());
+          frame_tree_node_id_, host_.BindNewPipeAndPassReceiver());
       break;
     case MirroringType::kOffscreenTab:
       media_router->GetMirroringServiceHostForOffscreenTab(
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.h b/chrome/browser/media/router/providers/cast/mirroring_activity.h
index 46ca250..152c3f37 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.h
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.h
@@ -48,7 +48,7 @@
                     const std::string& app_id,
                     cast_channel::CastMessageHandler* message_handler,
                     CastSessionTracker* session_tracker,
-                    int target_tab_id,
+                    int frame_tree_node_id,
                     const CastSinkExtraData& cast_data,
                     OnStopCallback callback);
   ~MirroringActivity() override;
@@ -114,6 +114,9 @@
   absl::optional<base::Time> did_start_mirroring_timestamp_;
 
   const absl::optional<MirroringType> mirroring_type_;
+
+  // The FrameTreeNode ID to retrieve the WebContents of the tab to mirror.
+  const int frame_tree_node_id_;
   const CastSinkExtraData cast_data_;
   OnStopCallback on_stop_;
   base::WeakPtrFactory<MirroringActivity> weak_ptr_factory_{this};
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc b/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
index bd19fb2..684606e 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
@@ -31,7 +31,8 @@
 namespace media_router {
 namespace {
 
-constexpr int kTabId = 123;
+constexpr int kFrameTreeNodeId = 123;
+constexpr int kTabId = 234;
 constexpr char kDescription[] = "";
 constexpr char kDesktopMediaId[] = "theDesktopMediaId";
 constexpr char kPresentationId[] = "thePresentationId";
@@ -84,16 +85,18 @@
                                       std::move(receiver));
         };
     ON_CALL(media_router_, GetMirroringServiceHostForDesktop)
-        .WillByDefault(WithArg<2>(make_mirroring_service));
+        .WillByDefault(WithArg<1>(make_mirroring_service));
     ON_CALL(media_router_, GetMirroringServiceHostForTab)
         .WillByDefault(WithArg<1>(make_mirroring_service));
     ON_CALL(media_router_, GetMirroringServiceHostForOffscreenTab)
         .WillByDefault(WithArg<2>(make_mirroring_service));
   }
 
-  void MakeActivity() { MakeActivity(MediaSource::ForTab(kTabId), kTabId); }
+  void MakeActivity() { MakeActivity(MediaSource::ForTab(kTabId)); }
 
-  void MakeActivity(const MediaSource& source, int tab_id = -1,
+  void MakeActivity(
+      const MediaSource& source,
+      int frame_tree_node_id = kFrameTreeNodeId,
       CastDiscoveryType discovery_type = CastDiscoveryType::kMdns) {
     CastSinkExtraData cast_data;
     cast_data.cast_channel_id = kChannelId;
@@ -102,8 +105,8 @@
     MediaRoute route(kRouteId, source, kSinkId, kDescription, route_is_local_);
     route.set_presentation_id(kPresentationId);
     activity_ = std::make_unique<MirroringActivity>(
-        route, kAppId, &message_handler_, &session_tracker_, kTabId, cast_data,
-        on_stop_.Get());
+        route, kAppId, &message_handler_, &session_tracker_, frame_tree_node_id,
+        cast_data, on_stop_.Get());
 
     activity_->CreateMojoBindings(&media_router_);
 
@@ -140,7 +143,7 @@
 TEST_F(MirroringActivityTest, MirrorDesktop) {
   base::HistogramTester uma_recorder;
   EXPECT_CALL(media_router_,
-              GetMirroringServiceHostForDesktop(_, kDesktopMediaId, _));
+              GetMirroringServiceHostForDesktop(kDesktopMediaId, _));
   MediaSource source = MediaSource::ForDesktop(kDesktopMediaId, true);
   ASSERT_TRUE(source.IsDesktopMirroringSource());
   MakeActivity(source);
@@ -157,10 +160,11 @@
 
 TEST_F(MirroringActivityTest, MirrorTab) {
   base::HistogramTester uma_recorder;
-  EXPECT_CALL(media_router_, GetMirroringServiceHostForTab(kTabId, _));
+  EXPECT_CALL(media_router_,
+              GetMirroringServiceHostForTab(kFrameTreeNodeId, _));
   MediaSource source = MediaSource::ForTab(kTabId);
   ASSERT_TRUE(source.IsTabMirroringSource());
-  MakeActivity(source, kTabId);
+  MakeActivity(source);
 
   activity_->DidStart();
   activity_.reset();
@@ -174,12 +178,13 @@
 
 TEST_F(MirroringActivityTest, CreateMojoBindingsForTabWithCastAppUrl) {
   base::HistogramTester uma_recorder;
-  EXPECT_CALL(media_router_, GetMirroringServiceHostForTab(kTabId, _));
+  EXPECT_CALL(media_router_,
+              GetMirroringServiceHostForTab(kFrameTreeNodeId, _));
   auto site_initiated_mirroring_source =
       CastMediaSource::ForSiteInitiatedMirroring();
   MediaSource source(site_initiated_mirroring_source->source_id());
   ASSERT_TRUE(source.IsCastPresentationUrl());
-  MakeActivity(source, kTabId);
+  MakeActivity(source);
 
   activity_->DidStart();
   activity_.reset();
@@ -213,10 +218,12 @@
 
 TEST_F(MirroringActivityTest, MirrorAccessCode) {
   base::HistogramTester uma_recorder;
-  EXPECT_CALL(media_router_, GetMirroringServiceHostForTab(kTabId, _));
+  EXPECT_CALL(media_router_,
+              GetMirroringServiceHostForTab(kFrameTreeNodeId, _));
   MediaSource source = MediaSource::ForTab(kTabId);
   ASSERT_TRUE(source.IsTabMirroringSource());
-  MakeActivity(source, kTabId, CastDiscoveryType::kAccessCodeManualEntry);
+  MakeActivity(source, kFrameTreeNodeId,
+               CastDiscoveryType::kAccessCodeManualEntry);
 
   activity_->DidStart();
   activity_.reset();
diff --git a/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc b/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
index 0c9ab5c..8a3f6973 100644
--- a/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
@@ -83,7 +83,8 @@
                                          const std::string& sink_id,
                                          const std::string& presentation_id,
                                          const url::Origin& origin,
-                                         int32_t tab_id,
+                                         int32_t frame_tree_node_id,
+
                                          base::TimeDelta timeout,
                                          bool incognito,
                                          CreateRouteCallback callback) {
@@ -152,7 +153,7 @@
 void DialMediaRouteProvider::JoinRoute(const std::string& media_source,
                                        const std::string& presentation_id,
                                        const url::Origin& origin,
-                                       int32_t tab_id,
+                                       int32_t frame_tree_node_id,
                                        base::TimeDelta timeout,
                                        bool incognito,
                                        JoinRouteCallback callback) {
diff --git a/chrome/browser/media/router/providers/dial/dial_media_route_provider.h b/chrome/browser/media/router/providers/dial/dial_media_route_provider.h
index 1ead2ac..8f048a1 100644
--- a/chrome/browser/media/router/providers/dial/dial_media_route_provider.h
+++ b/chrome/browser/media/router/providers/dial/dial_media_route_provider.h
@@ -72,14 +72,14 @@
                    const std::string& sink_id,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int32_t tab_id,
+                   int32_t frame_tree_node_id,
                    base::TimeDelta timeout,
                    bool incognito,
                    CreateRouteCallback callback) override;
   void JoinRoute(const std::string& media_source,
                  const std::string& presentation_id,
                  const url::Origin& origin,
-                 int32_t tab_id,
+                 int32_t frame_tree_node_id,
                  base::TimeDelta timeout,
                  bool incognito,
                  JoinRouteCallback callback) override;
diff --git a/chrome/browser/media/router/providers/dial/dial_media_route_provider_unittest.cc b/chrome/browser/media/router/providers/dial/dial_media_route_provider_unittest.cc
index 7a63369..83bf78e 100644
--- a/chrome/browser/media/router/providers/dial/dial_media_route_provider_unittest.cc
+++ b/chrome/browser/media/router/providers/dial/dial_media_route_provider_unittest.cc
@@ -36,6 +36,10 @@
 
 namespace media_router {
 
+namespace {
+static constexpr int kFrameTreeNodeId = 1;
+}
+
 class TestDialMediaSinkServiceImpl : public DialMediaSinkServiceImpl {
  public:
   TestDialMediaSinkServiceImpl()
@@ -150,7 +154,8 @@
     // CreateRoute, but MR will add the route returned in the response.
     EXPECT_CALL(mock_router_, OnRoutesUpdated(_, _)).Times(0);
     provider_->CreateRoute(
-        source_id, sink_id, presentation_id, origin_, 1, base::TimeDelta(),
+        source_id, sink_id, presentation_id, origin_, kFrameTreeNodeId,
+        base::TimeDelta(),
         /* off_the_record */ false,
         base::BindOnce(&DialMediaRouteProviderTest::ExpectRouteResult,
                        base::Unretained(this),
@@ -203,7 +208,7 @@
         client_incognito ? *client_incognito : route_->is_off_the_record();
 
     provider_->JoinRoute(
-        source, presentation, origin, /*tab_id*/ 5, base::TimeDelta(),
+        source, presentation, origin, kFrameTreeNodeId, base::TimeDelta(),
         incognito,
         base::BindOnce(&DialMediaRouteProviderTest::ExpectRouteResult,
                        base::Unretained(this), expected_result));
diff --git a/chrome/browser/media/router/providers/test/test_media_route_provider.cc b/chrome/browser/media/router/providers/test/test_media_route_provider.cc
index 9148be1..7857918 100644
--- a/chrome/browser/media/router/providers/test/test_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/test/test_media_route_provider.cc
@@ -71,7 +71,7 @@
                                          const std::string& sink_id,
                                          const std::string& presentation_id,
                                          const url::Origin& origin,
-                                         int32_t tab_id,
+                                         int32_t frame_tree_node_id,
                                          base::TimeDelta timeout,
                                          bool incognito,
                                          CreateRouteCallback callback) {
@@ -85,8 +85,8 @@
                        GetWeakPtr(), std::move(callback)),
         delay_);
   } else {
-    DVLOG(2) << "CreateRoute with origin: " << origin << " and tab ID "
-             << tab_id;
+    DVLOG(2) << "CreateRoute with origin: " << origin
+             << " and FrameTreeNode ID " << frame_tree_node_id;
     MediaRoute route(presentation_id, MediaSource(media_source), sink_id,
                      std::string("Test Route"), true);
     route.set_presentation_id(presentation_id);
@@ -115,7 +115,7 @@
 void TestMediaRouteProvider::JoinRoute(const std::string& media_source,
                                        const std::string& presentation_id,
                                        const url::Origin& origin,
-                                       int32_t tab_id,
+                                       int32_t frame_tree_node_id,
                                        base::TimeDelta timeout,
                                        bool incognito,
                                        JoinRouteCallback callback) {
diff --git a/chrome/browser/media/router/providers/test/test_media_route_provider.h b/chrome/browser/media/router/providers/test/test_media_route_provider.h
index c1731ec..3bb32637 100644
--- a/chrome/browser/media/router/providers/test/test_media_route_provider.h
+++ b/chrome/browser/media/router/providers/test/test_media_route_provider.h
@@ -36,14 +36,14 @@
                    const std::string& sink_id,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int32_t tab_id,
+                   int32_t frame_tree_node_id,
                    base::TimeDelta timeout,
                    bool incognito,
                    CreateRouteCallback callback) override;
   void JoinRoute(const std::string& media_source,
                  const std::string& presentation_id,
                  const url::Origin& origin,
-                 int32_t tab_id,
+                 int32_t frame_tree_node_id,
                  base::TimeDelta timeout,
                  bool incognito,
                  JoinRouteCallback callback) override;
diff --git a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
index 8416b20..8b32777 100644
--- a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc
@@ -95,7 +95,7 @@
     const std::string& sink_id,
     const std::string& presentation_id,
     const url::Origin& origin,
-    int32_t tab_id,
+    int32_t frame_tree_node_id,
     base::TimeDelta timeout,
     bool off_the_record,
     CreateRouteCallback callback) {
@@ -132,7 +132,7 @@
     const std::string& media_source,
     const std::string& presentation_id,
     const url::Origin& origin,
-    int32_t tab_id,
+    int32_t frame_tree_node_id,
     base::TimeDelta timeout,
     bool off_the_record,
     JoinRouteCallback callback) {
diff --git a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h
index 3e05bf49..0bdbcead 100644
--- a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h
+++ b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h
@@ -61,14 +61,14 @@
                    const std::string& sink_id,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int32_t tab_id,
+                   int32_t frame_tree_node_id,
                    base::TimeDelta timeout,
                    bool off_the_record,
                    CreateRouteCallback callback) override;
   void JoinRoute(const std::string& media_source,
                  const std::string& presentation_id,
                  const url::Origin& origin,
-                 int32_t tab_id,
+                 int32_t frame_tree_node_id,
                  base::TimeDelta timeout,
                  bool off_the_record,
                  JoinRouteCallback callback) override;
diff --git a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc
index 2347c146..54de256 100644
--- a/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc
+++ b/chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc
@@ -34,6 +34,8 @@
 
 namespace {
 
+static constexpr int kFrameTreeNodeId = 1;
+
 class MockCallback {
  public:
   MOCK_METHOD4(CreateRoute,
@@ -331,8 +333,8 @@
               Start(presentation_id, GURL(kPresentationSource)));
   provider_remote_->CreateRoute(
       kPresentationSource, GetSinkId(secondary_display1_), presentation_id,
-      url::Origin::Create(GURL(kPresentationSource)), 0, base::Seconds(100),
-      false,
+      url::Origin::Create(GURL(kPresentationSource)), kFrameTreeNodeId,
+      base::Seconds(100), false,
       base::BindOnce(&MockCallback::CreateRoute, base::Unretained(&callback)));
   base::RunLoop().RunUntilIdle();
 
@@ -367,8 +369,8 @@
   // Create a route for |presentation_id|.
   provider_remote_->CreateRoute(
       kPresentationSource, GetSinkId(secondary_display1_), presentation_id,
-      url::Origin::Create(GURL(kPresentationSource)), 0, base::Seconds(100),
-      false,
+      url::Origin::Create(GURL(kPresentationSource)), kFrameTreeNodeId,
+      base::Seconds(100), false,
       base::BindOnce(&MockCallback::CreateRoute, base::Unretained(&callback)));
   base::RunLoop().RunUntilIdle();
 
@@ -396,8 +398,8 @@
 
   provider_remote_->CreateRoute(
       kPresentationSource, GetSinkId(secondary_display1_), "presentationId",
-      url::Origin::Create(GURL(kPresentationSource)), 0, base::Seconds(100),
-      false,
+      url::Origin::Create(GURL(kPresentationSource)), kFrameTreeNodeId,
+      base::Seconds(100), false,
       base::BindOnce(&MockCallback::CreateRoute, base::Unretained(&callback)));
   base::RunLoop().RunUntilIdle();
 
diff --git a/chrome/browser/media/router/test/media_router_mojo_test.cc b/chrome/browser/media/router/test/media_router_mojo_test.cc
index d20ebe7..c87d4bb 100644
--- a/chrome/browser/media/router/test/media_router_mojo_test.cc
+++ b/chrome/browser/media/router/test/media_router_mojo_test.cc
@@ -15,6 +15,7 @@
 using testing::NiceMock;
 using testing::Not;
 using testing::Pointee;
+using testing::WithArg;
 
 namespace media_router {
 
@@ -27,7 +28,7 @@
 const char kRouteId[] = "routeId";
 const char kSource[] = "source1";
 const char kSinkId[] = "sink";
-const int kInvalidTabId = -1;
+const int kInvalidFrameTreeNodeId = -1;
 const int kTimeoutMillis = 5 * 1000;
 const uint8_t kBinaryMessage[] = {0x01, 0x02, 0x03, 0x04};
 
@@ -177,15 +178,12 @@
   EXPECT_CALL(mock_cast_provider_,
               CreateRouteInternal(kSource, kSinkId, _,
                                   url::Origin::Create(GURL(kOrigin)),
-                                  kInvalidTabId, _, _, _))
-      .WillOnce(Invoke([](const std::string& source, const std::string& sink,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool incognito,
-                          mojom::MediaRouteProvider::CreateRouteCallback& cb) {
-        std::move(cb).Run(CreateMediaRoute(), nullptr, std::string(),
-                          mojom::RouteRequestResultCode::OK);
-      }));
+                                  kInvalidFrameTreeNodeId, _, _, _))
+      .WillOnce(WithArg<7>(
+          Invoke([](mojom::MediaRouteProvider::CreateRouteCallback& cb) {
+            std::move(cb).Run(CreateMediaRoute(), nullptr, std::string(),
+                              mojom::RouteRequestResultCode::OK);
+          })));
 
   RouteResponseCallbackHandler handler;
   EXPECT_CALL(handler, DoInvoke(Pointee(expected_route), Not(""), "",
@@ -217,18 +215,15 @@
   // a limitation with GMock::Invoke that prevents it from using move-only types
   // in runnable parameter lists.
   EXPECT_CALL(mock_cast_provider_,
-              JoinRouteInternal(
-                  kSource, presentation_id, url::Origin::Create(GURL(kOrigin)),
-                  kInvalidTabId, base::Milliseconds(kTimeoutMillis), _, _))
-      .WillOnce(
-          Invoke([&route](const std::string& source,
-                          const std::string& presentation_id,
-                          const url::Origin& origin, int tab_id,
-                          base::TimeDelta timeout, bool incognito,
-                          mojom::MediaRouteProvider::JoinRouteCallback& cb) {
+              JoinRouteInternal(kSource, presentation_id,
+                                url::Origin::Create(GURL(kOrigin)),
+                                kInvalidFrameTreeNodeId,
+                                base::Milliseconds(kTimeoutMillis), _, _))
+      .WillOnce(WithArg<6>(
+          Invoke([&route](mojom::MediaRouteProvider::JoinRouteCallback& cb) {
             std::move(cb).Run(route, nullptr, std::string(),
                               mojom::RouteRequestResultCode::OK);
-          }));
+          })));
 
   RouteResponseCallbackHandler handler;
   EXPECT_CALL(handler, DoInvoke(Pointee(expected_route), Not(""), "",
diff --git a/chrome/browser/media/router/test/media_router_mojo_test.h b/chrome/browser/media/router/test/media_router_mojo_test.h
index eef6b08d..542da93 100644
--- a/chrome/browser/media/router/test/media_router_mojo_test.h
+++ b/chrome/browser/media/router/test/media_router_mojo_test.h
@@ -47,41 +47,43 @@
                    const std::string& sink_id,
                    const std::string& presentation_id,
                    const url::Origin& origin,
-                   int tab_id,
+                   int frame_tree_node_id,
                    base::TimeDelta timeout,
                    bool incognito,
                    CreateRouteCallback callback) override {
-    CreateRouteInternal(source_urn, sink_id, presentation_id, origin, tab_id,
-                        timeout, incognito, callback);
+    CreateRouteInternal(source_urn, sink_id, presentation_id, origin,
+                        frame_tree_node_id, timeout, incognito, callback);
   }
-  MOCK_METHOD8(CreateRouteInternal,
-               void(const std::string& source_urn,
-                    const std::string& sink_id,
-                    const std::string& presentation_id,
-                    const url::Origin& origin,
-                    int tab_id,
-                    base::TimeDelta timeout,
-                    bool incognito,
-                    CreateRouteCallback& callback));
+  MOCK_METHOD(void,
+              CreateRouteInternal,
+              (const std::string& source_urn,
+               const std::string& sink_id,
+               const std::string& presentation_id,
+               const url::Origin& origin,
+               int frame_tree_node_id,
+               base::TimeDelta timeout,
+               bool incognito,
+               CreateRouteCallback& callback));
   void JoinRoute(const std::string& source_urn,
                  const std::string& presentation_id,
                  const url::Origin& origin,
-                 int tab_id,
+                 int frame_tree_node_id,
                  base::TimeDelta timeout,
                  bool incognito,
                  JoinRouteCallback callback) override {
-    JoinRouteInternal(source_urn, presentation_id, origin, tab_id, timeout,
-                      incognito, callback);
+    JoinRouteInternal(source_urn, presentation_id, origin, frame_tree_node_id,
+                      timeout, incognito, callback);
   }
-  MOCK_METHOD7(JoinRouteInternal,
-               void(const std::string& source_urn,
-                    const std::string& presentation_id,
-                    const url::Origin& origin,
-                    int tab_id,
-                    base::TimeDelta timeout,
-                    bool incognito,
-                    JoinRouteCallback& callback));
-  MOCK_METHOD1(DetachRoute, void(const std::string& route_id));
+  MOCK_METHOD(void,
+              JoinRouteInternal,
+              (const std::string& source_urn,
+               const std::string& presentation_id,
+               const url::Origin& origin,
+               int frame_tree_node_id,
+               base::TimeDelta timeout,
+               bool incognito,
+               JoinRouteCallback& callback));
+  MOCK_METHOD(void, DetachRoute, (const std::string& route_id));
   void TerminateRoute(const std::string& route_id,
                       TerminateRouteCallback callback) override {
     TerminateRouteInternal(route_id, callback);
diff --git a/chrome/browser/media/router/test/mock_mojo_media_router.h b/chrome/browser/media/router/test/mock_mojo_media_router.h
index dc08246..0b48b6a 100644
--- a/chrome/browser/media/router/test/mock_mojo_media_router.h
+++ b/chrome/browser/media/router/test/mock_mojo_media_router.h
@@ -62,13 +62,12 @@
   MOCK_METHOD0(GetMediaSinkServiceStatus, std::string());
   MOCK_METHOD2(
       GetMirroringServiceHostForTab,
-      void(int32_t target_tab_id,
+      void(int32_t frame_tree_node_id,
            mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost>
                receiver));
-  MOCK_METHOD3(
+  MOCK_METHOD2(
       GetMirroringServiceHostForDesktop,
-      void(int32_t initiator_tab_id,
-           const std::string& desktop_stream_id,
+      void(const std::string& desktop_stream_id,
            mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost>
                receiver));
   MOCK_METHOD3(
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 537bf3c..30a326e 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -188,6 +188,10 @@
 #include "components/spellcheck/browser/pref_names.h"
 #endif  // BUILDFLAG(ENABLE_SPELLCHECK)
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+#include "components/device_signals/core/browser/pref_names.h"
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
     BUILDFLAG(IS_FUCHSIA)
 #include "chrome/browser/web_applications/policy/web_app_settings_policy_handler.h"
@@ -1400,6 +1404,11 @@
     prefs::kBackgroundModeEnabled,
     base::Value::Type::BOOLEAN },
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+  { key::kUnmanagedDeviceSignalsConsentFlowEnabled,
+    device_signals::prefs::kUnmanagedDeviceSignalsConsentFlowEnabled,
+    base::Value::Type::BOOLEAN },
+#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) \
     || BUILDFLAG(IS_FUCHSIA)
   { key::kDefaultBrowserSettingEnabled,
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc b/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
index 6de7d79..014e52c 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
+++ b/chrome/browser/policy/networking/user_network_configuration_updater_factory.cc
@@ -89,7 +89,7 @@
   return UserNetworkConfigurationUpdaterAsh::CreateForUserPolicy(
              profile, *user,
              profile->GetProfilePolicyConnector()->policy_service(),
-             chromeos::NetworkHandler::Get()
+             ash::NetworkHandler::Get()
                  ->managed_network_configuration_handler())
       .release();
 }
diff --git a/chrome/browser/power_bookmarks/DEPS b/chrome/browser/power_bookmarks/DEPS
new file mode 100644
index 0000000..9bcdd2e
--- /dev/null
+++ b/chrome/browser/power_bookmarks/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+    "+components/power_bookmarks",
+]
\ No newline at end of file
diff --git a/chrome/browser/power_bookmarks/OWNERS b/chrome/browser/power_bookmarks/OWNERS
new file mode 100644
index 0000000..9c8001ff
--- /dev/null
+++ b/chrome/browser/power_bookmarks/OWNERS
@@ -0,0 +1 @@
+file://components/power_bookmarks/OWNERS
diff --git a/chrome/browser/power_bookmarks/power_bookmark_service_factory.cc b/chrome/browser/power_bookmarks/power_bookmark_service_factory.cc
new file mode 100644
index 0000000..77a0ad6
--- /dev/null
+++ b/chrome/browser/power_bookmarks/power_bookmark_service_factory.cc
@@ -0,0 +1,33 @@
+// 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/power_bookmarks/power_bookmark_service_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/power_bookmarks/core/power_bookmark_service.h"
+
+// static
+power_bookmarks::PowerBookmarkService*
+PowerBookmarkServiceFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<power_bookmarks::PowerBookmarkService*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+PowerBookmarkServiceFactory* PowerBookmarkServiceFactory::GetInstance() {
+  return base::Singleton<PowerBookmarkServiceFactory>::get();
+}
+
+PowerBookmarkServiceFactory::PowerBookmarkServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "PowerBookmarkService",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+PowerBookmarkServiceFactory::~PowerBookmarkServiceFactory() = default;
+
+KeyedService* PowerBookmarkServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new power_bookmarks::PowerBookmarkService();
+}
diff --git a/chrome/browser/power_bookmarks/power_bookmark_service_factory.h b/chrome/browser/power_bookmarks/power_bookmark_service_factory.h
new file mode 100644
index 0000000..096bf3b
--- /dev/null
+++ b/chrome/browser/power_bookmarks/power_bookmark_service_factory.h
@@ -0,0 +1,37 @@
+// 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 CHROME_BROWSER_POWER_BOOKMARKS_POWER_BOOKMARK_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_POWER_BOOKMARKS_POWER_BOOKMARK_SERVICE_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace power_bookmarks {
+class PowerBookmarkService;
+}
+
+// Factory to create one PowerBookmarkService per browser context.
+class PowerBookmarkServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static power_bookmarks::PowerBookmarkService* GetForBrowserContext(
+      content::BrowserContext* browser_context);
+  static PowerBookmarkServiceFactory* GetInstance();
+
+  PowerBookmarkServiceFactory(const PowerBookmarkServiceFactory&) = delete;
+  PowerBookmarkServiceFactory& operator=(const PowerBookmarkServiceFactory&) =
+      delete;
+
+ private:
+  friend struct base::DefaultSingletonTraits<PowerBookmarkServiceFactory>;
+
+  PowerBookmarkServiceFactory();
+  ~PowerBookmarkServiceFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+};
+
+#endif  // CHROME_BROWSER_POWER_BOOKMARKS_POWER_BOOKMARK_SERVICE_FACTORY_H_
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 14a0582..08a3d7d 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -393,7 +393,6 @@
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
 #include "chrome/browser/upgrade_detector/upgrade_detector_chromeos.h"
-#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
 #include "chromeos/ash/components/local_search_service/search_metrics_reporter.h"
 #include "chromeos/ash/components/network/cellular_esim_profile_handler_impl.h"
 #include "chromeos/ash/components/network/cellular_metrics_logger.h"
@@ -439,9 +438,9 @@
 #include "components/os_crypt/os_crypt.h"
 #endif
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
-    (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 #include "chrome/browser/web_applications/url_handler_prefs.h"
+#include "components/device_signals/core/browser/pref_names.h"
 #endif
 
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
@@ -747,6 +746,12 @@
 const char kSettingsShowOSBanner[] = "settings.cros.show_os_banner";
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Deprecated 08/2022.
+constexpr char kSecurityTokenSessionNotificationDisplayed[] =
+    "security_token_session_notification_displayed";
+#endif
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -971,6 +976,12 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterBooleanPref(kSettingsShowOSBanner, false);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Deprecated 08/2022
+  registry->RegisterBooleanPref(kSecurityTokenSessionNotificationDisplayed,
+                                false);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 }  // namespace
@@ -1191,10 +1202,9 @@
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #endif  // BUILDFLAG(IS_WIN)
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
-    (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
   web_app::url_handler_prefs::RegisterLocalStatePrefs(registry);
-#endif
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
   RegisterDefaultBrowserPromptPrefs(registry);
@@ -1444,7 +1454,6 @@
   ash::file_system_provider::RegisterProfilePrefs(registry);
   ash::full_restore::RegisterProfilePrefs(registry);
   ash::KerberosCredentialsManager::RegisterProfilePrefs(registry);
-  ash::login::SecurityTokenSessionController::RegisterProfilePrefs(registry);
   ash::multidevice_setup::MultiDeviceSetupService::RegisterProfilePrefs(
       registry);
   ash::MultiProfileUserController::RegisterProfilePrefs(registry);
@@ -1507,6 +1516,10 @@
   safe_browsing::PostCleanupSettingsResetter::RegisterProfilePrefs(registry);
 #endif
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+  device_signals::RegisterProfilePrefs(registry);
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
 // of lacros-chrome is complete.
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
@@ -1715,12 +1728,6 @@
   profile_prefs->ClearPref(kCanShowFolderSelectionNudge);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // Added 2021/08.
-  web_app::ExternallyInstalledWebAppPrefs::RemoveTerminalPWA(profile_prefs);
-
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 #if !BUILDFLAG(IS_ANDROID)
   // Added 09/2021.
   profile_prefs->ClearPref(kNtpSearchSuggestionsBlocklist);
@@ -1917,6 +1924,11 @@
   profile_prefs->ClearPref(kSettingsShowOSBanner);
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Added 08/2022.
+  profile_prefs->ClearPref(kSecurityTokenSessionNotificationDisplayed);
+#endif
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
index 66bfdbc..e9b0860 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
@@ -151,7 +151,6 @@
       // utterances added to the queue later.
       chrome.accessibilityPrivate.silenceSpokenFeedback();
     }
-    this.startTone_.play();
     this.setStopTimeout_(
         Dictation.Timeouts.NO_FOCUSED_IME_MS,
         'Dictation stopped automatically: No focused IME');
@@ -310,6 +309,7 @@
       return;
     }
 
+    this.startTone_.play();
     this.clearInterimText_();
 
     // Record metrics.
diff --git a/chrome/browser/resources/chromeos/arc_support/main.js b/chrome/browser/resources/chromeos/arc_support/main.js
index c9809d1..53dcb0c7 100644
--- a/chrome/browser/resources/chromeos/arc_support/main.js
+++ b/chrome/browser/resources/chromeos/arc_support/main.js
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
-
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/chromeos/crostini_upgrader/app.js b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
index 3ee924b..d510302 100644
--- a/chrome/browser/resources/chromeos/crostini_upgrader/app.js
+++ b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/paper-progress/paper-progress.js';
 import './strings.m.js';
diff --git a/chrome/browser/resources/chromeos/emulator/audio_settings.js b/chrome/browser/resources/chromeos/emulator/audio_settings.js
index c904e4c..58ec2f7 100644
--- a/chrome/browser/resources/chromeos/emulator/audio_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/audio_settings.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/chromeos/emulator/battery_settings.js b/chrome/browser/resources/chromeos/emulator/battery_settings.js
index 399df8c..f2c37f0 100644
--- a/chrome/browser/resources/chromeos/emulator/battery_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/battery_settings.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
diff --git a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
index 1af478a8..cdaf2b5 100644
--- a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
index 243e576..d348bc38 100644
--- a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
+++ b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
@@ -92,9 +92,10 @@
 ]
 
 oobe_migrated_imports = [
-  "ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.html",
+  "ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.html",
   "ui/webui/resources/cr_elements/cr_expand_button/cr_expand_button.html",
-  "ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html",
   "ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_icon.html",
+  "ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html",
   "ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html",
+  "ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.html",
 ]
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
index d511f71..7064d52 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
@@ -440,7 +440,7 @@
     "../../components/dialogs:oobe_adaptive_dialog.m",
     "../../components/dialogs:oobe_modal_dialog.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
+    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox",
     "//ui/webui/resources/js:util.m",
   ]
   extra_deps = [ ":oobe_reset_module" ]
@@ -667,6 +667,7 @@
   html_file = "app_downloading.html"
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
+  migrated_imports = oobe_migrated_imports
   namespace_rewrites = oobe_namespace_rewrites
 }
 
@@ -683,6 +684,7 @@
   html_file = "arc_terms_of_service.html"
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
+  migrated_imports = oobe_migrated_imports
   namespace_rewrites = oobe_namespace_rewrites
 }
 
@@ -773,6 +775,7 @@
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
   namespace_rewrites = oobe_namespace_rewrites
+  migrated_imports = oobe_migrated_imports
 }
 
 polymer_modulizer("managed_terms_of_service") {
@@ -813,6 +816,7 @@
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
   namespace_rewrites = oobe_namespace_rewrites
+  migrated_imports = oobe_migrated_imports
 }
 
 polymer_modulizer("oobe_reset") {
@@ -821,6 +825,7 @@
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
   namespace_rewrites = oobe_namespace_rewrites
+  migrated_imports = oobe_migrated_imports
 }
 
 polymer_modulizer("os_install") {
@@ -861,6 +866,7 @@
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
   namespace_rewrites = oobe_namespace_rewrites
+  migrated_imports = oobe_migrated_imports
 }
 
 polymer_modulizer("user_creation") {
@@ -901,6 +907,7 @@
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
   namespace_rewrites = oobe_namespace_rewrites
+  migrated_imports = oobe_migrated_imports
 }
 
 polymer_modulizer("theme_selection") {
diff --git a/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js b/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
index ee3afb6..10d74855 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
+++ b/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
index 53e02f5..790fbbc 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
@@ -6,7 +6,7 @@
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -296,4 +296,4 @@
   }
 }
 
-customElements.define(NotificationTester.is, NotificationTester);
\ No newline at end of file
+customElements.define(NotificationTester.is, NotificationTester);
diff --git a/chrome/browser/resources/extensions/extensions.ts b/chrome/browser/resources/extensions/extensions.ts
index 5a20e92..4d1af5f 100644
--- a/chrome/browser/resources/extensions/extensions.ts
+++ b/chrome/browser/resources/extensions/extensions.ts
@@ -4,7 +4,7 @@
 
 import './manager.js';
 
-export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 export {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.js';
 export {IronIconElement} from 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 export {ActivityLogExtensionPlaceholder, ExtensionsActivityLogElement} from './activity_log/activity_log.js';
diff --git a/chrome/browser/resources/extensions/kiosk_dialog.ts b/chrome/browser/resources/extensions/kiosk_dialog.ts
index cfa27051..0e39489 100644
--- a/chrome/browser/resources/extensions/kiosk_dialog.ts
+++ b/chrome/browser/resources/extensions/kiosk_dialog.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_icons_css.m.js';
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/chrome/browser/resources/history/history_item.ts b/chrome/browser/resources/history/history_item.ts
index 8688389..e69fd49 100644
--- a/chrome/browser/resources/history/history_item.ts
+++ b/chrome/browser/resources/history/history_item.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/js/icon.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
diff --git a/chrome/browser/resources/history/lazy_load.ts b/chrome/browser/resources/history/lazy_load.ts
index a6adfbee..5475200a 100644
--- a/chrome/browser/resources/history/lazy_load.ts
+++ b/chrome/browser/resources/history/lazy_load.ts
@@ -5,7 +5,7 @@
 import './synced_device_manager.js';
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_drawer/cr_drawer.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
diff --git a/chrome/browser/resources/inline_login/welcome_page_app.js b/chrome/browser/resources/inline_login/welcome_page_app.js
index 0486995c..eae22a9c 100644
--- a/chrome/browser/resources/inline_login/welcome_page_app.js
+++ b/chrome/browser/resources/inline_login/welcome_page_app.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import './account_manager_shared_css.js';
 
diff --git a/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts b/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
index 98bc4343..bc13a27 100644
--- a/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
+++ b/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
@@ -4,7 +4,7 @@
 
 import './strings.m.js';
 import '//resources/cr_elements/cr_button/cr_button.m.js';
-import '//resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import '//resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import '//resources/cr_elements/cr_input/cr_input.m.js';
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
diff --git a/chrome/browser/resources/nearby_share/BUILD.gn b/chrome/browser/resources/nearby_share/BUILD.gn
index acfc4e3..66db547 100644
--- a/chrome/browser/resources/nearby_share/BUILD.gn
+++ b/chrome/browser/resources/nearby_share/BUILD.gn
@@ -181,7 +181,7 @@
     "//chrome/browser/ui/webui/nearby_share:share_type_js_library_for_compile",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
-    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
+    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
diff --git a/chrome/browser/resources/nearby_share/nearby_confirmation_page.js b/chrome/browser/resources/nearby_share/nearby_confirmation_page.js
index a49d5c8..01ff5dc14 100644
--- a/chrome/browser/resources/nearby_share/nearby_confirmation_page.js
+++ b/chrome/browser/resources/nearby_share/nearby_confirmation_page.js
@@ -9,7 +9,7 @@
  */
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_lottie/cr_lottie.m.js';
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
 import 'chrome://resources/mojo/mojo/public/mojom/base/unguessable_token.mojom-lite.js';
diff --git a/chrome/browser/resources/print_preview/print_preview.ts b/chrome/browser/resources/print_preview/print_preview.ts
index ba0e02ba..7725442d 100644
--- a/chrome/browser/resources/print_preview/print_preview.ts
+++ b/chrome/browser/resources/print_preview/print_preview.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 export {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 export {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 export {PluralStringProxyImpl as PrintPreviewPluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 export {IronMeta} from 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js';
diff --git a/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts b/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
index 8596e68d..71568abc 100644
--- a/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
+++ b/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
@@ -3,14 +3,14 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/search_highlight_style.css.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import './print_preview_shared.css.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/copies_settings.ts b/chrome/browser/resources/print_preview/ui/copies_settings.ts
index 19cdc1b..3eb2e63 100644
--- a/chrome/browser/resources/print_preview/ui/copies_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/copies_settings.ts
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import './number_settings_section.js';
 import './print_preview_shared.css.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/duplex_settings.ts b/chrome/browser/resources/print_preview/ui/duplex_settings.ts
index e4aee956..d14470e 100644
--- a/chrome/browser/resources/print_preview/ui/duplex_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/duplex_settings.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
 import 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js';
@@ -11,7 +11,7 @@
 import './print_preview_shared.css.js';
 import './settings_section.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {IronMeta} from 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/other_options_settings.ts b/chrome/browser/resources/print_preview/ui/other_options_settings.ts
index 0024de0..1206452 100644
--- a/chrome/browser/resources/print_preview/ui/other_options_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/other_options_settings.ts
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import './print_preview_shared.css.js';
 import './settings_section.js';
 import '../strings.m.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/pin_settings.ts b/chrome/browser/resources/print_preview/ui/pin_settings.ts
index 40ac96f..2643c4ea 100644
--- a/chrome/browser/resources/print_preview/ui/pin_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pin_settings.ts
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
 import './print_preview_shared.css.js';
 import './settings_section.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
diff --git a/chrome/browser/resources/settings/autofill_page/password_move_multiple_passwords_to_account_dialog.ts b/chrome/browser/resources/settings/autofill_page/password_move_multiple_passwords_to_account_dialog.ts
index 5f42dcc..2e832a6 100644
--- a/chrome/browser/resources/settings/autofill_page/password_move_multiple_passwords_to_account_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_move_multiple_passwords_to_account_dialog.ts
@@ -11,7 +11,7 @@
 import './avatar_icon.js';
 import './password_list_item.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
diff --git a/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts b/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
index 69834ed..e5b2ef7b 100644
--- a/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
@@ -11,12 +11,12 @@
  */
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import './avatar_icon.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 7acc984..bedd765 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -400,9 +400,7 @@
     "controls/pref_control_mixin.js",
     "controls/settings_boolean_control_mixin.js",
     "extension_control_browser_proxy.js",
-    "i18n_setup.js",
     "icons.html.js",
-    "lifetime_browser_proxy.js",
     "open_window_proxy.js",
     "people_page/profile_info_browser_proxy.js",
     "people_page/sync_browser_proxy.js",
@@ -494,7 +492,6 @@
     "keyboard_shortcut_banner:closure_compile_module",
     "multidevice_page:closure_compile_module",
     "os_a11y_page:closure_compile_module",
-    "os_about_page:closure_compile_module",
     "os_apps_page:closure_compile_module",
     "os_apps_page/app_management_page:closure_compile_module",
     "os_apps_page/app_management_page/borealis_page:closure_compile_module",
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
index 4a70d8f..3313c23f 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
@@ -8,7 +8,7 @@
  */
 import './album_list.js';
 import './art_album_dialog.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_components/localized_link/localized_link.js';
 import '../../settings_shared.css.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.js b/chrome/browser/resources/settings/chromeos/device_page/display.js
index ed1c7c1..b19cd37 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.js
@@ -7,7 +7,7 @@
  * 'settings-display' is the settings subpage for display settings.
  */
 
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 import 'chrome://resources/cr_elements/cr_tabs/cr_tabs.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_add_account_dialog.js b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_add_account_dialog.js
index 96393adc4..e40ac96 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_add_account_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_add_account_dialog.js
@@ -8,7 +8,7 @@
  */
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
deleted file mode 100644
index c9536ab..0000000
--- a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//third_party/closure_compiler/compile_js.gni")
-import("../os_settings.gni")
-
-js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags
-  is_polymer3 = true
-  deps = [
-    ":about_page_browser_proxy",
-    ":channel_switcher_dialog",
-    ":consumer_auto_update_toggle_dialog",
-    ":detailed_build_info",
-    ":device_name_browser_proxy",
-    ":device_name_util",
-    ":edit_hostname_dialog",
-    ":os_about_page",
-    ":update_warning_dialog",
-  ]
-}
-
-js_library("about_page_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
-js_library("channel_switcher_dialog") {
-  deps = [
-    ":about_page_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-}
-
-js_library("consumer_auto_update_toggle_dialog") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("detailed_build_info") {
-  deps = [
-    ":about_page_browser_proxy",
-    ":channel_switcher_dialog",
-    ":consumer_auto_update_toggle_dialog",
-    ":device_name_util",
-    ":edit_hostname_dialog",
-    "..:deep_linking_behavior",
-    "..:os_route",
-    "..:prefs_behavior",
-    "..:route_observer_behavior",
-    "../..:router",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/policy:cr_policy_indicator_behavior.m",
-    "//ui/webui/resources/cr_elements/policy:cr_tooltip_icon.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-}
-
-js_library("device_name_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-  externs_list = [ "$externs_path/chrome_send.js" ]
-}
-
-js_library("device_name_util") {
-  deps = []
-}
-
-js_library("edit_hostname_dialog") {
-  deps = [
-    ":about_page_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-}
-
-js_library("os_about_page") {
-  deps = [
-    ":about_page_browser_proxy",
-    ":device_name_browser_proxy",
-    "..:deep_linking_behavior",
-    "..:os_route",
-    "..:route_observer_behavior",
-    "../..:router",
-    "../os_settings_page:main_page_behavior",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js:parse_html_subset.m",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-
-  externs_list = [ "../settings_controls_types.js" ]
-}
-
-js_library("update_warning_dialog") {
-  deps = [
-    ":about_page_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:i18n_behavior.m",
-  ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.js
deleted file mode 100644
index ab0498ed..0000000
--- a/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.js
+++ /dev/null
@@ -1,390 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "About" section to interact with
- * the browser.
- */
-
-import {assertNotReached} from 'chrome://resources/js/assert.m.js';
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
-
-/**
- * @typedef {{
- *   text: string,
- *   url: string,
- * }}
- */
-export let RegulatoryInfo;
-
-/**
- * @typedef {{
- *   currentChannel: BrowserChannel,
- *   targetChannel: BrowserChannel,
- *   isLts: boolean,
- * }}
- */
-export let ChannelInfo;
-
-/**
- * @typedef {{
- *   arcVersion: string,
- *   osFirmware: string,
- *   osVersion: string,
- * }}
- */
-export let VersionInfo;
-
-/**
- * @typedef {{
- *   version: (string|undefined),
- *   size: (string|undefined),
- * }}
- */
-export let AboutPageUpdateInfo;
-
-/**
- * @typedef {{
- *   hasEndOfLife: (boolean|undefined),
- *   eolMessageWithMonthAndYear: (string|undefined),
- * }}
- */
-let EndOfLifeInfo;
-
-/**
- * Enumeration of all possible browser channels.
- * @enum {string}
- */
-export const BrowserChannel = {
-  BETA: 'beta-channel',
-  CANARY: 'canary-channel',
-  DEV: 'dev-channel',
-  STABLE: 'stable-channel',
-};
-
-/**
- * @typedef {{
- *   updateAvailable: boolean,
- * }}
- */
-export let TPMFirmwareUpdateStatusChangedEvent;
-
-/**
- * Enumeration of all possible update statuses. The string literals must match
- * the ones defined at |AboutHandler::UpdateStatusToString|.
- * @enum {string}
- */
-export const UpdateStatus = {
-  CHECKING: 'checking',
-  UPDATING: 'updating',
-  NEARLY_UPDATED: 'nearly_updated',
-  UPDATED: 'updated',
-  FAILED: 'failed',
-  FAILED_HTTP: 'failed_http',
-  FAILED_DOWNLOAD: 'failed_download',
-  DISABLED: 'disabled',
-  DISABLED_BY_ADMIN: 'disabled_by_admin',
-  NEED_PERMISSION_TO_UPDATE: 'need_permission_to_update',
-  DEFERRED: 'deferred',
-};
-
-/**
- * @typedef {{
- *   status: !UpdateStatus,
- *   progress: (number|undefined),
- *   message: (string|undefined),
- *   connectionTypes: (string|undefined),
- *   version: (string|undefined),
- *   size: (string|undefined),
- * }}
- */
-export let UpdateStatusChangedEvent;
-
-/**
- * @param {!BrowserChannel} channel
- * @param {boolean} isLts
- * @return {string}
- */
-export function browserChannelToI18nId(channel, isLts) {
-  if (isLts) {
-    return 'aboutChannelLongTermSupport';
-  }
-
-  switch (channel) {
-    case BrowserChannel.BETA:
-      return 'aboutChannelBeta';
-    case BrowserChannel.CANARY:
-      return 'aboutChannelCanary';
-    case BrowserChannel.DEV:
-      return 'aboutChannelDev';
-    case BrowserChannel.STABLE:
-      return 'aboutChannelStable';
-  }
-
-  assertNotReached();
-}
-
-/**
- * @param {!BrowserChannel} currentChannel
- * @param {!BrowserChannel} targetChannel
- * @return {boolean} Whether the target channel is more stable than the
- *     current channel.
- */
-export function isTargetChannelMoreStable(currentChannel, targetChannel) {
-  // List of channels in increasing stability order.
-  const channelList = [
-    BrowserChannel.CANARY,
-    BrowserChannel.DEV,
-    BrowserChannel.BETA,
-    BrowserChannel.STABLE,
-  ];
-  const currentIndex = channelList.indexOf(currentChannel);
-  const targetIndex = channelList.indexOf(targetChannel);
-  return currentIndex < targetIndex;
-}
-
-/** @interface */
-export class AboutPageBrowserProxy {
-  /**
-   * Applies deferred update if it exists.
-   */
-  applyDeferredUpdate() {}
-
-  /**
-   * Indicates to the browser that the page is ready.
-   */
-  pageReady() {}
-
-  /**
-   * Request update status from the browser. It results in one or more
-   * 'update-status-changed' WebUI events.
-   */
-  refreshUpdateStatus() {}
-
-  /** Opens the release notes app. */
-  launchReleaseNotes() {}
-
-  // <if expr="_google_chrome">
-  /**
-   * Opens the feedback dialog.
-   */
-  openFeedbackDialog() {}
-
-  // </if>
-
-  /** Opens the diagnostics page. */
-  openDiagnostics() {}
-
-  /** Opens the OS help page. */
-  openOsHelpPage() {}
-
-  /** Opens the firmware updates page. */
-  openFirmwareUpdatesPage() {}
-
-  /**
-   * Requests the number of firmware updates.
-   * @return {!Promise<number>}
-   */
-  getFirmwareUpdateCount() {}
-
-  /**
-   * Checks for available update and applies if it exists.
-   */
-  requestUpdate() {}
-
-  /**
-   * Checks for the update with specified version and size and applies over
-   * cellular. The target version and size are the same as were received from
-   * 'update-status-changed' WebUI event. We send this back all the way to
-   * update engine for it to double check with update server in case there's a
-   * new update available. This prevents downloading the new update that user
-   * didn't agree to.
-   * @param {string} target_version
-   * @param {string} target_size
-   */
-  requestUpdateOverCellular(target_version, target_size) {}
-
-  /**
-   * @param {!BrowserChannel} channel
-   * @param {boolean} isPowerwashAllowed
-   */
-  setChannel(channel, isPowerwashAllowed) {}
-
-  /**
-   * Requests channel info from the version updater. This may have latency if
-   * the version updater is busy, for example with downloading updates.
-   * @return {!Promise<!ChannelInfo>}
-   */
-  getChannelInfo() {}
-
-  /** @return {!Promise<!boolean>} */
-  canChangeChannel() {}
-
-  /** @return {!Promise<!VersionInfo>} */
-  getVersionInfo() {}
-
-  /** @return {!Promise<?RegulatoryInfo>} */
-  getRegulatoryInfo() {}
-
-  /**
-   * Checks if the device has reached end-of-life status and will no longer
-   * receive updates.
-   * @return {!Promise<!EndOfLifeInfo>}
-   */
-  getEndOfLifeInfo() {}
-
-  /**
-   * Request TPM firmware update status from the browser. It results in one or
-   * more 'tpm-firmware-update-status-changed' WebUI events.
-   */
-  refreshTPMFirmwareUpdateStatus() {}
-
-  /**
-   * Checks if the device is connected to the internet.
-   * @return {!Promise<boolean>}
-   */
-  checkInternetConnection() {}
-
-  /** @return {!Promise<boolean>} */
-  isManagedAutoUpdateEnabled() {}
-
-  /** @return {!Promise<boolean>} */
-  isConsumerAutoUpdateEnabled() {}
-
-  /**
-   * @param {boolean} enable
-   */
-  setConsumerAutoUpdate(enable) {}
-}
-
-/** @type {?AboutPageBrowserProxy} */
-let instance = null;
-
-/**
- * @implements {AboutPageBrowserProxy}
- */
-export class AboutPageBrowserProxyImpl {
-  /** @return {!AboutPageBrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new AboutPageBrowserProxyImpl());
-  }
-
-  /** @param {!AboutPageBrowserProxy} obj */
-  static setInstanceForTesting(obj) {
-    instance = obj;
-  }
-
-  /** @override */
-  applyDeferredUpdate() {
-    chrome.send('applyDeferredUpdate');
-  }
-
-  /** @override */
-  pageReady() {
-    chrome.send('aboutPageReady');
-  }
-
-  /** @override */
-  refreshUpdateStatus() {
-    chrome.send('refreshUpdateStatus');
-  }
-
-  /** @override */
-  launchReleaseNotes() {
-    chrome.send('launchReleaseNotes');
-  }
-
-  // <if expr="_google_chrome">
-  /** @override */
-  openFeedbackDialog() {
-    chrome.send('openFeedbackDialog');
-  }
-
-  // </if>
-
-  /** @override */
-  openDiagnostics() {
-    chrome.send('openDiagnostics');
-  }
-
-  /** @override */
-  openOsHelpPage() {
-    chrome.send('openOsHelpPage');
-  }
-
-  /** @override */
-  openFirmwareUpdatesPage() {
-    chrome.send('openFirmwareUpdatesPage');
-  }
-
-  /** @override */
-  getFirmwareUpdateCount() {
-    return sendWithPromise('getFirmwareUpdateCount');
-  }
-
-  /** @override */
-  requestUpdate() {
-    chrome.send('requestUpdate');
-  }
-
-  /** @override */
-  requestUpdateOverCellular(target_version, target_size) {
-    chrome.send('requestUpdateOverCellular', [target_version, target_size]);
-  }
-
-  /** @override */
-  setChannel(channel, isPowerwashAllowed) {
-    chrome.send('setChannel', [channel, isPowerwashAllowed]);
-  }
-
-  /** @override */
-  getChannelInfo() {
-    return sendWithPromise('getChannelInfo');
-  }
-
-  /** @override */
-  canChangeChannel() {
-    return sendWithPromise('canChangeChannel');
-  }
-
-  /** @override */
-  getVersionInfo() {
-    return sendWithPromise('getVersionInfo');
-  }
-
-  /** @override */
-  getRegulatoryInfo() {
-    return sendWithPromise('getRegulatoryInfo');
-  }
-
-  /** @override */
-  getEndOfLifeInfo() {
-    return sendWithPromise('getEndOfLifeInfo');
-  }
-
-  /** @override */
-  checkInternetConnection() {
-    return sendWithPromise('checkInternetConnection');
-  }
-
-  /** @override */
-  refreshTPMFirmwareUpdateStatus() {
-    chrome.send('refreshTPMFirmwareUpdateStatus');
-  }
-
-  /** @override */
-  isManagedAutoUpdateEnabled() {
-    return sendWithPromise('isManagedAutoUpdateEnabled');
-  }
-
-  /** @override */
-  isConsumerAutoUpdateEnabled() {
-    return sendWithPromise('isConsumerAutoUpdateEnabled');
-  }
-
-  /** @override */
-  setConsumerAutoUpdate(enable) {
-    chrome.send('setConsumerAutoUpdate', [enable]);
-  }
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.ts b/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.ts
new file mode 100644
index 0000000..967d4c5de
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/about_page_browser_proxy.ts
@@ -0,0 +1,311 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A helper object used from the "About" section to interact with
+ * the browser.
+ */
+
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+export interface RegulatoryInfo {
+  text: string;
+  url: string;
+}
+
+/**
+ * Enumeration of all possible browser channels.
+ */
+export enum BrowserChannel {
+  BETA = 'beta-channel',
+  CANARY = 'canary-channel',
+  DEV = 'dev-channel',
+  STABLE = 'stable-channel',
+}
+
+export interface ChannelInfo {
+  currentChannel: BrowserChannel;
+  targetChannel: BrowserChannel;
+  isLts: boolean;
+}
+
+export interface VersionInfo {
+  arcVersion: string;
+  osFirmware: string;
+  osVersion: string;
+}
+
+export interface AboutPageUpdateInfo {
+  version?: string;
+  size?: string;
+}
+
+interface EndOfLifeInfo {
+  hasEndOfLife?: boolean;
+  aboutPageEndOfLifeMessage?: string;
+}
+
+export interface TPMFirmwareUpdateStatusChangedEvent {
+  updateAvailable: boolean;
+}
+
+/**
+ * Enumeration of all possible update statuses. The string literals must match
+ * the ones defined at |AboutHandler::UpdateStatusToString|.
+ */
+export enum UpdateStatus {
+  CHECKING = 'checking',
+  UPDATING = 'updating',
+  NEARLY_UPDATED = 'nearly_updated',
+  UPDATED = 'updated',
+  FAILED = 'failed',
+  FAILED_HTTP = 'failed_http',
+  FAILED_DOWNLOAD = 'failed_download',
+  DISABLED = 'disabled',
+  DISABLED_BY_ADMIN = 'disabled_by_admin',
+  NEED_PERMISSION_TO_UPDATE = 'need_permission_to_update',
+  DEFERRED = 'deferred',
+}
+
+export interface UpdateStatusChangedEvent {
+  status: UpdateStatus;
+  progress?: number;
+  message?: string;
+  connectionTypes?: string;
+  version?: string;
+  size?: string;
+  rollback?: boolean;
+  powerwash?: boolean;
+}
+
+export function browserChannelToI18nId(
+    channel: BrowserChannel, isLts: boolean): string {
+  if (isLts) {
+    return 'aboutChannelLongTermSupport';
+  }
+
+  switch (channel) {
+    case BrowserChannel.BETA:
+      return 'aboutChannelBeta';
+    case BrowserChannel.CANARY:
+      return 'aboutChannelCanary';
+    case BrowserChannel.DEV:
+      return 'aboutChannelDev';
+    case BrowserChannel.STABLE:
+      return 'aboutChannelStable';
+  }
+}
+
+/**
+ * Returns whether the target channel is more stable than the current channel.
+ */
+export function isTargetChannelMoreStable(
+    currentChannel: BrowserChannel, targetChannel: BrowserChannel): boolean {
+  // List of channels in increasing stability order.
+  const channelList = [
+    BrowserChannel.CANARY,
+    BrowserChannel.DEV,
+    BrowserChannel.BETA,
+    BrowserChannel.STABLE,
+  ];
+  const currentIndex = channelList.indexOf(currentChannel);
+  const targetIndex = channelList.indexOf(targetChannel);
+  return currentIndex < targetIndex;
+}
+
+export interface AboutPageBrowserProxy {
+  /**
+   * Applies deferred update if it exists.
+   */
+  applyDeferredUpdate(): void;
+
+  /**
+   * Indicates to the browser that the page is ready.
+   */
+  pageReady(): void;
+
+  /**
+   * Request update status from the browser. It results in one or more
+   * 'update-status-changed' WebUI events.
+   */
+  refreshUpdateStatus(): void;
+
+  /** Opens the release notes app. */
+  launchReleaseNotes(): void;
+
+  // <if expr="_google_chrome">
+  /**
+   * Opens the feedback dialog.
+   */
+  openFeedbackDialog(): void;
+  // </if>
+
+  /** Opens the diagnostics page. */
+  openDiagnostics(): void;
+
+  /** Opens the OS help page. */
+  openOsHelpPage(): void;
+
+  /** Opens the firmware updates page. */
+  openFirmwareUpdatesPage(): void;
+
+  /**
+   * Requests the number of firmware updates.
+   */
+  getFirmwareUpdateCount(): Promise<number>;
+
+  /**
+   * Checks for available update and applies if it exists.
+   */
+  requestUpdate(): void;
+
+  /**
+   * Checks for the update with specified version and size and applies over
+   * cellular. The target version and size are the same as were received from
+   * 'update-status-changed' WebUI event. We send this back all the way to
+   * update engine for it to double check with update server in case there's a
+   * new update available. This prevents downloading the new update that user
+   * didn't agree to.
+   */
+  requestUpdateOverCellular(targetVersion: string, targetSize: string): void;
+
+  setChannel(channel: BrowserChannel, isPowerwashAllowed: boolean): void;
+
+  /**
+   * Requests channel info from the version updater. This may have latency if
+   * the version updater is busy, for example with downloading updates.
+   */
+  getChannelInfo(): Promise<ChannelInfo>;
+
+  canChangeChannel(): Promise<boolean>;
+
+  getVersionInfo(): Promise<VersionInfo>;
+
+  getRegulatoryInfo(): Promise<RegulatoryInfo|null>;
+
+  /**
+   * Checks if the device has reached end-of-life status and will no longer
+   * receive updates.
+   */
+  getEndOfLifeInfo(): Promise<EndOfLifeInfo>;
+
+  /**
+   * Request TPM firmware update status from the browser. It results in one or
+   * more 'tpm-firmware-update-status-changed' WebUI events.
+   */
+  refreshTPMFirmwareUpdateStatus(): void;
+
+  /**
+   * Checks if the device is connected to the internet.
+   */
+  checkInternetConnection(): Promise<boolean>;
+
+  isManagedAutoUpdateEnabled(): Promise<boolean>;
+
+  isConsumerAutoUpdateEnabled(): Promise<boolean>;
+
+  setConsumerAutoUpdate(enable: boolean): void;
+}
+
+let instance: AboutPageBrowserProxy|null = null;
+
+export class AboutPageBrowserProxyImpl implements AboutPageBrowserProxy {
+  static getInstance(): AboutPageBrowserProxy {
+    return instance || (instance = new AboutPageBrowserProxyImpl());
+  }
+
+  static setInstanceForTesting(obj: AboutPageBrowserProxy) {
+    instance = obj;
+  }
+
+  applyDeferredUpdate() {
+    chrome.send('applyDeferredUpdate');
+  }
+
+  pageReady() {
+    chrome.send('aboutPageReady');
+  }
+
+  refreshUpdateStatus() {
+    chrome.send('refreshUpdateStatus');
+  }
+
+  launchReleaseNotes() {
+    chrome.send('launchReleaseNotes');
+  }
+
+  // <if expr="_google_chrome">
+  openFeedbackDialog() {
+    chrome.send('openFeedbackDialog');
+  }
+  // </if>
+
+  openDiagnostics() {
+    chrome.send('openDiagnostics');
+  }
+
+  openOsHelpPage() {
+    chrome.send('openOsHelpPage');
+  }
+
+  openFirmwareUpdatesPage() {
+    chrome.send('openFirmwareUpdatesPage');
+  }
+
+  getFirmwareUpdateCount() {
+    return sendWithPromise('getFirmwareUpdateCount');
+  }
+
+  requestUpdate() {
+    chrome.send('requestUpdate');
+  }
+
+  requestUpdateOverCellular(targetVersion: string, targetSize: string) {
+    chrome.send('requestUpdateOverCellular', [targetVersion, targetSize]);
+  }
+
+  setChannel(channel: BrowserChannel, isPowerwashAllowed: boolean) {
+    chrome.send('setChannel', [channel, isPowerwashAllowed]);
+  }
+
+  getChannelInfo(): Promise<ChannelInfo> {
+    return sendWithPromise('getChannelInfo');
+  }
+
+  canChangeChannel(): Promise<boolean> {
+    return sendWithPromise('canChangeChannel');
+  }
+
+  getVersionInfo(): Promise<VersionInfo> {
+    return sendWithPromise('getVersionInfo');
+  }
+
+  getRegulatoryInfo(): Promise<RegulatoryInfo|null> {
+    return sendWithPromise('getRegulatoryInfo');
+  }
+
+  getEndOfLifeInfo(): Promise<EndOfLifeInfo> {
+    return sendWithPromise('getEndOfLifeInfo');
+  }
+
+  checkInternetConnection(): Promise<boolean> {
+    return sendWithPromise('checkInternetConnection');
+  }
+
+  refreshTPMFirmwareUpdateStatus(): void {
+    chrome.send('refreshTPMFirmwareUpdateStatus');
+  }
+
+  isManagedAutoUpdateEnabled(): Promise<boolean> {
+    return sendWithPromise('isManagedAutoUpdateEnabled');
+  }
+
+  isConsumerAutoUpdateEnabled(): Promise<boolean> {
+    return sendWithPromise('isConsumerAutoUpdateEnabled');
+  }
+
+  setConsumerAutoUpdate(enable: boolean): void {
+    chrome.send('setConsumerAutoUpdate', [enable]);
+  }
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
similarity index 72%
rename from chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
index 0442202..f0f56093 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
@@ -9,20 +9,27 @@
  * release channel to notify parents of this dialog.
  */
 
-import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
-import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import '../../settings_shared.css.js';
 
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, BrowserChannel, isTargetChannelMoreStable} from './about_page_browser_proxy.js';
 import {getTemplate} from './channel_switcher_dialog.html.js';
 
+interface SettingsChannelSwitcherDialogElement {
+  $: {
+    dialog: CrDialogElement,
+    warningSelector: IronSelectorElement,
+  };
+}
+
 const WarningMessage = {
   NONE: -1,
   ENTERPRISE_MANAGED: 0,
@@ -30,7 +37,6 @@
   UNSTABLE: 2,
 };
 
-/** @polymer */
 class SettingsChannelSwitcherDialogElement extends PolymerElement {
   static get is() {
     return 'settings-channel-switcher-dialog';
@@ -42,22 +48,17 @@
 
   static get properties() {
     return {
-      /** @private */
       browserChannelEnum_: {
         type: Object,
         value: BrowserChannel,
       },
 
-      /** @private {!BrowserChannel} */
       currentChannel_: String,
 
-      /** @private {!BrowserChannel} */
       targetChannel_: String,
 
       /**
        * Controls which of the two action buttons is visible.
-       * @private {?{changeChannel: boolean, changeChannelAndPowerwash:
-       *     boolean}}
        */
       shouldShowButtons_: {
         type: Object,
@@ -66,72 +67,76 @@
     };
   }
 
+  private browserChannelEnum_: typeof BrowserChannel;
+  private currentChannel_: BrowserChannel;
+  private targetChannel_: BrowserChannel;
+  private shouldShowButtons_: {
+    changeChannel: boolean,
+    changeChannelAndPowerwash: boolean,
+  }|null;
+
+  private browserProxy_: AboutPageBrowserProxy;
+
   constructor() {
     super();
 
-    /** @private {AboutPageBrowserProxy} */
     this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance();
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
 
     this.browserProxy_.getChannelInfo().then(info => {
       this.currentChannel_ = info.currentChannel;
       this.targetChannel_ = info.targetChannel;
       // Pre-populate radio group with target channel.
-      const radioGroup = this.shadowRoot.querySelector('cr-radio-group');
+      const radioGroup = this.shadowRoot!.querySelector('cr-radio-group')!;
       radioGroup.selected = this.targetChannel_;
       radioGroup.focus();
     });
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.$.dialog.showModal();
   }
 
-  /** @private */
-  onCancelTap_() {
+  private onCancelTap_() {
     this.$.dialog.close();
   }
 
-  /** @private */
-  onChangeChannelTap_() {
+  private onChangeChannelTap_() {
     const selectedChannel =
-        this.shadowRoot.querySelector('cr-radio-group').selected;
+        this.shadowRoot!.querySelector('cr-radio-group')!.selected as
+        BrowserChannel;
     this.browserProxy_.setChannel(selectedChannel, false);
     this.$.dialog.close();
     this.fireTargetChannelChangedEvent_(selectedChannel);
   }
 
-  /** @private */
-  onChangeChannelAndPowerwashTap_() {
+  private onChangeChannelAndPowerwashTap_() {
     const selectedChannel =
-        this.shadowRoot.querySelector('cr-radio-group').selected;
+        this.shadowRoot!.querySelector('cr-radio-group')!.selected as
+        BrowserChannel;
     this.browserProxy_.setChannel(selectedChannel, true);
     this.$.dialog.close();
     this.fireTargetChannelChangedEvent_(selectedChannel);
   }
 
-  /** @private */
-  fireTargetChannelChangedEvent_(detail = {}) {
+  private fireTargetChannelChangedEvent_(detail = {}) {
     const event = new CustomEvent(
         'target-channel-changed', {bubbles: true, composed: true, detail});
     this.dispatchEvent(event);
   }
 
   /**
-   * @param {boolean} changeChannel Whether the changeChannel button should be
-   *     visible.
-   * @param {boolean} changeChannelAndPowerwash Whether the
-   *     changeChannelAndPowerwash button should be visible.
-   * @private
+   * @param changeChannel Whether the changeChannel button should be visible.
+   * @param changeChannelAndPowerwash Whether the changeChannelAndPowerwash
+   *    button should be visible.
    */
-  updateButtons_(changeChannel, changeChannelAndPowerwash) {
+  private updateButtons_(
+      changeChannel: boolean, changeChannelAndPowerwash: boolean) {
     if (changeChannel || changeChannelAndPowerwash) {
       // Ensure that at most one button is visible at any given time.
       assert(changeChannel !== changeChannelAndPowerwash);
@@ -143,10 +148,10 @@
     };
   }
 
-  /** @private */
-  onChannelSelectionChanged_() {
+  private onChannelSelectionChanged_() {
     const selectedChannel =
-        this.shadowRoot.querySelector('cr-radio-group').selected;
+        this.shadowRoot!.querySelector('cr-radio-group')!.selected as
+        BrowserChannel;
 
     // Selected channel is the same as the target channel so only show 'cancel'.
     if (selectedChannel === this.targetChannel_) {
@@ -184,17 +189,17 @@
     }
   }
 
-  /**
-   * @param {string} format
-   * @param {string} replacement
-   * @return {string}
-   * @private
-   */
-  substituteString_(format, replacement) {
+  private substituteString_(format: string, replacement: string): string {
     return loadTimeData.substituteString(format, replacement);
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-channel-switcher-dialog': SettingsChannelSwitcherDialogElement;
+  }
+}
+
 customElements.define(
     SettingsChannelSwitcherDialogElement.is,
     SettingsChannelSwitcherDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.ts
similarity index 71%
rename from chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.ts
index 7d63ed2aa..8c5d441 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog.ts
@@ -2,13 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
-
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './consumer_auto_update_toggle_dialog.html.js';
 
-/** @polymer */
+interface SettingsConsumerAutoUpdateToggleDialogElement {
+  $: {
+    dialog: CrDialogElement,
+  };
+}
+
 class SettingsConsumerAutoUpdateToggleDialogElement extends PolymerElement {
   static get is() {
     return 'settings-consumer-auto-update-toggle-dialog';
@@ -18,15 +22,13 @@
     return getTemplate();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.$.dialog.showModal();
   }
 
-  /** @private */
-  onTurnOffTap_() {
+  private onTurnOffTap_() {
     this.dispatchEvent(new CustomEvent('set-consumer-auto-update', {
       bubbles: true,
       composed: true,
@@ -37,8 +39,7 @@
     this.$.dialog.close();
   }
 
-  /** @private */
-  onKeepUpdatesTap_() {
+  private onKeepUpdatesTap_() {
     this.dispatchEvent(new CustomEvent('set-consumer-auto-update', {
       bubbles: true,
       composed: true,
@@ -50,6 +51,13 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-consumer-auto-update-toggle-dialog':
+        SettingsConsumerAutoUpdateToggleDialogElement;
+  }
+}
+
 customElements.define(
     SettingsConsumerAutoUpdateToggleDialogElement.is,
     SettingsConsumerAutoUpdateToggleDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
similarity index 72%
rename from chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
index fdd3da0..8e2b9de 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
@@ -20,8 +20,8 @@
 import {CrPolicyIndicatorType} from 'chrome://resources/cr_elements/policy/cr_policy_indicator_behavior.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
+import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
@@ -30,32 +30,32 @@
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
-import {RouteObserverBehavior} from '../route_observer_behavior.js';
+import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, browserChannelToI18nId, ChannelInfo, VersionInfo} from './about_page_browser_proxy.js';
 import {getTemplate} from './detailed_build_info.html.js';
 import {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl, DeviceNameMetadata} from './device_name_browser_proxy.js';
 import {DeviceNameState} from './device_name_util.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- * @implements {WebUIListenerBehaviorInterface}
- * @implements {DeepLinkingBehaviorInterface}
- * @implements {PrefsBehaviorInterface}
- */
-const SettingsDetailedBuildInfoBase = mixinBehaviors(
-    [
-      DeepLinkingBehavior,
-      WebUIListenerBehavior,
-      I18nBehavior,
-      PrefsBehavior,
-      RouteObserverBehavior,
-    ],
-    PolymerElement);
+declare global {
+  interface HTMLElementEventMap {
+    'set-consumer-auto-update': CustomEvent<{item: boolean}>;
+  }
+}
 
-/** @polymer */
+const SettingsDetailedBuildInfoBase =
+    mixinBehaviors(
+        [
+          DeepLinkingBehavior,
+          PrefsBehavior,
+          RouteObserverBehavior,
+        ],
+        I18nMixin(WebUIListenerMixin(PolymerElement))) as {
+      new (): PolymerElement & DeepLinkingBehaviorInterface &
+          WebUIListenerMixinInterface & I18nMixinInterface &
+          PrefsBehaviorInterface & RouteObserverBehaviorInterface,
+    };
+
 class SettingsDetailedBuildInfoElement extends SettingsDetailedBuildInfoBase {
   static get is() {
     return 'settings-detailed-build-info';
@@ -73,31 +73,22 @@
         notify: true,
       },
 
-      /** @private {!VersionInfo} */
       versionInfo_: Object,
 
-      /** @private {!ChannelInfo} */
       channelInfo_: Object,
 
-      /** @private {!DeviceNameMetadata} */
       deviceNameMetadata_: Object,
 
-      /** @private */
       currentlyOnChannelText_: String,
 
-      /** @private */
       showChannelSwitcherDialog_: Boolean,
 
-      /** @private */
       showEditHostnameDialog_: Boolean,
 
-      /** @private */
       canChangeChannel_: Boolean,
 
-      /** @private */
       isManagedAutoUpdateEnabled_: Boolean,
 
-      /** @private */
       showConsumerAutoUpdateToggleDialog_: Boolean,
 
       eolMessageWithMonthAndYear: {
@@ -107,7 +98,6 @@
 
       /**
        * Used by DeepLinkingBehavior to focus this page's deep links.
-       * @type {!Set<!Setting>}
        */
       supportedSettingIds: {
         type: Object,
@@ -118,13 +108,11 @@
         ]),
       },
 
-      /** @private */
       shouldHideEolInfo_: {
         type: Boolean,
         computed: 'computeShouldHideEolInfo_(eolMessageWithMonthAndYear)',
       },
 
-      /** @private */
       isHostnameSettingEnabled_: {
         type: Boolean,
         value() {
@@ -136,7 +124,6 @@
       /**
        * Whether the browser/ChromeOS is managed by their organization
        * through enterprise policies.
-       * @private
        */
       isManaged_: {
         type: Boolean,
@@ -148,7 +135,6 @@
 
       /**
        * Whether or not the consumer auto update toggling is allowed.
-       * @private
        */
       isConsumerAutoUpdateTogglingAllowed_: {
         type: Boolean,
@@ -160,7 +146,6 @@
 
       /**
        * Whether or not to show the consumer auto update toggle.
-       * @private
        */
       showConsumerAutoUpdateToggle_: {
         type: Boolean,
@@ -173,24 +158,41 @@
     };
   }
 
+  private versionInfo_: VersionInfo;
+  private channelInfo_: ChannelInfo;
+  private deviceNameMetadata_: DeviceNameMetadata;
+  private currentlyOnChannelText_: string;
+  private showChannelSwitcherDialog_: boolean;
+  private showEditHostnameDialog_: boolean;
+  private canChangeChannel_: boolean;
+  private isManagedAutoUpdateEnabled_: boolean;
+  private showConsumerAutoUpdateToggleDialog_: boolean;
+  private eolMessageWithMonthAndYear: string;
+  override supportedSettingIds: Set<Setting>;
+  private shouldHideEolInfo_: boolean;
+  private isHostnameSettingEnabled_: boolean;
+  private isManaged_: boolean;
+  private isConsumerAutoUpdateTogglingAllowed_: boolean;
+  private showConsumerAutoUpdateToggle_: boolean;
+
+  private aboutPageBrowserProxy_: AboutPageBrowserProxy;
+  private deviceNameBrowserProxy_: DeviceNameBrowserProxy;
+
   constructor() {
     super();
 
-    /** @private {!AboutPageBrowserProxy} */
     this.aboutPageBrowserProxy_ = AboutPageBrowserProxyImpl.getInstance();
-
-    /** @private {!DeviceNameBrowserProxy} */
     this.deviceNameBrowserProxy_ = DeviceNameBrowserProxyImpl.getInstance();
   }
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
     this.aboutPageBrowserProxy_.pageReady();
 
-    this.addEventListener('set-consumer-auto-update', e => {
-      this.aboutPageBrowserProxy_.setConsumerAutoUpdate(e.detail.item);
-    });
+    this.addEventListener(
+        'set-consumer-auto-update', (event: CustomEvent<{item: boolean}>) => {
+          this.aboutPageBrowserProxy_.setConsumerAutoUpdate(event.detail.item);
+        });
 
     if (this.isManaged_) {
       this.syncManagedAutoUpdateToggle_();
@@ -211,16 +213,12 @@
     if (this.isHostnameSettingEnabled_) {
       this.addWebUIListener(
           'settings.updateDeviceNameMetadata',
-          (data) => this.updateDeviceNameMetadata_(data));
+          (data: DeviceNameMetadata) => this.updateDeviceNameMetadata_(data));
       this.deviceNameBrowserProxy_.notifyReadyForDeviceName();
     }
   }
 
-  /**
-   * @param {!Route} route
-   * @param {!Route} oldRoute
-   */
-  currentRouteChanged(route, oldRoute) {
+  override currentRouteChanged(route: Route, _oldRoute?: Route) {
     // Does not apply to this page.
     if (route !== routes.DETAILED_BUILD_INFO) {
       return;
@@ -229,16 +227,11 @@
     this.attemptDeepLink();
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  computeShouldHideEolInfo_() {
+  private computeShouldHideEolInfo_(): boolean {
     return this.isManaged_ || !this.eolMessageWithMonthAndYear;
   }
 
-  /** @private */
-  updateChannelInfo_() {
+  private updateChannelInfo_() {
     // canChangeChannel() call is expected to be low-latency, so fetch this
     // value by itself to ensure UI consistency (see https://crbug.com/848750).
     this.aboutPageBrowserProxy_.canChangeChannel().then(canChangeChannel => {
@@ -256,34 +249,24 @@
     });
   }
 
-  /** @private */
-  syncManagedAutoUpdateToggle_() {
+  private syncManagedAutoUpdateToggle_() {
     this.aboutPageBrowserProxy_.isManagedAutoUpdateEnabled().then(
         isManagedAutoUpdateEnabled => {
           this.isManagedAutoUpdateEnabled_ = isManagedAutoUpdateEnabled;
         });
   }
 
-  /** @private */
-  syncConsumerAutoUpdateToggle_() {
+  private syncConsumerAutoUpdateToggle_() {
     this.aboutPageBrowserProxy_.isConsumerAutoUpdateEnabled().then(enabled => {
       this.aboutPageBrowserProxy_.setConsumerAutoUpdate(enabled);
     });
   }
 
-  /**
-   * @param {!DeviceNameMetadata} data
-   * @private
-   */
-  updateDeviceNameMetadata_(data) {
+  private updateDeviceNameMetadata_(data: DeviceNameMetadata) {
     this.deviceNameMetadata_ = data;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getDeviceNameText_() {
+  private getDeviceNameText_(): string {
     if (!this.deviceNameMetadata_) {
       return '';
     }
@@ -291,11 +274,7 @@
     return this.deviceNameMetadata_.deviceName;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getDeviceNameEditButtonA11yDescription_() {
+  private getDeviceNameEditButtonA11yDescription_(): string {
     if (!this.deviceNameMetadata_) {
       return '';
     }
@@ -304,11 +283,7 @@
         'aboutDeviceNameEditBtnA11yDescription', this.getDeviceNameText_());
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  canEditDeviceName_() {
+  private canEditDeviceName_(): boolean {
     if (!this.deviceNameMetadata_) {
       return false;
     }
@@ -317,27 +292,15 @@
         DeviceNameState.CAN_BE_MODIFIED;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowPolicyIndicator_() {
+  private shouldShowPolicyIndicator_(): boolean {
     return this.getDeviceNameIndicatorType_() !== CrPolicyIndicatorType.NONE;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowConsumerAutoUpdateToggle_() {
+  private shouldShowConsumerAutoUpdateToggle_(): boolean {
     return !this.isManaged_;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getDeviceNameIndicatorType_() {
+  private getDeviceNameIndicatorType_(): string {
     if (!this.deviceNameMetadata_) {
       return CrPolicyIndicatorType.NONE;
     }
@@ -355,12 +318,8 @@
     return CrPolicyIndicatorType.NONE;
   }
 
-  /**
-   * @param {boolean} canChangeChannel
-   * @return {string}
-   * @private
-   */
-  getChangeChannelIndicatorSourceName_(canChangeChannel) {
+  private getChangeChannelIndicatorSourceName_(canChangeChannel: boolean):
+      string {
     if (canChangeChannel) {
       // the indicator should be invisible.
       return '';
@@ -370,12 +329,8 @@
         loadTimeData.getString('ownerEmail');
   }
 
-  /**
-   * @param {boolean} canChangeChannel
-   * @return {CrPolicyIndicatorType}
-   * @private
-   */
-  getChangeChannelIndicatorType_(canChangeChannel) {
+  private getChangeChannelIndicatorType_(canChangeChannel: boolean):
+      CrPolicyIndicatorType {
     if (canChangeChannel) {
       return CrPolicyIndicatorType.NONE;
     }
@@ -384,35 +339,22 @@
         CrPolicyIndicatorType.OWNER;
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onChangeChannelTap_(e) {
+  private onChangeChannelTap_(e: Event) {
     e.preventDefault();
     this.showChannelSwitcherDialog_ = true;
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onEditHostnameTap_(e) {
+  private onEditHostnameTap_(e: Event) {
     e.preventDefault();
     this.showEditHostnameDialog_ = true;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  copyToClipBoardEnabled_() {
+  private copyToClipBoardEnabled_(): boolean {
     return !!this.versionInfo_ && !!this.channelInfo_;
   }
 
-  /** @private */
-  onCopyBuildDetailsToClipBoardTap_() {
-    const buildInfo = {
+  private onCopyBuildDetailsToClipBoardTap_() {
+    const buildInfo: {[key: string]: string|boolean} = {
       'application_label': loadTimeData.getString('aboutBrowserVersion'),
       'platform': this.versionInfo_.osVersion,
       'aboutChannelLabel': this.channelInfo_.targetChannel,
@@ -425,27 +367,22 @@
           loadTimeData.getBoolean('aboutIsDeveloperMode'),
     };
 
-    const entries = [];
+    const entries: string[] = [];
     for (const key in buildInfo) {
-      entries.push(this.i18n(key) + ': ' + buildInfo[key]);
+      entries.push(this.i18n(key) + ': ' + String(buildInfo[key]));
     }
 
     navigator.clipboard.writeText(entries.join('\n'));
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onConsumerAutoUpdateToggled_(e) {
+  private onConsumerAutoUpdateToggled_(_event: Event) {
     if (!this.isConsumerAutoUpdateTogglingAllowed_) {
       return;
     }
     this.showDialogOrFlushConsumerAutoUpdateToggle();
   }
 
-  /** @private */
-  onConsumerAutoUpdateToggledSettingsBox_() {
+  private onConsumerAutoUpdateToggledSettingsBox_() {
     if (!this.isConsumerAutoUpdateTogglingAllowed_) {
       return;
     }
@@ -456,8 +393,7 @@
     this.showDialogOrFlushConsumerAutoUpdateToggle();
   }
 
-  /** @private */
-  showDialogOrFlushConsumerAutoUpdateToggle() {
+  private showDialogOrFlushConsumerAutoUpdateToggle() {
     if (!this.getPref('settings.consumer_auto_update_toggle').value) {
       // Only show dialog when turning the toggle off.
       this.showConsumerAutoUpdateToggleDialog_ = true;
@@ -467,31 +403,30 @@
     this.aboutPageBrowserProxy_.setConsumerAutoUpdate(true);
   }
 
-  /** @private */
-  onConsumerAutoUpdateToggleDialogClosed_() {
+  private onConsumerAutoUpdateToggleDialogClosed_() {
     this.showConsumerAutoUpdateToggleDialog_ = false;
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onVisitBuildDetailsPageTap_(e) {
+  private onVisitBuildDetailsPageTap_(e: Event) {
     e.preventDefault();
     window.open('chrome://version');
   }
 
-  /** @private */
-  onChannelSwitcherDialogClosed_() {
+  private onChannelSwitcherDialogClosed_() {
     this.showChannelSwitcherDialog_ = false;
-    focusWithoutInk(assert(this.shadowRoot.querySelector('cr-button')));
+    focusWithoutInk(assert(this.shadowRoot!.querySelector('cr-button'))!);
     this.updateChannelInfo_();
   }
 
-  /** @private */
-  onEditHostnameDialogClosed_() {
+  private onEditHostnameDialogClosed_() {
     this.showEditHostnameDialog_ = false;
-    focusWithoutInk(assert(this.shadowRoot.querySelector('cr-button')));
+    focusWithoutInk(assert(this.shadowRoot!.querySelector('cr-button'))!);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-detailed-build-info': SettingsDetailedBuildInfoElement;
   }
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
deleted file mode 100644
index eefe6eb..0000000
--- a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
-import {DeviceNameState, SetDeviceNameResult} from './device_name_util.js';
-
-/**
- * @typedef {{
- *   deviceName: string,
- *   deviceNameState: !DeviceNameState,
- * }}
- */
-export let DeviceNameMetadata;
-
-/** @interface */
-export class DeviceNameBrowserProxy {
-  /**
-   * Notifies the system that the page is ready for the device name.
-   * @return {!Promise<!DeviceNameMetadata>}
-   */
-  notifyReadyForDeviceName() {}
-
-  /**
-   * Attempts to set the device name to the new name entered by the user.
-   * @param {string} name
-   * @return {!Promise<!SetDeviceNameResult>}
-   */
-  attemptSetDeviceName(name) {}
-}
-
-/** @type {?DeviceNameBrowserProxy} */
-let instance = null;
-
-/**
- * @implements {DeviceNameBrowserProxy}
- */
-export class DeviceNameBrowserProxyImpl {
-  /** @return {!DeviceNameBrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new DeviceNameBrowserProxyImpl());
-  }
-
-  /** @param {!DeviceNameBrowserProxy} obj */
-  static setInstanceForTesting(obj) {
-    instance = obj;
-  }
-
-  /** @override */
-  notifyReadyForDeviceName() {
-    return chrome.send('notifyReadyForDeviceName');
-  }
-
-  /** @override */
-  attemptSetDeviceName(name) {
-    return sendWithPromise('attemptSetDeviceName', name);
-  }
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.ts b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.ts
new file mode 100644
index 0000000..173acc4
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.ts
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+import {DeviceNameState, SetDeviceNameResult} from './device_name_util.js';
+
+export interface DeviceNameMetadata {
+  deviceName: string;
+  deviceNameState: DeviceNameState;
+}
+
+export interface DeviceNameBrowserProxy {
+  /**
+   * Notifies the system that the page is ready for the device name.
+   */
+  notifyReadyForDeviceName(): Promise<DeviceNameMetadata>;
+
+  /**
+   * Attempts to set the device name to the new name entered by the user.
+   */
+  attemptSetDeviceName(name: string): Promise<SetDeviceNameResult>;
+}
+
+let instance: DeviceNameBrowserProxy|null = null;
+
+export class DeviceNameBrowserProxyImpl implements DeviceNameBrowserProxy {
+  static getInstance(): DeviceNameBrowserProxy {
+    return instance || (instance = new DeviceNameBrowserProxyImpl());
+  }
+
+  static setInstanceForTesting(obj: DeviceNameBrowserProxy): void {
+    instance = obj;
+  }
+
+  notifyReadyForDeviceName(): Promise<DeviceNameMetadata> {
+    return sendWithPromise('notifyReadyForDeviceName');
+  }
+
+  attemptSetDeviceName(name: string): Promise<SetDeviceNameResult> {
+    return sendWithPromise('attemptSetDeviceName', name);
+  }
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.js b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.ts
similarity index 74%
rename from chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.ts
index fd34c24f..fd19daa 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_util.ts
@@ -10,36 +10,34 @@
  * DeviceNameState stores information about the states of the device name.
  * Numerical values from this enum must stay in sync with the C++ enum in
  * device_name_store.h.
- * @enum {number}
  */
-export const DeviceNameState = {
+export enum DeviceNameState {
   // We can modify the device name.
-  CAN_BE_MODIFIED: 0,
+  CAN_BE_MODIFIED = 0,
 
   // We cannot modify the device name because of active policies.
-  CANNOT_BE_MODIFIED_BECAUSE_OF_POLICIES: 1,
+  CANNOT_BE_MODIFIED_BECAUSE_OF_POLICIES = 1,
 
   // We cannot modify the device name because user is not device
   // owner.
-  CANNOT_BE_MODIFIED_BECAUSE_NOT_DEVICE_OWNER: 2,
-};
+  CANNOT_BE_MODIFIED_BECAUSE_NOT_DEVICE_OWNER = 2,
+}
 
 /**
  * NameUpdateResult stores information about the result of the name update
  * attempt. Numerical values from this enum must stay in sync with the C++ enum
  * in device_name_store.h.
- * @enum {number}
  */
-export const SetDeviceNameResult = {
+export enum SetDeviceNameResult {
   // Update was successful.
-  UPDATE_SUCCESSFUL: 0,
+  UPDATE_SUCCESSFUL = 0,
 
   // Update was unsuccessful because it is prohibited by policy.
-  ERROR_DUE_TO_POLICY: 1,
+  ERROR_DUE_TO_POLICY = 1,
 
   // Update was unsuccessful because user is not the device owner.
-  ERROR_DUE_TO_NOT_DEVICE_OWNER: 2,
+  ERROR_DUE_TO_NOT_DEVICE_OWNER = 2,
 
   // Update was unsuccessful because user input an invalid name.
-  ERROR_DUE_TO_INVALID_INPUT: 3,
-};
\ No newline at end of file
+  ERROR_DUE_TO_INVALID_INPUT = 3,
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
similarity index 77%
rename from chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
index fa86822..7312520 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
@@ -7,41 +7,39 @@
  * user to edit the device hostname.
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import '../../settings_shared.css.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './device_name_browser_proxy.js';
 import {SetDeviceNameResult} from './device_name_util.js';
 import {getTemplate} from './edit_hostname_dialog.html.js';
 
-/** @type {number} */
 const MAX_INPUT_LENGTH = 15;
 
-/** @type {number} */
 const MIN_INPUT_LENGTH = 1;
 
 const UNALLOWED_CHARACTERS = '[^0-9A-Za-z-]+';
 
-/** @type {RegExp} */
 const EMOJI_REGEX_EXP =
     /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi;
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const EditHostnameDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+interface EditHostnameDialogElement {
+  $: {
+    dialog: CrDialogElement,
+  };
+}
 
-/** @polymer */
+const EditHostnameDialogElementBase = I18nMixin(PolymerElement) as {
+  new (): PolymerElement & I18nMixinInterface,
+};
+
 class EditHostnameDialogElement extends EditHostnameDialogElementBase {
   static get is() {
     return 'edit-hostname-dialog';
@@ -53,51 +51,49 @@
 
   static get properties() {
     return {
-      /** @private {string} */
       deviceName_: {
         type: String,
         value: '',
         observer: 'onDeviceNameChanged_',
       },
 
-      /** @private {boolean} */
       isInputInvalid_: {
         type: Boolean,
         value: false,
         reflectToAttribute: true,
       },
 
-      /** @private {string} */
       inputCountString_: {
         type: String,
         computed: 'computeInputCountString_(deviceName_)',
       },
-
     };
   }
 
+  private deviceName_: string;
+  private isInputInvalid_: boolean;
+  private inputCountString_: string;
+
+  private deviceNameBrowserProxy_: DeviceNameBrowserProxy;
+
   constructor() {
     super();
 
-    /** @private {DeviceNameBrowserProxy} */
     this.deviceNameBrowserProxy_ = DeviceNameBrowserProxyImpl.getInstance();
   }
 
   /**
    * Returns a formatted string containing the current number of characters
    * entered in the input compared to the maximum number of characters allowed.
-   * @return {string}
-   * @private
    */
-  computeInputCountString_() {
+  private computeInputCountString_(): string {
     return this.i18n(
         'aboutDeviceNameInputCharacterCount',
         this.deviceName_.length.toLocaleString(),
         MAX_INPUT_LENGTH.toLocaleString());
   }
 
-  /** @private */
-  onCancelTap_() {
+  private onCancelTap_() {
     this.$.dialog.close();
   }
 
@@ -105,11 +101,8 @@
    * Observer for deviceName_ that sanitizes its value by removing any
    * Emojis and truncating it to MAX_INPUT_LENGTH. This method will be
    * recursively called until deviceName_ is fully sanitized.
-   * @param {string} newValue
-   * @param {string} oldValue
-   * @private
    */
-  onDeviceNameChanged_(newValue, oldValue) {
+  private onDeviceNameChanged_(_newValue: string, oldValue: string) {
     if (oldValue) {
       const sanitizedOldValue = oldValue.replace(EMOJI_REGEX_EXP, '');
       // If sanitizedOldValue.length > MAX_INPUT_LENGTH, the user attempted to
@@ -132,8 +125,7 @@
     }
   }
 
-  /** @private */
-  onDoneTap_() {
+  private onDoneTap_() {
     this.deviceNameBrowserProxy_.attemptSetDeviceName(this.deviceName_)
         .then(result => {
           this.handleSetDeviceNameResponse_(result);
@@ -141,15 +133,17 @@
     this.$.dialog.close();
   }
 
-  /**
-   * @param {SetDeviceNameResult} result
-   * @private
-   */
-  handleSetDeviceNameResponse_(result) {
+  private handleSetDeviceNameResponse_(result: SetDeviceNameResult) {
     if (result !== SetDeviceNameResult.UPDATE_SUCCESSFUL) {
       console.error('ERROR IN UPDATE', result);
     }
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'edit-hostname-dialog': EditHostnameDialogElement;
+  }
+}
+
 customElements.define(EditHostnameDialogElement.is, EditHostnameDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
similarity index 79%
rename from chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
index 6fb524d2..ded44ffd 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.ts
@@ -27,9 +27,9 @@
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
 import {parseHtmlSubset} from 'chrome://resources/js/parse_html_subset.m.js';
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerMixin, WebUIListenerMixinInterface} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
@@ -45,27 +45,33 @@
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent} from './about_page_browser_proxy.js';
 import {getTemplate} from './os_about_page.html.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {DeepLinkingBehaviorInterface}
- * @implements {WebUIListenerBehaviorInterface}
- * @implements {MainPageBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- * @implements {I18nBehaviorInterface}
- */
-const OsSettingsAboutPageBase = mixinBehaviors(
-    [
-      DeepLinkingBehavior,
-      WebUIListenerBehavior,
-      MainPageBehavior,
-      RouteObserverBehavior,
-      I18nBehavior,
-    ],
-    PolymerElement);
+declare global {
+  interface HTMLElementEventMap {
+    'target-channel-changed': CustomEvent<BrowserChannel>;
+  }
+}
 
-/** @polymer */
-class OsSettingsAboutPageElement extends OsSettingsAboutPageBase {
+interface OsSettingsAboutPageElement {
+  $: {
+    updateStatusMessageInner: HTMLDivElement,
+    'product-logo': HTMLImageElement,
+  };
+}
+
+const OsSettingsAboutPageBaseElement =
+    mixinBehaviors(
+        [
+          DeepLinkingBehavior,
+          MainPageBehavior,
+          RouteObserverBehavior,
+        ],
+        I18nMixin(WebUIListenerMixin(PolymerElement))) as {
+      new (): PolymerElement & DeepLinkingBehaviorInterface &
+          WebUIListenerMixinInterface & MainPageBehaviorInterface &
+          RouteObserverBehaviorInterface & I18nMixinInterface,
+    };
+
+class OsSettingsAboutPageElement extends OsSettingsAboutPageBaseElement {
   static get is() {
     return 'os-settings-about-page';
   }
@@ -78,14 +84,12 @@
     return {
       /**
        * Whether the about page is being rendered in dark mode.
-       * @private
        */
       isDarkModeActive_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {?UpdateStatusChangedEvent} */
       currentUpdateStatusEvent_: {
         type: Object,
         value: {
@@ -100,7 +104,6 @@
       /**
        * Whether the browser/ChromeOS is managed by their organization
        * through enterprise policies.
-       * @private
        */
       isManaged_: {
         type: Boolean,
@@ -111,7 +114,6 @@
 
       /**
        * The domain of the organization managing the device.
-       * @private
        */
       deviceManager_: {
         type: String,
@@ -120,96 +122,78 @@
         },
       },
 
-      /** @private */
       hasCheckedForUpdates_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {!BrowserChannel} */
       currentChannel_: String,
 
-      /** @private {!BrowserChannel} */
       targetChannel_: String,
 
-      /** @private */
       isLts_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private {?RegulatoryInfo} */
       regulatoryInfo_: Object,
 
-      /** @private */
       hasEndOfLife_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       hasDeferredUpdate_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       eolMessageWithMonthAndYear_: {
         type: String,
         value: '',
       },
 
-      /** @private */
       hasInternetConnection_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       firmwareUpdateCount_: {
         type: Number,
         value: 0,
       },
 
-      /** @private */
       showCrostini: Boolean,
 
-      /** @private */
       showCrostiniLicense_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       showUpdateStatus_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       showButtonContainer_: Boolean,
 
-      /** @private */
       showRelaunch_: {
         type: Boolean,
         value: false,
         computed: 'computeShowRelaunch_(currentUpdateStatusEvent_)',
       },
 
-      /** @private */
       showCheckUpdates_: {
         type: Boolean,
         computed: 'computeShowCheckUpdates_(' +
             'currentUpdateStatusEvent_, hasCheckedForUpdates_, hasEndOfLife_)',
       },
 
-      /** @protected */
       showFirmwareUpdatesApp_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('isFirmwareUpdaterAppEnabled'),
       },
 
-      /** @private {!Map<string, string>} */
       focusConfig_: {
         type: Object,
         value() {
@@ -223,28 +207,23 @@
         },
       },
 
-      /** @private */
       showUpdateWarningDialog_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       showTPMFirmwareUpdateLineItem_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       showTPMFirmwareUpdateDialog_: Boolean,
 
-      /** @private {!AboutPageUpdateInfo|undefined} */
       updateInfo_: Object,
 
       /**
        * Whether the deep link to the check for OS update setting was unable
        * to be shown.
-       * @private
        */
       isPendingOsUpdateDeepLink_: {
         type: Boolean,
@@ -253,7 +232,6 @@
 
       /**
        * Used by DeepLinkingBehavior to focus this page's deep links.
-       * @type {!Set<!Setting>}
        */
       supportedSettingIds: {
         type: Object,
@@ -279,22 +257,52 @@
     ];
   }
 
+  private isDarkModeActive_: boolean;
+  private currentUpdateStatusEvent_: UpdateStatusChangedEvent;
+  private isManaged_: boolean;
+  private deviceManager_: string;
+  private hasCheckedForUpdates_: boolean;
+  private currentChannel_: BrowserChannel;
+  private targetChannel_: BrowserChannel;
+  private isLts_: boolean;
+  private regulatoryInfo_: RegulatoryInfo|null;
+  private hasEndOfLife_: boolean;
+  private hasDeferredUpdate_: boolean;
+  private eolMessageWithMonthAndYear_: string;
+  private hasInternetConnection_: boolean;
+  private firmwareUpdateCount_: number;
+  private showCrostini: boolean;
+  private showCrostiniLicense_: boolean;
+  private showUpdateStatus_: boolean;
+  private showButtonContainer_: boolean;
+  private showRelaunch_: boolean;
+  private showCheckUpdates_: boolean;
+  protected showFirmwareUpdatesApp_: boolean;
+  private focusConfig_: Map<string, string>;
+  private showUpdateWarningDialog_: boolean;
+  private showTPMFirmwareUpdateLineItem_: boolean;
+  private showTPMFirmwareUpdateDialog_: boolean;
+  private updateInfo_?: AboutPageUpdateInfo;
+  private isPendingOsUpdateDeepLink_: boolean;
+  override supportedSettingIds: Set<Setting>;
+
+  private aboutBrowserProxy_: AboutPageBrowserProxy;
+
   constructor() {
     super();
 
-    /** @private {!AboutPageBrowserProxy} */
     this.aboutBrowserProxy_ = AboutPageBrowserProxyImpl.getInstance();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.aboutBrowserProxy_.pageReady();
 
-    this.addEventListener('target-channel-changed', e => {
-      this.targetChannel_ = e.detail;
-    });
+    this.addEventListener(
+        'target-channel-changed', (e: CustomEvent<BrowserChannel>) => {
+          this.targetChannel_ = e.detail;
+        });
 
     this.aboutBrowserProxy_.getChannelInfo().then(info => {
       this.currentChannel_ = info.currentChannel;
@@ -328,12 +336,7 @@
     }
   }
 
-  /**
-   * @override
-   * @param {!Route} newRoute
-   * @param {!Route=} oldRoute
-   */
-  currentRouteChanged(newRoute, oldRoute) {
+  override currentRouteChanged(newRoute: Route, oldRoute?: Route) {
     // super.currentRouteChanged() does not produce desired results since
     // RouteObserverBehavior has higher precedence than MainPageBehavior given
     // this element's behavior list order. In order to trigger the
@@ -356,13 +359,11 @@
     });
   }
 
-  /** @override */
-  containsRoute(route) {
+  override containsRoute(route: Route) {
     return !route || routes.ABOUT.contains(route);
   }
 
-  /** @private */
-  startListening_() {
+  private startListening_() {
     this.addWebUIListener(
         'update-status-changed', this.onUpdateStatusChanged_.bind(this));
     this.aboutBrowserProxy_.refreshUpdateStatus();
@@ -372,11 +373,7 @@
     this.aboutBrowserProxy_.refreshTPMFirmwareUpdateStatus();
   }
 
-  /**
-   * @param {!UpdateStatusChangedEvent} event
-   * @private
-   */
-  onUpdateStatusChanged_(event) {
+  private onUpdateStatusChanged_(event: UpdateStatusChangedEvent) {
     if (event.status === UpdateStatus.CHECKING) {
       this.hasCheckedForUpdates_ = true;
     } else if (event.status === UpdateStatus.NEED_PERMISSION_TO_UPDATE) {
@@ -387,47 +384,37 @@
     this.currentUpdateStatusEvent_ = event;
   }
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  onLearnMoreClick_(event) {
+  private onLearnMoreClick_(event: Event) {
     // Stop the propagation of events, so that clicking on links inside
     // actionable items won't trigger action.
     event.stopPropagation();
   }
 
-  /** @private */
-  onReleaseNotesTap_() {
+  private onReleaseNotesTap_() {
     this.aboutBrowserProxy_.launchReleaseNotes();
   }
 
-  /** @private */
-  onHelpClick_() {
+  private onHelpClick_() {
     this.aboutBrowserProxy_.openOsHelpPage();
   }
 
-  /** @private */
-  onDiagnosticsClick_() {
+  private onDiagnosticsClick_() {
     this.aboutBrowserProxy_.openDiagnostics();
     recordSettingChange(Setting.kDiagnostics);
   }
 
-  /** @private */
-  onFirmwareUpdatesClick_() {
+  private onFirmwareUpdatesClick_() {
     assert(this.showFirmwareUpdatesApp_);
     this.aboutBrowserProxy_.openFirmwareUpdatesPage();
     recordSettingChange(Setting.kFirmwareUpdates);
   }
 
-  /** @private */
-  onRelaunchClick_() {
+  private onRelaunchClick_() {
     recordSettingChange();
     LifetimeBrowserProxyImpl.getInstance().relaunch();
   }
 
-  /** @private */
-  updateShowUpdateStatus_() {
+  private updateShowUpdateStatus_() {
     // Do not show the "updated" status or error states from a previous update
     // attempt if we haven't checked yet or the update warning dialog is shown
     // to user.
@@ -455,9 +442,8 @@
   /**
    * Hide the button container if all buttons are hidden, otherwise the
    * container displays an unwanted border (see separator class).
-   * @private
    */
-  updateShowButtonContainer_() {
+  private updateShowButtonContainer_() {
     this.showButtonContainer_ = this.showRelaunch_ || this.showCheckUpdates_;
 
     // Check if we have yet to focus the check for update button.
@@ -472,32 +458,19 @@
     });
   }
 
-  /** @private */
-  computeShowRelaunch_() {
+  private computeShowRelaunch_() {
     return this.checkStatus_(UpdateStatus.NEARLY_UPDATED);
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowLearnMoreLink_() {
+  private shouldShowLearnMoreLink_(): boolean {
     return this.currentUpdateStatusEvent_.status === UpdateStatus.FAILED;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowFirmwareUpdatesBadge_() {
+  private shouldShowFirmwareUpdatesBadge_(): boolean {
     return this.showFirmwareUpdatesApp_ && this.firmwareUpdateCount_ > 0;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getUpdateStatusMessage_() {
+  private getUpdateStatusMessage_(): string {
     switch (this.currentUpdateStatusEvent_.status) {
       case UpdateStatus.CHECKING:
       case UpdateStatus.NEED_PERMISSION_TO_UPDATE:
@@ -516,7 +489,7 @@
         return this.i18nAdvanced('aboutUpgradeUpToDate');
       case UpdateStatus.UPDATING:
         assert(typeof this.currentUpdateStatusEvent_.progress === 'number');
-        const progressPercent = this.currentUpdateStatusEvent_.progress + '%';
+        const progressPercent = this.currentUpdateStatusEvent_.progress! + '%';
 
         if (this.currentChannel_ !== this.targetChannel_) {
           return this.i18nAdvanced('aboutUpgradeUpdatingChannelSwitch', {
@@ -532,7 +505,7 @@
             substitutions: [this.deviceManager_, progressPercent],
           });
         }
-        if (this.currentUpdateStatusEvent_.progress > 0) {
+        if (this.currentUpdateStatusEvent_.progress! > 0) {
           // NOTE(dbeam): some platforms (i.e. Mac) always send 0% while
           // updating (they don't support incremental upgrade progress). Though
           // it's certainly quite possible to validly end up here with 0% on
@@ -552,9 +525,10 @@
       case UpdateStatus.DEFERRED:
         return this.i18nAdvanced('aboutUpgradeNotUpToDate');
       default:
-        function formatMessage(msg) {
-          return parseHtmlSubset('<b>' + msg + '</b>', ['br', 'pre'])
-              .firstChild.innerHTML;
+        function formatMessage(msg: string) {
+          return (parseHtmlSubset('<b>' + msg + '</b>', ['br', 'pre'])
+                      .firstChild as HTMLElement)
+              .innerHTML;
         }
         let result = '';
         const message = this.currentUpdateStatusEvent_.message;
@@ -569,11 +543,7 @@
     }
   }
 
-  /**
-   * @return {?string}
-   * @private
-   */
-  getUpdateStatusIcon_() {
+  private getUpdateStatusIcon_(): string|null {
     // If Chrome OS has reached end of life, display a special icon and
     // ignore UpdateStatus.
     if (this.hasEndOfLife_) {
@@ -598,11 +568,7 @@
     }
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getFirmwareUpdatesIcon_() {
+  private getFirmwareUpdatesIcon_(): string {
     if (this.firmwareUpdateCount_ === 0) {
       return '';
     }
@@ -614,11 +580,7 @@
     return `os-settings:counter-${updateBadgeId}`;
   }
 
-  /**
-   * @return {?string}
-   * @private
-   */
-  getThrobberSrcIfUpdating_() {
+  private getThrobberSrcIfUpdating_(): string|null {
     if (this.hasEndOfLife_) {
       return null;
     }
@@ -634,38 +596,23 @@
     }
   }
 
-  /**
-   * @param {!UpdateStatus} status
-   * @return {boolean}
-   * @private
-   */
-  checkStatus_(status) {
+  private checkStatus_(status: UpdateStatus): boolean {
     return this.currentUpdateStatusEvent_.status === status;
   }
 
-  /** @private */
-  onManagementPageClick_() {
+  private onManagementPageClick_() {
     window.open('chrome://management');
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isPowerwash_() {
-    return this.currentUpdateStatusEvent_.powerwash;
+  private isPowerwash_(): boolean {
+    return !!this.currentUpdateStatusEvent_.powerwash;
   }
 
-  /** @private */
-  onDetailedBuildInfoClick_() {
+  private onDetailedBuildInfoClick_() {
     Router.getInstance().navigateTo(routes.DETAILED_BUILD_INFO);
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getRelaunchButtonText_() {
+  private getRelaunchButtonText_(): string {
     if (this.checkStatus_(UpdateStatus.NEARLY_UPDATED)) {
       if (this.isPowerwash_()) {
         return this.i18nAdvanced('aboutRelaunchAndPowerwash');
@@ -676,30 +623,23 @@
     return '';
   }
 
-  /** @private */
-  onCheckUpdatesClick_() {
+  private onCheckUpdatesClick_() {
     this.onUpdateStatusChanged_({status: UpdateStatus.CHECKING});
     this.aboutBrowserProxy_.requestUpdate();
     this.$.updateStatusMessageInner.focus();
   }
 
-  /** @private */
-  onApplyDeferredUpdateClick_() {
+  private onApplyDeferredUpdateClick_() {
     this.aboutBrowserProxy_.applyDeferredUpdate();
     this.$.updateStatusMessageInner.focus();
   }
 
-  /** @private */
-  onApplyAndSetAutoUpdateClick_() {
+  private onApplyAndSetAutoUpdateClick_() {
     this.aboutBrowserProxy_.setConsumerAutoUpdate(true);
     this.onApplyDeferredUpdateClick_();
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  computeShowCheckUpdates_() {
+  private computeShowCheckUpdates_(): boolean {
     // Disable update button if the device is end of life.
     if (this.hasEndOfLife_) {
       return false;
@@ -716,77 +656,55 @@
   }
 
   /**
-   * @param {boolean} showCrostiniLicense True if Crostini is enabled and
+   * @param showCrostiniLicense True if Crostini is enabled and
    * Crostini UI is allowed.
-   * @return {string}
-   * @private
    */
-  getAboutProductOsLicense_(showCrostiniLicense) {
+  private getAboutProductOsLicense_(showCrostiniLicense: boolean): string {
     return showCrostiniLicense ?
         this.i18nAdvanced('aboutProductOsWithLinuxLicense') :
         this.i18nAdvanced('aboutProductOsLicense');
   }
 
   /**
-   * @param {boolean} enabled True if Crostini is enabled.
-   * @private
+   * @param enabled True if Crostini is enabled.
    */
-  handleCrostiniEnabledChanged_(enabled) {
+  private handleCrostiniEnabledChanged_(enabled: boolean) {
     this.showCrostiniLicense_ = enabled && this.showCrostini;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowSafetyInfo_() {
+  private shouldShowSafetyInfo_(): boolean {
     return loadTimeData.getBoolean('shouldShowSafetyInfo');
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowRegulatoryInfo_() {
+  private shouldShowRegulatoryInfo_(): boolean {
     return this.regulatoryInfo_ !== null;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowRegulatoryOrSafetyInfo_() {
+  private shouldShowRegulatoryOrSafetyInfo_(): boolean {
     return this.shouldShowSafetyInfo_() || this.shouldShowRegulatoryInfo_();
   }
 
-  /** @private */
-  onUpdateWarningDialogClose_() {
+  private onUpdateWarningDialogClose_() {
     this.showUpdateWarningDialog_ = false;
     // Shows 'check for updates' button in case that the user cancels the
     // dialog and then intends to check for update again.
     this.hasCheckedForUpdates_ = false;
   }
 
-  /**
-   * @param {!TPMFirmwareUpdateStatusChangedEvent} event
-   * @private
-   */
-  onTPMFirmwareUpdateStatusChanged_(event) {
+  private onTPMFirmwareUpdateStatusChanged_(
+      event: TPMFirmwareUpdateStatusChangedEvent) {
     this.showTPMFirmwareUpdateLineItem_ = event.updateAvailable;
   }
 
-  /** @private */
-  onTPMFirmwareUpdateClick_() {
+  private onTPMFirmwareUpdateClick_() {
     this.showTPMFirmwareUpdateDialog_ = true;
   }
 
-  /** @private */
-  onPowerwashDialogClose_() {
+  private onPowerwashDialogClose_() {
     this.showTPMFirmwareUpdateDialog_ = false;
   }
 
-  /** @private */
-  onProductLogoClick_() {
+  private onProductLogoClick_() {
     this.$['product-logo'].animate(
         {
           transform: ['none', 'rotate(-10turn)'],
@@ -798,27 +716,18 @@
   }
 
   // <if expr="_google_chrome">
-  /** @private */
-  onReportIssueClick_() {
+  private onReportIssueClick_() {
     this.aboutBrowserProxy_.openFeedbackDialog();
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getReportIssueLabel_() {
+  private getReportIssueLabel_(): string {
     return loadTimeData.getBoolean('isOsFeedbackEnabled') ?
         this.i18nAdvanced('aboutSendFeedback') :
         this.i18nAdvanced('aboutReportAnIssue');
   }
   // </if>
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowIcons_() {
+  private shouldShowIcons_(): boolean {
     if (this.hasEndOfLife_) {
       return true;
     }
@@ -826,5 +735,11 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'os-settings-about-page': OsSettingsAboutPageElement;
+  }
+}
+
 customElements.define(
     OsSettingsAboutPageElement.is, OsSettingsAboutPageElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.ts
similarity index 60%
rename from chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
rename to chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.ts
index 471f12a..d3b44f3 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.ts
@@ -8,24 +8,25 @@
  * agrees to download update using mobile data.
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import '../../settings_shared.css.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo} from './about_page_browser_proxy.js';
 import {getTemplate} from './update_warning_dialog.html.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const SettingsUpdateWarningDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+interface SettingsUpdateWarningDialogElement {
+  $: {
+    dialog: CrDialogElement,
+  };
+}
 
-/** @polymer */
+const SettingsUpdateWarningDialogElementBase = I18nMixin(PolymerElement) as {
+  new (): PolymerElement & I18nMixinInterface,
+};
+
 class SettingsUpdateWarningDialogElement extends
     SettingsUpdateWarningDialogElementBase {
   static get is() {
@@ -38,7 +39,6 @@
 
   static get properties() {
     return {
-      /** @type {!AboutPageUpdateInfo|undefined} */
       updateInfo: {
         type: Object,
         observer: 'updateInfoChanged_',
@@ -46,40 +46,43 @@
     };
   }
 
+  updateInfo?: AboutPageUpdateInfo;
+
+  private browserProxy_: AboutPageBrowserProxy;
+
   constructor() {
     super();
 
-    /** @private {AboutPageBrowserProxy} */
     this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.$.dialog.showModal();
   }
 
-  /** @private */
-  onCancelTap_() {
+  private onCancelTap_() {
     this.$.dialog.close();
   }
 
-  /** @private */
-  onContinueTap_() {
-    if (!this.updateInfo || !this.updateInfo.version || !this.updateInfo.size){
+  private onContinueTap_() {
+    if (!this.updateInfo || !this.updateInfo.version || !this.updateInfo.size) {
       console.warn('ERROR: requestUpdateOverCellular arguments are undefined');
       return;
     }
     this.browserProxy_.requestUpdateOverCellular(
-        /** @type {!string} */ (this.updateInfo.version),
-        /** @type {!string} */ (this.updateInfo.size));
+        this.updateInfo.version, this.updateInfo.size);
     this.$.dialog.close();
   }
 
-  /** @private */
-  updateInfoChanged_() {
-    this.shadowRoot.querySelector('#update-warning-message').innerHTML =
+  private updateInfoChanged_() {
+    if (!this.updateInfo || this.updateInfo.size === undefined) {
+      console.warn('ERROR: Update size is undefined');
+      return;
+    }
+
+    this.shadowRoot!.querySelector('#update-warning-message')!.innerHTML =
         this.i18n(
             'aboutUpdateWarningMessage',
             // Convert bytes to megabytes
@@ -87,5 +90,11 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-update-warning-dialog': SettingsUpdateWarningDialogElement;
+  }
+}
+
 customElements.define(
     SettingsUpdateWarningDialogElement.is, SettingsUpdateWarningDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
index eda15f44..8b7d7fa 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
@@ -89,7 +89,7 @@
     "//ui/webui/resources/cr_elements:cr_scrollable_behavior.m",
     "//ui/webui/resources/cr_elements:find_shortcut_behavior",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
-    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
+    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox",
     "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
   ]
   externs_list = [ "//ui/webui/resources/cr_elements/cr_search_field/cr_search_field_externs.js" ]
@@ -122,7 +122,7 @@
     "//third_party/polymer/v3_0/components-chromium/iron-icon:iron-icon",
     "//third_party/polymer/v3_0/components-chromium/paper-tooltip:paper-tooltip",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
+    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/cr_checkbox_with_policy.js b/chrome/browser/resources/settings/chromeos/os_languages_page/cr_checkbox_with_policy.js
index 131b803..3181a4c 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/cr_checkbox_with_policy.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/cr_checkbox_with_policy.js
@@ -21,7 +21,7 @@
  * element to focus. This does not work when the element to focus is in a shadow
  * root.
  */
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.js b/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.js
index cab7f7b..12fa7c8 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.js
@@ -8,7 +8,7 @@
  * from the user for a device reset (aka powerwash).
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/cr_components/localized_link/localized_link.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index e60811b..3d215d8 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -83,12 +83,12 @@
 # TODO(crbug/1292025) Files that have their HTML wrapper file generated
 # by html_to_wrapper() go here.
 web_component_files = [
-  "chromeos/os_about_page/channel_switcher_dialog.js",
-  "chromeos/os_about_page/consumer_auto_update_toggle_dialog.js",
-  "chromeos/os_about_page/detailed_build_info.js",
-  "chromeos/os_about_page/edit_hostname_dialog.js",
-  "chromeos/os_about_page/os_about_page.js",
-  "chromeos/os_about_page/update_warning_dialog.js",
+  "chromeos/os_about_page/channel_switcher_dialog.ts",
+  "chromeos/os_about_page/consumer_auto_update_toggle_dialog.ts",
+  "chromeos/os_about_page/detailed_build_info.ts",
+  "chromeos/os_about_page/edit_hostname_dialog.ts",
+  "chromeos/os_about_page/os_about_page.ts",
+  "chromeos/os_about_page/update_warning_dialog.ts",
 ]
 
 # Files that are passed as input to html_to_wrapper().
@@ -147,9 +147,9 @@
   "chromeos/os_a11y_page/switch_access_subpage_browser_proxy.js",
   "chromeos/os_a11y_page/text_to_speech_page_browser_proxy.js",
   "chromeos/os_a11y_page/tts_subpage_browser_proxy.js",
-  "chromeos/os_about_page/about_page_browser_proxy.js",
-  "chromeos/os_about_page/device_name_browser_proxy.js",
-  "chromeos/os_about_page/device_name_util.js",
+  "chromeos/os_about_page/about_page_browser_proxy.ts",
+  "chromeos/os_about_page/device_name_browser_proxy.ts",
+  "chromeos/os_about_page/device_name_util.ts",
   "chromeos/os_apps_page/android_apps_browser_proxy.js",
   "chromeos/os_apps_page/app_management_page/actions.js",
   "chromeos/os_apps_page/app_management_page/api_listener.js",
@@ -183,6 +183,10 @@
   "chromeos/personalization_page/change_picture_browser_proxy.js",
   "chromeos/personalization_page/personalization_hub_browser_proxy.js",
   "chromeos/personalization_page/wallpaper_browser_proxy.js",
+
+  # Files below are from Browser Settings and shared with ChromeOS Settings
+  "i18n_setup.ts",
+  "lifetime_browser_proxy.ts",
   "router.js",
 ]
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index b90cd15..6b1edc7 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -150,7 +150,7 @@
 export {TextToSpeechPageBrowserProxy, TextToSpeechPageBrowserProxyImpl} from './os_a11y_page/text_to_speech_page_browser_proxy.js';
 export {TtsSubpageBrowserProxy, TtsSubpageBrowserProxyImpl} from './os_a11y_page/tts_subpage_browser_proxy.js';
 export {AboutPageBrowserProxyImpl, BrowserChannel, UpdateStatus} from './os_about_page/about_page_browser_proxy.js';
-export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.js';
+export {DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.js';
 export {DeviceNameState, SetDeviceNameResult} from './os_about_page/device_name_util.js';
 export {AndroidAppsBrowserProxyImpl} from './os_apps_page/android_apps_browser_proxy.js';
 export {addApp, changeApp, removeApp, updateSelectedAppId} from './os_apps_page/app_management_page/actions.js';
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/installed_app_checkbox.ts b/chrome/browser/resources/settings/clear_browsing_data_dialog/installed_app_checkbox.ts
index 3fd4b17..fbf1c0b 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/installed_app_checkbox.ts
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/installed_app_checkbox.ts
@@ -7,7 +7,7 @@
  * An installed app could be a domain with data that the user might want
  * to protect from being deleted.
  */
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
 import '../settings_shared.css.js';
 import '../site_favicon.js';
diff --git a/chrome/browser/resources/settings/controls/settings_checkbox.ts b/chrome/browser/resources/settings/controls/settings_checkbox.ts
index abcabe6..ac2ccfe 100644
--- a/chrome/browser/resources/settings/controls/settings_checkbox.ts
+++ b/chrome/browser/resources/settings/controls/settings_checkbox.ts
@@ -6,11 +6,11 @@
  * @fileoverview
  * `settings-checkbox` is a checkbox that controls a supplied preference.
  */
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
 import '../settings_shared.css.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {SettingsBooleanControlMixin} from './settings_boolean_control_mixin.js';
diff --git a/chrome/browser/resources/settings/languages_page/add_languages_dialog.ts b/chrome/browser/resources/settings/languages_page/add_languages_dialog.ts
index bc41dc3e..3684324 100644
--- a/chrome/browser/resources/settings/languages_page/add_languages_dialog.ts
+++ b/chrome/browser/resources/settings/languages_page/add_languages_dialog.ts
@@ -7,14 +7,14 @@
  * languages.
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import '../settings_shared.css.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {CrScrollableBehavior} from 'chrome://resources/cr_elements/cr_scrollable_behavior.m.js';
 import {CrSearchFieldElement} from 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.ts b/chrome/browser/resources/settings/languages_page/languages_page.ts
index 1a669b4..dd21975 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.ts
+++ b/chrome/browser/resources/settings/languages_page/languages_page.ts
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_components/managed_dialog/managed_dialog.js';
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import 'chrome://resources/cr_elements/icons.m.js';
@@ -29,7 +29,7 @@
 import '../settings_vars.css.js';
 
 import {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {isWindows} from 'chrome://resources/js/cr.m.js';
diff --git a/chrome/browser/resources/settings/languages_page/spell_check_page.ts b/chrome/browser/resources/settings/languages_page/spell_check_page.ts
index 91c72817..c23d92f 100644
--- a/chrome/browser/resources/settings/languages_page/spell_check_page.ts
+++ b/chrome/browser/resources/settings/languages_page/spell_check_page.ts
@@ -139,7 +139,7 @@
 
   private onSelectedSpellingServiceChange_() {
     this.languageSettingsMetricsProxy_.recordSettingsMetric(
-      this.prefs.spellcheck.use_spelling_service ?
+      this.prefs.spellcheck.use_spelling_service.value ?
           LanguageSettingsActionType.SELECT_ENHANCED_SPELL_CHECK :
           LanguageSettingsActionType.SELECT_BASIC_SPELL_CHECK);
   }
diff --git a/chrome/browser/resources/settings/lazy_load.ts b/chrome/browser/resources/settings/lazy_load.ts
index 27eec361..aa0f0b8 100644
--- a/chrome/browser/resources/settings/lazy_load.ts
+++ b/chrome/browser/resources/settings/lazy_load.ts
@@ -69,7 +69,7 @@
 
 // </if>
 
-export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+export {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 export {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 export {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 export {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/settings/people_page/signout_dialog.ts b/chrome/browser/resources/settings/people_page/signout_dialog.ts
index aaf8ea6..b327b16 100644
--- a/chrome/browser/resources/settings/people_page/signout_dialog.ts
+++ b/chrome/browser/resources/settings/people_page/signout_dialog.ts
@@ -7,7 +7,7 @@
  * user to turn off sync and sign out of Chromium.
  */
 import '//resources/cr_elements/cr_button/cr_button.m.js';
-import '//resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import '//resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import '//resources/cr_elements/cr_expand_button/cr_expand_button.js';
 import '//resources/cr_elements/shared_style_css.m.js';
diff --git a/chrome/browser/resources/settings/reset_page/reset_profile_dialog.ts b/chrome/browser/resources/settings/reset_page/reset_profile_dialog.ts
index fa110714..8357b05 100644
--- a/chrome/browser/resources/settings/reset_page/reset_profile_dialog.ts
+++ b/chrome/browser/resources/settings/reset_page/reset_profile_dialog.ts
@@ -11,7 +11,7 @@
  * variant will be used.
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/js/action_link.js';
 import 'chrome://resources/cr_elements/action_link_css.m.js';
@@ -19,7 +19,7 @@
 import '../settings_shared.css.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {PaperSpinnerLiteElement} from 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
diff --git a/chrome/browser/resources/settings/site_settings/add_site_dialog.ts b/chrome/browser/resources/settings/site_settings/add_site_dialog.ts
index c7a3f30d..93114f8 100644
--- a/chrome/browser/resources/settings/site_settings/add_site_dialog.ts
+++ b/chrome/browser/resources/settings/site_settings/add_site_dialog.ts
@@ -8,13 +8,13 @@
  * Settings category.
  */
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import '../settings_shared.css.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
index 9616b03b..40c54c3 100644
--- a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
+++ b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/cr_elements/icons.m.js';
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
index e5c11618..ed3a417 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
index 5af0677..11e0702cc2 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
@@ -6,14 +6,14 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import './icons.js';
 import './profile_card.js';
 import './profile_picker_shared.css.js';
 import './strings.m.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/support_tool/data_collectors.ts b/chrome/browser/resources/support_tool/data_collectors.ts
index cba80de..3481fdc 100644
--- a/chrome/browser/resources/support_tool/data_collectors.ts
+++ b/chrome/browser/resources/support_tool/data_collectors.ts
@@ -4,7 +4,7 @@
 
 import './support_tool_shared.css.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/support_tool/pii_selection.ts b/chrome/browser/resources/support_tool/pii_selection.ts
index b51de97..26fb2de 100644
--- a/chrome/browser/resources/support_tool/pii_selection.ts
+++ b/chrome/browser/resources/support_tool/pii_selection.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
diff --git a/chrome/browser/resources/support_tool/url_generator.ts b/chrome/browser/resources/support_tool/url_generator.ts
index e396554..d8bd180 100644
--- a/chrome/browser/resources/support_tool/url_generator.ts
+++ b/chrome/browser/resources/support_tool/url_generator.ts
@@ -6,7 +6,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts
index e7c94c7..0c4b313 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_action_menu_demo_component.ts b/chrome/browser/resources/webui_gallery/demos/cr_action_menu_demo_component.ts
index 83dd173..6c54fbc4 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_action_menu_demo_component.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_action_menu_demo_component.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_icons_css.m.js';
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_checkbox_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_checkbox_demo.html
index 132f1892..96c3cde 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_checkbox_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_checkbox_demo.html
@@ -20,7 +20,7 @@
       </template>
     </dom-bind>
 
-    <script src="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js"
+    <script src="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js"
         type="module"></script>
   </body>
 </html>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_dialog_demo_component.ts b/chrome/browser/resources/webui_gallery/demos/cr_dialog_demo_component.ts
index 2812b14..f4161d4 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_dialog_demo_component.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_dialog_demo_component.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 
@@ -86,4 +86,4 @@
   }
 }
 
-customElements.define(CrDialogDemoComponent.is, CrDialogDemoComponent);
\ No newline at end of file
+customElements.define(CrDialogDemoComponent.is, CrDialogDemoComponent);
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_slider/cr_slider_demo_component.ts b/chrome/browser/resources/webui_gallery/demos/cr_slider/cr_slider_demo_component.ts
index 18a08a9..05384020 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_slider/cr_slider_demo_component.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_slider/cr_slider_demo_component.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 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
 
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index f982ee32..5b037d51 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -256,10 +256,10 @@
 
 void ChromeSigninClient::DelayNetworkCall(base::OnceClosure callback) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  // Do not make network requests in unit tests. chromeos::NetworkHandler should
+  // Do not make network requests in unit tests. ash::NetworkHandler should
   // not be used and is not expected to have been initialized in unit tests.
   if (url_loader_factory_for_testing_ &&
-      !chromeos::NetworkHandler::IsInitialized()) {
+      !ash::NetworkHandler::IsInitialized()) {
     std::move(callback).Run();
     return;
   }
diff --git a/chrome/browser/speech/speech_recognition_client_browser_interface.h b/chrome/browser/speech/speech_recognition_client_browser_interface.h
index 04a3dbaa..da12d57 100644
--- a/chrome/browser/speech/speech_recognition_client_browser_interface.h
+++ b/chrome/browser/speech/speech_recognition_client_browser_interface.h
@@ -48,7 +48,9 @@
   void OnSodaInstalled(speech::LanguageCode language_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override {}
-  void OnSodaError(speech::LanguageCode language_code) override {}
+  void OnSodaInstallError(
+      speech::LanguageCode language_code,
+      speech::SodaInstaller::ErrorCode error_code) override {}
 
  private:
   void OnSpeechRecognitionAvailabilityChanged();
diff --git a/chrome/browser/sync/sync_service_factory_unittest.cc b/chrome/browser/sync/sync_service_factory_unittest.cc
index 3369657..24e7b1b 100644
--- a/chrome/browser/sync/sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/sync_service_factory_unittest.cc
@@ -74,9 +74,9 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   SyncServiceFactoryTest() {
     // Fake network stack is required for WIFI_CONFIGURATIONS datatype.
-    chromeos::NetworkHandler::Initialize();
+    ash::NetworkHandler::Initialize();
   }
-  ~SyncServiceFactoryTest() override { chromeos::NetworkHandler::Shutdown(); }
+  ~SyncServiceFactoryTest() override { ash::NetworkHandler::Shutdown(); }
 #else
   SyncServiceFactoryTest() = default;
   ~SyncServiceFactoryTest() override = default;
diff --git a/chrome/browser/sync/test/integration/secondary_account_helper.cc b/chrome/browser/sync/test/integration/secondary_account_helper.cc
index 1d2984d..4f42d37 100644
--- a/chrome/browser/sync/test/integration/secondary_account_helper.cc
+++ b/chrome/browser/sync/test/integration/secondary_account_helper.cc
@@ -47,9 +47,8 @@
 void InitNetwork() {
   auto* portal_detector = new ash::NetworkPortalDetectorTestImpl();
 
-  const ash::NetworkState* default_network = chromeos::NetworkHandler::Get()
-                                                 ->network_state_handler()
-                                                 ->DefaultNetwork();
+  const ash::NetworkState* default_network =
+      ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
 
   portal_detector->SetDefaultNetworkForTesting(default_network->guid());
 
diff --git a/chrome/browser/sync/wifi_configuration_sync_service_factory.cc b/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
index f1f1b76..96a85edd 100644
--- a/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
+++ b/chrome/browser/sync/wifi_configuration_sync_service_factory.cc
@@ -41,8 +41,7 @@
   // Run when signed in to a real account.  Skip during tests when network stack
   // has not been initialized.
   return profile && ash::ProfileHelper::IsRegularProfile(profile) &&
-         !profile->IsOffTheRecord() &&
-         chromeos::NetworkHandler::IsInitialized();
+         !profile->IsOffTheRecord() && ash::NetworkHandler::IsInitialized();
 }
 
 WifiConfigurationSyncServiceFactory::WifiConfigurationSyncServiceFactory()
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3ff96cce..379c2ee1 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1844,6 +1844,7 @@
       "//ui/base/dragdrop/mojom:mojom_headers",
     ]
 
+    # TODO(crbug.com/1030821): Resolve circular dependencies.
     allow_circular_includes_from += [ "//chrome/browser/media/router" ]
 
     if (use_ozone && !is_chromeos_ash) {
@@ -5468,8 +5469,8 @@
       "views/extensions/media_galleries_dialog_views.h",
       "views/extensions/media_gallery_checkbox_view.cc",
       "views/extensions/media_gallery_checkbox_view.h",
-      "views/extensions/settings_overridden_dialog_view.cc",
-      "views/extensions/settings_overridden_dialog_view.h",
+      "views/extensions/settings_overridden_dialog.cc",
+      "views/extensions/settings_overridden_dialog.h",
       "views/javascript_app_modal_event_blocker.h",
       "web_applications/app_browser_controller.cc",
       "web_applications/app_browser_controller.h",
diff --git a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
index d48371a..5d29064 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 
 import("//build/config/android/rules.gni")
+import("//build/config/locales.gni")
+import("//chrome/common/features.gni")
+import("//tools/grit/grit_rule.gni")
 
 android_library("java") {
   sources = [
@@ -10,18 +13,39 @@
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutModel.java",
-    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutView.java",
-    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutViewBinder.java",
+    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java",
   ]
   deps = [
+    ":java_resources",
     "//base:base_java",
     "//base:jni_java",
     "//build/android:build_java",
     "//chrome/browser/ui/android/fast_checkout:java",
+    "//chrome/browser/ui/android/strings:ui_strings_grd",
     "//components/autofill_assistant/android:public_java",
     "//components/browser_ui/bottomsheet/android:java",
+    "//components/browser_ui/strings/android:browser_ui_strings_grd",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//ui/android:ui_no_recycler_view_java",
   ]
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+  resources_package = "org.chromium.chrome.browser.ui.fast_checkout"
+}
+
+android_resources("java_resources") {
+  deps = [
+    ":java_strings_grd",
+    "//ui/android:ui_java_resources",
+  ]
+  custom_package = "org.chromium.chrome.browser.ui.fast_checkout"
+}
+
+java_strings_grd("java_strings_grd") {
+  defines = chrome_grit_defines
+  grd_file = "java/strings/android_fast_checkout_strings.grd"
+  outputs =
+      [ "values/android_fast_checkout_strings.xml" ] +
+      process_file_template(
+          android_bundle_locales_as_resources,
+          [ "values-{{source_name_part}}/android_fast_checkout_strings.xml" ])
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
index 74109af..fb9c96e 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
@@ -12,25 +12,37 @@
 import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutCreditCard;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 class FastCheckoutCoordinator implements FastCheckoutComponent {
     private FastCheckoutMediator mMediator = new FastCheckoutMediator();
+    private PropertyModel mModel = FastCheckoutModel.createDefaultModel();
+    private FastCheckoutSheetContent mContent;
 
     @Override
     public void initialize(Context context, BottomSheetController sheetController,
             FastCheckoutComponent.Delegate delegate) {
-        PropertyModel model = FastCheckoutModel.createDefaultModel(mMediator::onDismissed);
-        mMediator.initialize(delegate, model, sheetController);
+        mMediator.initialize(delegate, mModel, sheetController);
 
         LinearLayout rootView = new LinearLayout(context);
         rootView.setLayoutParams(new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
         rootView.setOrientation(LinearLayout.VERTICAL);
 
-        // TODO(crbug.com/1334642): Create views for all 3 screens.
+        mContent = new FastCheckoutSheetContent(rootView);
 
-        setUpModelChangeProcessors(model, new FastCheckoutView(rootView, sheetController));
+        // TODO(crbug.com/1334642): Create views for all 3 screens.
+        mModel.addObserver((source, propertyKey) -> {
+            if (FastCheckoutModel.CURRENT_SCREEN == propertyKey) {
+                mContent.updateCurrentScreen(mModel.get(FastCheckoutModel.CURRENT_SCREEN));
+            } else if (FastCheckoutModel.VISIBLE == propertyKey) {
+                // Dismiss the sheet if it can't be immediately shown.
+                boolean visibilityChangeSuccessful =
+                        mMediator.setVisible(mModel.get(FastCheckoutModel.VISIBLE), mContent);
+                if (!visibilityChangeSuccessful && mModel.get(FastCheckoutModel.VISIBLE)) {
+                    mMediator.dismiss(BottomSheetController.StateChangeReason.NONE);
+                }
+            }
+        });
     }
 
     @Override
@@ -38,9 +50,4 @@
             FastCheckoutAutofillProfile[] profiles, FastCheckoutCreditCard[] creditCards) {
         mMediator.showOptions(profiles, creditCards);
     }
-
-    static void setUpModelChangeProcessors(PropertyModel model, FastCheckoutView view) {
-        PropertyModelChangeProcessor.create(
-                model, view, FastCheckoutViewBinder::bindFastCheckoutView);
-    }
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
index 43ed1628..93929e4 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
@@ -6,6 +6,7 @@
 
 import android.view.View;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 
 import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutAutofillProfile;
@@ -27,6 +28,7 @@
     private FastCheckoutComponent.Delegate mDelegate;
     private BottomSheetController mBottomSheetController;
     private BottomSheetObserver mBottomSheetClosedObserver;
+    private BottomSheetObserver mBottomSheetDismissedObserver;
 
     void initialize(FastCheckoutComponent.Delegate delegate, PropertyModel model,
             BottomSheetController bottomSheetController) {
@@ -43,6 +45,15 @@
                 }
             }
         };
+
+        mBottomSheetDismissedObserver = new EmptyBottomSheetObserver() {
+            @Override
+            public void onSheetClosed(@BottomSheetController.StateChangeReason int reason) {
+                super.onSheetClosed(reason);
+                dismiss(reason);
+                mBottomSheetController.removeObserver(mBottomSheetDismissedObserver);
+            }
+        };
     }
 
     public void showOptions(
@@ -61,7 +72,29 @@
         }
     }
 
-    public void onDismissed(@StateChangeReason int reason) {
+    /**
+     * If set to true, requests to show the bottom sheet. Otherwise, requests to hide the sheet.
+     * @param isVisible A boolean describing whether to show or hide the sheet.
+     * @param content The bottom sheet content to show/hide.
+     * @return True if the request was successful, false otherwise.
+     */
+    public boolean setVisible(boolean isVisible, BottomSheetContent content) {
+        if (isVisible) {
+            mBottomSheetController.addObserver(mBottomSheetDismissedObserver);
+            if (!mBottomSheetController.requestShowContent(content, true)) {
+                mBottomSheetController.removeObserver(mBottomSheetDismissedObserver);
+                return false;
+            }
+        } else {
+            mBottomSheetController.hideContent(content, true);
+        }
+        return true;
+    }
+
+    /**
+     * Dismisses the current bottom sheet.
+     */
+    public void dismiss(@StateChangeReason int reason) {
         if (!mModel.get(FastCheckoutModel.VISIBLE)) return; // Dismiss only if not dismissed yet.
         // TODO(crbug.com/1334642): Record dismissal metrics.
         mModel.set(FastCheckoutModel.VISIBLE, false);
@@ -79,4 +112,12 @@
                 && view.getTag().equals(
                         AutofillAssistantPublicTags.AUTOFILL_ASSISTANT_BOTTOM_SHEET_CONTENT_TAG);
     }
+
+    /**
+     * Releases the resources used by FastCheckoutMediator.
+     */
+    @MainThread
+    private void destroy() {
+        mBottomSheetController.removeObserver(mBottomSheetDismissedObserver);
+    }
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutModel.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutModel.java
index 6c456a4d..b1aca1bc3 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutModel.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutModel.java
@@ -6,7 +6,6 @@
 
 import androidx.annotation.IntDef;
 
-import org.chromium.base.Callback;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -29,10 +28,6 @@
         int CREDIT_CARDS_SCREEN = 2;
     }
 
-    /** The handler for dismissing the bottom sheet. */
-    public static final PropertyModel.WritableObjectPropertyKey<Callback<Integer>> DISMISS_HANDLER =
-            new PropertyModel.WritableObjectPropertyKey<>();
-
     /** Property that indicates the bottom sheet visibility. */
     public static final PropertyModel.WritableBooleanPropertyKey VISIBLE =
             new PropertyModel.WritableBooleanPropertyKey();
@@ -44,15 +39,13 @@
     public static final PropertyModel.WritableIntPropertyKey CURRENT_SCREEN =
             new PropertyModel.WritableIntPropertyKey();
 
-    static PropertyModel createDefaultModel(Callback<Integer> dismissHandler) {
+    static PropertyModel createDefaultModel() {
         return new PropertyModel.Builder(ALL_KEYS)
                 .with(VISIBLE, false)
-                .with(DISMISS_HANDLER, dismissHandler)
                 .with(CURRENT_SCREEN, ScreenType.HOME_SCREEN)
                 .build();
     }
 
     /** All keys used for the fast checkout bottom sheet. */
-    static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {CURRENT_SCREEN, DISMISS_HANDLER, VISIBLE};
+    static final PropertyKey[] ALL_KEYS = new PropertyKey[] {CURRENT_SCREEN, VISIBLE};
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java
new file mode 100644
index 0000000..eb59092
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java
@@ -0,0 +1,106 @@
+// 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.
+
+package org.chromium.chrome.browser.ui.fast_checkout;
+
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+
+/**
+ * The {@link BottomSheetContent} for Fast Checkout.
+ */
+public class FastCheckoutSheetContent implements BottomSheetContent {
+    private final View mContentView;
+
+    /**
+     * Constructs a FastCheckoutSheetContent which creates, modifies, and shows the bottom sheet.
+     */
+    FastCheckoutSheetContent(View contentView) {
+        mContentView = contentView;
+    }
+
+    /**
+     * Sets the screen to show on the bottom sheet.
+     * @param screenType A {@link ScreenType} specifying the screen to show.
+     */
+    void updateCurrentScreen(int screenType) {
+        // TODO(crbug.com/1334642): Implement.
+    }
+
+    @Override
+    public View getContentView() {
+        return mContentView;
+    }
+
+    @Nullable
+    @Override
+    public View getToolbarView() {
+        // TODO(crbug.com/1334642): Implement.
+        return null;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        // TODO(crbug.com/1334642): Implement.
+        return 0;
+    }
+
+    @Override
+    public void destroy() {}
+
+    @Override
+    public int getPriority() {
+        return BottomSheetContent.ContentPriority.HIGH;
+    }
+
+    @Override
+    public boolean swipeToDismissEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean skipHalfStateOnScrollingDown() {
+        return false;
+    }
+
+    @Override
+    public int getPeekHeight() {
+        return BottomSheetContent.HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getFullHeightRatio() {
+        // TODO(crbug.com/1334642): Implement.
+        return HeightMode.DEFAULT;
+    }
+
+    @Override
+    public float getHalfHeightRatio() {
+        // TODO(crbug.com/1334642): Implement.
+        return BottomSheetContent.HeightMode.DEFAULT;
+    }
+
+    @Override
+    public int getSheetContentDescriptionStringId() {
+        return R.string.fast_checkout_content_description;
+    }
+
+    @Override
+    public int getSheetClosedAccessibilityStringId() {
+        return R.string.fast_checkout_sheet_closed;
+    }
+
+    @Override
+    public int getSheetHalfHeightAccessibilityStringId() {
+        return R.string.fast_checkout_sheet_half_height;
+    }
+
+    @Override
+    public int getSheetFullHeightAccessibilityStringId() {
+        return R.string.fast_checkout_sheet_full_height;
+    }
+}
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutView.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutView.java
deleted file mode 100644
index 022868b..0000000
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutView.java
+++ /dev/null
@@ -1,53 +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.
-
-package org.chromium.chrome.browser.ui.fast_checkout;
-
-import android.view.View;
-
-import org.chromium.base.Callback;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-
-/**
- * The {@link BottomSheetContent} for Fast Checkout. TODO(crbug.com/1334642): The view
- * should implement BottomSheetContent.
- */
-public class FastCheckoutView {
-    private final BottomSheetController mBottomSheetController;
-    private final View mContentView;
-
-    /**
-     * Constructs a FastCheckoutView which creates, modifies, and shows the bottom sheet.
-     */
-    FastCheckoutView(View contentView, BottomSheetController bottomSheetController) {
-        mBottomSheetController = bottomSheetController;
-        mContentView = contentView;
-    }
-
-    /**
-     * Sets a new listener that reacts to events like item selection or dismissal.
-     * @param dismissHandler A {@link Callback<Integer>}.
-     */
-    void setDismissHandler(Callback<Integer> dismissHandler) {
-        // TODO(crbug.com/1334642): Implement.
-    }
-
-    /**
-     * If set to true, requests to show the bottom sheet. Otherwise, requests to hide the sheet.
-     * @param isVisible A boolean describing whether to show or hide the sheet.
-     * @return True if the request was successful, false otherwise.
-     */
-    boolean setVisible(boolean isVisible) {
-        // TODO(crbug.com/1334642): Implement.
-        return false;
-    }
-
-    /**
-     * Sets the screen to show on the bottom sheet.
-     * @param screenType A {@link ScreenType} specifying the screen to show.
-     */
-    void updateCurrentScreen(int screenType) {
-        // TODO(crbug.com/1334642): Implement.
-    }
-}
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutViewBinder.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutViewBinder.java
deleted file mode 100644
index e00f9ede..0000000
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutViewBinder.java
+++ /dev/null
@@ -1,34 +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.
-
-package org.chromium.chrome.browser.ui.fast_checkout;
-
-import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutModel.DISMISS_HANDLER;
-import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutModel.VISIBLE;
-
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.ui.modelutil.PropertyKey;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * Provides functions that map {@link FastCheckoutModel} changes to the suitable
- * method in {@link FastCheckoutView}.
- */
-public class FastCheckoutViewBinder {
-    static void bindFastCheckoutView(
-            PropertyModel model, FastCheckoutView view, PropertyKey propertyKey) {
-        if (FastCheckoutModel.CURRENT_SCREEN == propertyKey) {
-            view.updateCurrentScreen(model.get(FastCheckoutModel.CURRENT_SCREEN));
-        } else if (FastCheckoutModel.DISMISS_HANDLER == propertyKey) {
-            view.setDismissHandler(model.get(DISMISS_HANDLER));
-        } else if (FastCheckoutModel.VISIBLE == propertyKey) {
-            // Dismiss the sheet if it can't be immediately shown.
-            boolean visibilityChangeSuccessful = view.setVisible(model.get(VISIBLE));
-            if (!visibilityChangeSuccessful && model.get(VISIBLE)) {
-                assert (model.get(DISMISS_HANDLER) != null);
-                model.get(DISMISS_HANDLER).onResult(BottomSheetController.StateChangeReason.NONE);
-            }
-        }
-    }
-}
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings.grd b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings.grd
new file mode 100644
index 0000000..a3e83af
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings.grd
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+
+<grit current_release="1" latest_public_release="0" output_all_resource_defines="false">
+  <outputs>
+    <output filename="values-af/android_fast_checkout_strings.xml" lang="af" type="android" />
+    <output filename="values-am/android_fast_checkout_strings.xml" lang="am" type="android" />
+    <output filename="values-ar/android_fast_checkout_strings.xml" lang="ar" type="android" />
+    <output filename="values-as/android_fast_checkout_strings.xml" lang="as" type="android" />
+    <output filename="values-az/android_fast_checkout_strings.xml" lang="az" type="android" />
+    <output filename="values-be/android_fast_checkout_strings.xml" lang="be" type="android" />
+    <output filename="values-bg/android_fast_checkout_strings.xml" lang="bg" type="android" />
+    <output filename="values-bn/android_fast_checkout_strings.xml" lang="bn" type="android" />
+    <output filename="values-bs/android_fast_checkout_strings.xml" lang="bs" type="android" />
+    <output filename="values-ca/android_fast_checkout_strings.xml" lang="ca" type="android" />
+    <output filename="values-cs/android_fast_checkout_strings.xml" lang="cs" type="android" />
+    <output filename="values-da/android_fast_checkout_strings.xml" lang="da" type="android" />
+    <output filename="values-de/android_fast_checkout_strings.xml" lang="de" type="android" />
+    <output filename="values-el/android_fast_checkout_strings.xml" lang="el" type="android" />
+    <output filename="values/android_fast_checkout_strings.xml" lang="en" type="android" />
+    <output filename="values-en-rGB/android_fast_checkout_strings.xml" lang="en-GB" type="android" />
+    <output filename="values-es/android_fast_checkout_strings.xml" lang="es" type="android" />
+    <output filename="values-es-rUS/android_fast_checkout_strings.xml" lang="es-419" type="android" />
+    <output filename="values-et/android_fast_checkout_strings.xml" lang="et" type="android" />
+    <output filename="values-eu/android_fast_checkout_strings.xml" lang="eu" type="android" />
+    <output filename="values-fa/android_fast_checkout_strings.xml" lang="fa" type="android" />
+    <output filename="values-fi/android_fast_checkout_strings.xml" lang="fi" type="android" />
+    <output filename="values-tl/android_fast_checkout_strings.xml" lang="fil" type="android" />
+    <output filename="values-fr/android_fast_checkout_strings.xml" lang="fr" type="android" />
+    <output filename="values-fr-rCA/android_fast_checkout_strings.xml" lang="fr-CA" type="android" />
+    <output filename="values-gl/android_fast_checkout_strings.xml" lang="gl" type="android" />
+    <output filename="values-gu/android_fast_checkout_strings.xml" lang="gu" type="android" />
+    <output filename="values-hi/android_fast_checkout_strings.xml" lang="hi" type="android" />
+    <output filename="values-hr/android_fast_checkout_strings.xml" lang="hr" type="android" />
+    <output filename="values-hu/android_fast_checkout_strings.xml" lang="hu" type="android" />
+    <output filename="values-hy/android_fast_checkout_strings.xml" lang="hy" type="android" />
+    <output filename="values-in/android_fast_checkout_strings.xml" lang="id" type="android" />
+    <output filename="values-is/android_fast_checkout_strings.xml" lang="is" type="android" />
+    <output filename="values-it/android_fast_checkout_strings.xml" lang="it" type="android" />
+    <output filename="values-iw/android_fast_checkout_strings.xml" lang="iw" type="android" />
+    <output filename="values-ja/android_fast_checkout_strings.xml" lang="ja" type="android" />
+    <output filename="values-ka/android_fast_checkout_strings.xml" lang="ka" type="android" />
+    <output filename="values-kk/android_fast_checkout_strings.xml" lang="kk" type="android" />
+    <output filename="values-km/android_fast_checkout_strings.xml" lang="km" type="android" />
+    <output filename="values-kn/android_fast_checkout_strings.xml" lang="kn" type="android" />
+    <output filename="values-ko/android_fast_checkout_strings.xml" lang="ko" type="android" />
+    <output filename="values-ky/android_fast_checkout_strings.xml" lang="ky" type="android" />
+    <output filename="values-lo/android_fast_checkout_strings.xml" lang="lo" type="android" />
+    <output filename="values-lt/android_fast_checkout_strings.xml" lang="lt" type="android" />
+    <output filename="values-lv/android_fast_checkout_strings.xml" lang="lv" type="android" />
+    <output filename="values-mk/android_fast_checkout_strings.xml" lang="mk" type="android" />
+    <output filename="values-ml/android_fast_checkout_strings.xml" lang="ml" type="android" />
+    <output filename="values-mn/android_fast_checkout_strings.xml" lang="mn" type="android" />
+    <output filename="values-mr/android_fast_checkout_strings.xml" lang="mr" type="android" />
+    <output filename="values-ms/android_fast_checkout_strings.xml" lang="ms" type="android" />
+    <output filename="values-my/android_fast_checkout_strings.xml" lang="my" type="android" />
+    <output filename="values-ne/android_fast_checkout_strings.xml" lang="ne" type="android" />
+    <output filename="values-nl/android_fast_checkout_strings.xml" lang="nl" type="android" />
+    <output filename="values-nb/android_fast_checkout_strings.xml" lang="no" type="android" />
+    <output filename="values-or/android_fast_checkout_strings.xml" lang="or" type="android" />
+    <output filename="values-pa/android_fast_checkout_strings.xml" lang="pa" type="android" />
+    <output filename="values-pl/android_fast_checkout_strings.xml" lang="pl" type="android" />
+    <output filename="values-pt-rBR/android_fast_checkout_strings.xml" lang="pt-BR" type="android" />
+    <output filename="values-pt-rPT/android_fast_checkout_strings.xml" lang="pt-PT" type="android" />
+    <output filename="values-ro/android_fast_checkout_strings.xml" lang="ro" type="android" />
+    <output filename="values-ru/android_fast_checkout_strings.xml" lang="ru" type="android" />
+    <output filename="values-si/android_fast_checkout_strings.xml" lang="si" type="android" />
+    <output filename="values-sk/android_fast_checkout_strings.xml" lang="sk" type="android" />
+    <output filename="values-sl/android_fast_checkout_strings.xml" lang="sl" type="android" />
+    <output filename="values-sq/android_fast_checkout_strings.xml" lang="sq" type="android" />
+    <output filename="values-sr/android_fast_checkout_strings.xml" lang="sr" type="android" />
+    <output filename="values-b+sr+Latn/android_fast_checkout_strings.xml" lang="sr-Latn" type="android" />
+    <output filename="values-sv/android_fast_checkout_strings.xml" lang="sv" type="android" />
+    <output filename="values-sw/android_fast_checkout_strings.xml" lang="sw" type="android" />
+    <output filename="values-ta/android_fast_checkout_strings.xml" lang="ta" type="android" />
+    <output filename="values-te/android_fast_checkout_strings.xml" lang="te" type="android" />
+    <output filename="values-th/android_fast_checkout_strings.xml" lang="th" type="android" />
+    <output filename="values-tr/android_fast_checkout_strings.xml" lang="tr" type="android" />
+    <output filename="values-uk/android_fast_checkout_strings.xml" lang="uk" type="android" />
+    <output filename="values-ur/android_fast_checkout_strings.xml" lang="ur" type="android" />
+    <output filename="values-uz/android_fast_checkout_strings.xml" lang="uz" type="android" />
+    <output filename="values-vi/android_fast_checkout_strings.xml" lang="vi" type="android" />
+    <output filename="values-zh-rCN/android_fast_checkout_strings.xml" lang="zh-CN" type="android" />
+    <output filename="values-zh-rHK/android_fast_checkout_strings.xml" lang="zh-HK" type="android" />
+    <output filename="values-zh-rTW/android_fast_checkout_strings.xml" lang="zh-TW" type="android" />
+    <output filename="values-zu/android_fast_checkout_strings.xml" lang="zu" type="android" />
+    <!-- Pseudolocales -->
+    <output filename="values-ar-rXB/android_fast_checkout_strings.xml" lang="ar-XB" type="android" />
+    <output filename="values-en-rXA/android_fast_checkout_strings.xml" lang="en-XA" type="android" />
+  </outputs>
+   <translations>
+    <file path="translations/android_fast_checkout_strings_af.xtb" lang="af"/>
+    <file path="translations/android_fast_checkout_strings_am.xtb" lang="am"/>
+    <file path="translations/android_fast_checkout_strings_ar.xtb" lang="ar"/>
+    <file path="translations/android_fast_checkout_strings_as.xtb" lang="as"/>
+    <file path="translations/android_fast_checkout_strings_az.xtb" lang="az"/>
+    <file path="translations/android_fast_checkout_strings_be.xtb" lang="be"/>
+    <file path="translations/android_fast_checkout_strings_bg.xtb" lang="bg"/>
+    <file path="translations/android_fast_checkout_strings_bn.xtb" lang="bn"/>
+    <file path="translations/android_fast_checkout_strings_bs.xtb" lang="bs"/>
+    <file path="translations/android_fast_checkout_strings_ca.xtb" lang="ca"/>
+    <file path="translations/android_fast_checkout_strings_cs.xtb" lang="cs"/>
+    <file path="translations/android_fast_checkout_strings_cy.xtb" lang="cy"/>
+    <file path="translations/android_fast_checkout_strings_da.xtb" lang="da"/>
+    <file path="translations/android_fast_checkout_strings_de.xtb" lang="de"/>
+    <file path="translations/android_fast_checkout_strings_el.xtb" lang="el"/>
+    <file path="translations/android_fast_checkout_strings_en-GB.xtb" lang="en-GB"/>
+    <file path="translations/android_fast_checkout_strings_es.xtb" lang="es"/>
+    <file path="translations/android_fast_checkout_strings_es-419.xtb" lang="es-419"/>
+    <file path="translations/android_fast_checkout_strings_et.xtb" lang="et"/>
+    <file path="translations/android_fast_checkout_strings_eu.xtb" lang="eu"/>
+    <file path="translations/android_fast_checkout_strings_fa.xtb" lang="fa"/>
+    <file path="translations/android_fast_checkout_strings_fi.xtb" lang="fi"/>
+    <file path="translations/android_fast_checkout_strings_fil.xtb" lang="fil"/>
+    <file path="translations/android_fast_checkout_strings_fr.xtb" lang="fr"/>
+    <file path="translations/android_fast_checkout_strings_fr-CA.xtb" lang="fr-CA"/>
+    <file path="translations/android_fast_checkout_strings_gl.xtb" lang="gl"/>
+    <file path="translations/android_fast_checkout_strings_gu.xtb" lang="gu"/>
+    <file path="translations/android_fast_checkout_strings_hi.xtb" lang="hi"/>
+    <file path="translations/android_fast_checkout_strings_hr.xtb" lang="hr"/>
+    <file path="translations/android_fast_checkout_strings_hu.xtb" lang="hu"/>
+    <file path="translations/android_fast_checkout_strings_hy.xtb" lang="hy"/>
+    <file path="translations/android_fast_checkout_strings_id.xtb" lang="id"/>
+    <file path="translations/android_fast_checkout_strings_is.xtb" lang="is"/>
+    <file path="translations/android_fast_checkout_strings_it.xtb" lang="it"/>
+    <file path="translations/android_fast_checkout_strings_iw.xtb" lang="iw"/>
+    <file path="translations/android_fast_checkout_strings_ja.xtb" lang="ja"/>
+    <file path="translations/android_fast_checkout_strings_ka.xtb" lang="ka"/>
+    <file path="translations/android_fast_checkout_strings_kk.xtb" lang="kk"/>
+    <file path="translations/android_fast_checkout_strings_km.xtb" lang="km"/>
+    <file path="translations/android_fast_checkout_strings_kn.xtb" lang="kn"/>
+    <file path="translations/android_fast_checkout_strings_ko.xtb" lang="ko"/>
+    <file path="translations/android_fast_checkout_strings_ky.xtb" lang="ky"/>
+    <file path="translations/android_fast_checkout_strings_lo.xtb" lang="lo"/>
+    <file path="translations/android_fast_checkout_strings_lt.xtb" lang="lt"/>
+    <file path="translations/android_fast_checkout_strings_lv.xtb" lang="lv"/>
+    <file path="translations/android_fast_checkout_strings_mk.xtb" lang="mk"/>
+    <file path="translations/android_fast_checkout_strings_ml.xtb" lang="ml"/>
+    <file path="translations/android_fast_checkout_strings_mn.xtb" lang="mn"/>
+    <file path="translations/android_fast_checkout_strings_mr.xtb" lang="mr"/>
+    <file path="translations/android_fast_checkout_strings_ms.xtb" lang="ms"/>
+    <file path="translations/android_fast_checkout_strings_my.xtb" lang="my"/>
+    <file path="translations/android_fast_checkout_strings_ne.xtb" lang="ne"/>
+    <file path="translations/android_fast_checkout_strings_nl.xtb" lang="nl"/>
+    <file path="translations/android_fast_checkout_strings_no.xtb" lang="no"/>
+    <file path="translations/android_fast_checkout_strings_or.xtb" lang="or"/>
+    <file path="translations/android_fast_checkout_strings_pa.xtb" lang="pa"/>
+    <file path="translations/android_fast_checkout_strings_pl.xtb" lang="pl"/>
+    <file path="translations/android_fast_checkout_strings_pt-BR.xtb" lang="pt-BR"/>
+    <file path="translations/android_fast_checkout_strings_pt-PT.xtb" lang="pt-PT"/>
+    <file path="translations/android_fast_checkout_strings_ro.xtb" lang="ro"/>
+    <file path="translations/android_fast_checkout_strings_ru.xtb" lang="ru"/>
+    <file path="translations/android_fast_checkout_strings_si.xtb" lang="si"/>
+    <file path="translations/android_fast_checkout_strings_sk.xtb" lang="sk"/>
+    <file path="translations/android_fast_checkout_strings_sl.xtb" lang="sl"/>
+    <file path="translations/android_fast_checkout_strings_sq.xtb" lang="sq"/>
+    <file path="translations/android_fast_checkout_strings_sr.xtb" lang="sr"/>
+    <file path="translations/android_fast_checkout_strings_sr-Latn.xtb" lang="sr-Latn"/>
+    <file path="translations/android_fast_checkout_strings_sv.xtb" lang="sv"/>
+    <file path="translations/android_fast_checkout_strings_sw.xtb" lang="sw"/>
+    <file path="translations/android_fast_checkout_strings_ta.xtb" lang="ta"/>
+    <file path="translations/android_fast_checkout_strings_te.xtb" lang="te"/>
+    <file path="translations/android_fast_checkout_strings_th.xtb" lang="th"/>
+    <file path="translations/android_fast_checkout_strings_tr.xtb" lang="tr"/>
+    <file path="translations/android_fast_checkout_strings_uk.xtb" lang="uk"/>
+    <file path="translations/android_fast_checkout_strings_ur.xtb" lang="ur"/>
+    <file path="translations/android_fast_checkout_strings_uz.xtb" lang="uz"/>
+    <file path="translations/android_fast_checkout_strings_vi.xtb" lang="vi"/>
+    <file path="translations/android_fast_checkout_strings_zh-CN.xtb" lang="zh-CN"/>
+    <file path="translations/android_fast_checkout_strings_zh-HK.xtb" lang="zh-HK"/>
+    <file path="translations/android_fast_checkout_strings_zh-TW.xtb" lang="zh-TW"/>
+    <file path="translations/android_fast_checkout_strings_zu.xtb" lang="zu"/>
+  </translations>
+  <release allow_pseudo="false" seq="1">
+    <messages fallback_to_english="true">
+      <!-- Fast Checkout -->
+      <message name="IDS_FAST_CHECKOUT_CONTENT_DESCRIPTION" desc="Accessibility string read when the Fast Checkout bottom sheet is opened. It describes the bottom sheet where a user can pick an address and payment option to fill during checkout flows">
+        List of addresses and payment options to be filled during checkout flows.
+      </message>
+      <message name="IDS_FAST_CHECKOUT_SHEET_HALF_HEIGHT" desc="Accessibility string read when the Fast Checkout bottom sheet showing a list of the user stored addresses and payment options is opened at half height. The sheet will occupy the bottom half the screen.">
+        List of addresses and payment options to be filled during checkout flows opened at half height.
+      </message>
+      <message name="IDS_FAST_CHECKOUT_SHEET_FULL_HEIGHT" desc="Accessibility string read when the Fast Checkout bottom sheet showing a list of the user stored addresses and payment options is opened at full height. The sheet will occupy the entire screen.">
+        List of addresses and payment options to be filled during checkout flows opened at full height.
+      </message>
+      <message name="IDS_FAST_CHECKOUT_SHEET_CLOSED" desc="Accessibility string read when the Fast Checkout bottom sheet showing a list of the user stored addresses and payment options is closed.">
+        List of addresses and payment options to be filled during checkout flows is closed.
+      </message>
+    </messages>
+  </release>
+</grit>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_CONTENT_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_CONTENT_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_CONTENT_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_CLOSED.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_CLOSED.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_CLOSED.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_FULL_HEIGHT.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_FULL_HEIGHT.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_FULL_HEIGHT.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_HALF_HEIGHT.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_HALF_HEIGHT.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_HALF_HEIGHT.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_SUBTITLE.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_SUBTITLE.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_TITLE.png.sha1 b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_TITLE.png.sha1
new file mode 100644
index 0000000..4b78845
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings_grd/IDS_FAST_CHECKOUT_SHEET_TITLE.png.sha1
@@ -0,0 +1 @@
+7536bf3c61acf2dbc814ac68882a5ec5d4f783f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_af.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_af.xtb
new file mode 100644
index 0000000..b369e833
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_af.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="af">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_am.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_am.xtb
new file mode 100644
index 0000000..92406ec2
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_am.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="am">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ar.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ar.xtb
new file mode 100644
index 0000000..198ea62
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ar.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ar">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_as.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_as.xtb
new file mode 100644
index 0000000..9aebd78
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_as.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="as">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_az.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_az.xtb
new file mode 100644
index 0000000..db47f35
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_az.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="az">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_be.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_be.xtb
new file mode 100644
index 0000000..ccab734
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_be.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="be">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bg.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bg.xtb
new file mode 100644
index 0000000..6681995
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bg.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bg">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bn.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bn.xtb
new file mode 100644
index 0000000..eca68d46
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bs.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bs.xtb
new file mode 100644
index 0000000..07aea3c6
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_bs.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bs">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ca.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ca.xtb
new file mode 100644
index 0000000..71cdd772
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ca.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ca">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cs.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cs.xtb
new file mode 100644
index 0000000..dc153a85
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cs.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="cs">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cy.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cy.xtb
new file mode 100644
index 0000000..eae5ddc
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_cy.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="cy">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_da.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_da.xtb
new file mode 100644
index 0000000..1256832
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_da.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="da">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_de.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_de.xtb
new file mode 100644
index 0000000..91de7f51
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_de.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_el.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_el.xtb
new file mode 100644
index 0000000..6e5e7d8
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_el.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="el">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_en-GB.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_en-GB.xtb
new file mode 100644
index 0000000..12c3fa00
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_en-GB.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="en-GB">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es-419.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es-419.xtb
new file mode 100644
index 0000000..b652ed0
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es-419.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es-419">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es.xtb
new file mode 100644
index 0000000..4d4f400
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_es.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_et.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_et.xtb
new file mode 100644
index 0000000..ab777bc5
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_et.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="et">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_eu.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_eu.xtb
new file mode 100644
index 0000000..6910975
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_eu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="eu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fa.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fa.xtb
new file mode 100644
index 0000000..4cff15d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fa.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fa">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fi.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fi.xtb
new file mode 100644
index 0000000..60ba9aa
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fi">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fil.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fil.xtb
new file mode 100644
index 0000000..8f6a880
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fil.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fil">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr-CA.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr-CA.xtb
new file mode 100644
index 0000000..e2557d0
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr-CA.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr-CA">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr.xtb
new file mode 100644
index 0000000..bf48975a
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_fr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gl.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gl.xtb
new file mode 100644
index 0000000..e04c577
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="gl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gu.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gu.xtb
new file mode 100644
index 0000000..7969d06
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_gu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="gu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hi.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hi.xtb
new file mode 100644
index 0000000..a6ddd5d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hi">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hr.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hr.xtb
new file mode 100644
index 0000000..26f99d0
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hr">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hu.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hu.xtb
new file mode 100644
index 0000000..bdc02ee
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hy.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hy.xtb
new file mode 100644
index 0000000..c9b28dd
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_hy.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hy">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_id.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_id.xtb
new file mode 100644
index 0000000..5f2882d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_id.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="id">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_is.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_is.xtb
new file mode 100644
index 0000000..6d0302a
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_is.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="is">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_it.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_it.xtb
new file mode 100644
index 0000000..e7df702
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_it.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="it">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_iw.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_iw.xtb
new file mode 100644
index 0000000..a29d4ad
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_iw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="iw">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ja.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ja.xtb
new file mode 100644
index 0000000..d8a3543
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ja.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ja">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ka.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ka.xtb
new file mode 100644
index 0000000..b5877bc
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ka.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ka">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kk.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kk.xtb
new file mode 100644
index 0000000..94816de
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="kk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_km.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_km.xtb
new file mode 100644
index 0000000..6a62979
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_km.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="km">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kn.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kn.xtb
new file mode 100644
index 0000000..4ecb12b
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_kn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="kn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ko.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ko.xtb
new file mode 100644
index 0000000..558b05b
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ko.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ko">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ky.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ky.xtb
new file mode 100644
index 0000000..71a0883
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ky.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ky">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lo.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lo.xtb
new file mode 100644
index 0000000..eb6216c
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lo.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lo">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lt.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lt.xtb
new file mode 100644
index 0000000..f20c0fa2
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lt.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lt">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lv.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lv.xtb
new file mode 100644
index 0000000..6f3afbc
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_lv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lv">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mk.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mk.xtb
new file mode 100644
index 0000000..02ed730
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ml.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ml.xtb
new file mode 100644
index 0000000..e01197e
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ml.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ml">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mn.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mn.xtb
new file mode 100644
index 0000000..0713767
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mr.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mr.xtb
new file mode 100644
index 0000000..b137924e
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_mr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ms.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ms.xtb
new file mode 100644
index 0000000..518685dd
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ms.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ms">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_my.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_my.xtb
new file mode 100644
index 0000000..60d303e4
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_my.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="my">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ne.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ne.xtb
new file mode 100644
index 0000000..66f9f15
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ne.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ne">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_nl.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_nl.xtb
new file mode 100644
index 0000000..05ab957
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_nl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="nl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_no.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_no.xtb
new file mode 100644
index 0000000..ede4de30
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_no.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="no">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_or.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_or.xtb
new file mode 100644
index 0000000..27b3d81c
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_or.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="or">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pa.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pa.xtb
new file mode 100644
index 0000000..5c7ac50
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pa.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pa">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pl.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pl.xtb
new file mode 100644
index 0000000..4519e3d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pl">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-BR.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-BR.xtb
new file mode 100644
index 0000000..de39dfa
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-BR.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-BR">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-PT.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-PT.xtb
new file mode 100644
index 0000000..0b98ee77
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_pt-PT.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-PT">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ro.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ro.xtb
new file mode 100644
index 0000000..7129eb4
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ro.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ro">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ru.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ru.xtb
new file mode 100644
index 0000000..6dfaa442
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ru.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ru">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_si.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_si.xtb
new file mode 100644
index 0000000..cc50fd5
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_si.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="si">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sk.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sk.xtb
new file mode 100644
index 0000000..202e515a
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sl.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sl.xtb
new file mode 100644
index 0000000..31b5a1a
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sq.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sq.xtb
new file mode 100644
index 0000000..a954869
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sq.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sq">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr-Latn.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr-Latn.xtb
new file mode 100644
index 0000000..c205597
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr-Latn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr-Latn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr.xtb
new file mode 100644
index 0000000..984d7192
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sv.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sv.xtb
new file mode 100644
index 0000000..9a787b8
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sv">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sw.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sw.xtb
new file mode 100644
index 0000000..9aa61cb
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_sw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sw">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ta.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ta.xtb
new file mode 100644
index 0000000..ef90687
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ta.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ta">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_te.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_te.xtb
new file mode 100644
index 0000000..48c714b
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_te.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="te">
+</translationbundle>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_th.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_th.xtb
new file mode 100644
index 0000000..dbe6a601
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_th.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="th">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_tr.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_tr.xtb
new file mode 100644
index 0000000..d99480c
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_tr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="tr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uk.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uk.xtb
new file mode 100644
index 0000000..6e80099d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="uk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ur.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ur.xtb
new file mode 100644
index 0000000..624b043
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_ur.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ur">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uz.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uz.xtb
new file mode 100644
index 0000000..63653888
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_uz.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="uz">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_vi.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_vi.xtb
new file mode 100644
index 0000000..8a42ab1
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_vi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="vi">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-CN.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-CN.xtb
new file mode 100644
index 0000000..c7d76e8
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-CN.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-HK.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-HK.xtb
new file mode 100644
index 0000000..b78b9774
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-HK.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-HK">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-TW.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-TW.xtb
new file mode 100644
index 0000000..3e0c306
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zh-TW.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-TW">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zu.xtb b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zu.xtb
new file mode 100644
index 0000000..be432e9a
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/strings/translations/android_fast_checkout_strings_zu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index c495201..f0572a05 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -40,7 +40,6 @@
 import org.chromium.components.browser_ui.site_settings.SiteSettingsUtil;
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
-import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.page_info.PageInfoController;
 import org.chromium.components.page_info.PageInfoDiscoverabilityMetrics;
 import org.chromium.components.page_info.PageInfoDiscoverabilityMetrics.DiscoverabilityAction;
@@ -328,15 +327,14 @@
         // This logic doesn't apply to tablets.
         if (mIsTablet) return;
 
-        boolean shouldShowLogo = mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                mLocationBarDataProvider.isIncognito());
+        boolean shouldShowLogo = !mLocationBarDataProvider.isIncognito();
         setShowIconsWhenUrlFocused(shouldShowLogo);
         if (!shouldShowLogo) return;
 
         if (mLocationBarDataProvider.isInOverviewAndShowingOmnibox()) {
             setStatusIconShown(true);
         } else if (mProfileSupplier.get() != null
-                && UrlUtilities.isCanonicalizedNTPUrl(mLocationBarDataProvider.getCurrentUrl())) {
+                && mLocationBarDataProvider.getNewTabPageDelegate().isCurrentlyVisible()) {
             setStatusIconShown(shouldShowLogo && (mUrlHasFocus || mUrlFocusPercent > 0));
         } else {
             setStatusIconShown(true);
@@ -356,7 +354,7 @@
 
         // Only fade the animation on the new tab page.
         if (mProfileSupplier.get() != null
-                && UrlUtilities.isCanonicalizedNTPUrl(mLocationBarDataProvider.getCurrentUrl())) {
+                && mLocationBarDataProvider.getNewTabPageDelegate().isCurrentlyVisible()) {
             float focusAnimationProgress = percent;
             if (!mUrlHasFocus) {
                 focusAnimationProgress = MathUtils.clamp(
@@ -519,14 +517,13 @@
     boolean maybeUpdateStatusIconForSearchEngineIcon() {
         // Show the logo unfocused if we're on the NTP.
         if (shouldDisplaySearchEngineIcon()) {
-            getStatusIconResourceForSearchEngineIcon(
-                    mLocationBarDataProvider.isIncognito(), (statusIconRes) -> {
-                        // Check again in case the conditions have changed since this callback was
-                        // created.
-                        if (shouldDisplaySearchEngineIcon()) {
-                            mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes);
-                        }
-                    });
+            getStatusIconResourceForSearchEngineIcon((statusIconRes) -> {
+                // Check again in case the conditions have changed since this callback was
+                // created.
+                if (shouldDisplaySearchEngineIcon()) {
+                    mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes);
+                }
+            });
             return true;
         } else {
             mShouldCancelCustomFavicon = true;
@@ -539,28 +536,29 @@
      * independent from alpha/visibility.
      */
     boolean shouldDisplaySearchEngineIcon() {
-        boolean showIconWhenFocused = mUrlHasFocus && mShowStatusIconWhenUrlFocused;
-        boolean showIconOnNTP = mProfileSupplier.get() != null
-                && UrlUtilities.isCanonicalizedNTPUrl(mLocationBarDataProvider.getCurrentUrl())
-                && !mLocationBarDataProvider.isLoading() && !mIsTablet
-                && (mUrlHasFocus || mUrlFocusPercent > 0);
+        if (mLocationBarDataProvider.isIncognito()) {
+            return false;
+        }
 
-        return mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                       mLocationBarDataProvider.isIncognito())
-                && (showIconWhenFocused || showIconOnNTP);
+        if (mUrlHasFocus && mShowStatusIconWhenUrlFocused) {
+            return true;
+        }
+
+        return (mUrlHasFocus || mUrlFocusPercent > 0)
+                && mLocationBarDataProvider.getNewTabPageDelegate().isCurrentlyVisible()
+                && mProfileSupplier.get() != null;
     }
 
     /**
      * Set the security icon resource for the search engine icon and invoke the callback to inform
      * the caller which resource has been set.
      *
-     * @param isIncognito True if the user is incognito.
      * @param resourceCallback Called when the final value is set for the security icon resource.
      *                         Meant to give the caller a chance to set the tint for the given
      *                         resource.
      */
     private void getStatusIconResourceForSearchEngineIcon(
-            boolean isIncognito, Callback<StatusIconResource> resourceCallback) {
+            Callback<StatusIconResource> resourceCallback) {
         mShouldCancelCustomFavicon = false;
         // If the current url text is a valid url, then swap the dse icon for a globe.
         if (!mUrlBarTextIsSearch) {
@@ -574,9 +572,7 @@
 
     /** Return the resource id for the accessibility description or 0 if none apply. */
     private int getAccessibilityDescriptionRes() {
-        if (mUrlHasFocus
-                && mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                        mLocationBarDataProvider.isIncognito())) {
+        if (mUrlHasFocus && !mLocationBarDataProvider.isIncognito()) {
             return 0;
         }
         return (mSecurityIconRes != 0) ? mSecurityIconDescriptionRes : 0;
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
index 12ed55b5..a2c3fe6 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
@@ -52,7 +52,6 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
-import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.permissions.PermissionDialogController;
 import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
@@ -108,6 +107,7 @@
         };
         doReturn(false).when(mLocationBarDataProvider).isInOverviewAndShowingOmnibox();
         doReturn(false).when(mLocationBarDataProvider).isIncognito();
+        doReturn(mNewTabPageDelegate).when(mLocationBarDataProvider).getNewTabPageDelegate();
         doAnswer(logoAnswer)
                 .when(mSearchEngineLogoUtils)
                 .getSearchEngineLogo(
@@ -139,9 +139,6 @@
     @Test
     @SmallTest
     public void searchEngineLogo_isGoogleLogo() {
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
-
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         Assert.assertEquals(R.drawable.ic_logo_googleg_20dp,
@@ -151,9 +148,7 @@
     @Test
     @SmallTest
     public void searchEngineLogo_isGoogleLogo_hideAfterUnfocusFinished() {
-        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
+        doReturn(true).when(mNewTabPageDelegate).isCurrentlyVisible();
 
         mMediator.setUrlHasFocus(true);
         mMediator.setUrlHasFocus(false);
@@ -163,9 +158,6 @@
     @Test
     @SmallTest
     public void searchEngineLogo_isGoogleLogo_noHideIconAfterUnfocusedWhenScrolled() {
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
-
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
         mMediator.setUrlFocusChangePercent(1f);
@@ -177,9 +169,7 @@
     @Test
     @SmallTest
     public void searchEngineLogo_isGoogleLogoOnNtp() {
-        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ false, /* loupeEverywhere= */ false);
+        doReturn(true).when(mNewTabPageDelegate).isCurrentlyVisible();
 
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
@@ -191,9 +181,7 @@
     @SmallTest
     public void searchEngineLogo_isGoogleLogoOnNtpTablet() {
         setupStatusMediator(/* isTablet= */ true);
-        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ false, /* loupeEverywhere= */ false);
+        doReturn(true).when(mNewTabPageDelegate).isCurrentlyVisible();
 
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
@@ -205,12 +193,7 @@
     @SmallTest
     public void searchEngineLogo_isGoogleLogo_whenScrolled() {
         doReturn(false).when(mLocationBarDataProvider).isLoading();
-        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
-        doReturn(mNewTabPageDelegate).when(mLocationBarDataProvider).getNewTabPageDelegate();
         doReturn(true).when(mNewTabPageDelegate).isCurrentlyVisible();
-        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
@@ -221,13 +204,10 @@
 
     @Test
     @SmallTest
-
     public void searchEngineLogo_onTextChanged_globeReplacesIconWhenTextIsSite() {
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         doReturn(TEST_SEARCH_URL).when(mUrlBarEditingTextStateProvider).getTextWithAutocomplete();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.updateLocationBarIconForDefaultMatchCategory(false);
         Assert.assertEquals(R.drawable.ic_globe_24dp,
@@ -236,13 +216,10 @@
 
     @Test
     @SmallTest
-
     public void searchEngineLogo_onTextChanged_globeReplacesIconWhenAutocompleteSiteContainsText() {
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         doReturn(TEST_SEARCH_URL).when(mUrlBarEditingTextStateProvider).getTextWithAutocomplete();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.updateLocationBarIconForDefaultMatchCategory(false);
         Assert.assertEquals(R.drawable.ic_globe_24dp,
@@ -255,8 +232,6 @@
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         doReturn(TEST_SEARCH_URL).when(mUrlBarEditingTextStateProvider).getTextWithAutocomplete();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.updateLocationBarIconForDefaultMatchCategory(true);
         Assert.assertNotEquals(R.drawable.ic_globe_24dp,
@@ -265,14 +240,11 @@
 
     @Test
     @SmallTest
-
     public void searchEngineLogo_onTextChanged_noGlobeReplacementWhenUrlBarTextIsEmpty() {
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         // Setup a valid url to prevent the default "" from matching the url.
         doReturn(TEST_SEARCH_URL).when(mUrlBarEditingTextStateProvider).getTextWithAutocomplete();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.updateLocationBarIconForDefaultMatchCategory(false);
         mMediator.updateLocationBarIconForDefaultMatchCategory(true);
@@ -293,8 +265,6 @@
     @SmallTest
     public void searchEngineLogo_incognitoNoIcon() {
         doReturn(true).when(mLocationBarDataProvider).isIncognito();
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
@@ -309,8 +279,6 @@
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(true);
         mMediator.updateSecurityIcon(0, 0, 0);
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         Assert.assertTrue(mMediator.maybeUpdateStatusIconForSearchEngineIcon());
         Assert.assertEquals(R.drawable.ic_logo_googleg_20dp,
@@ -319,13 +287,10 @@
 
     @Test
     @SmallTest
-
     public void searchEngineLogo_maybeUpdateStatusIconForSearchEngineIconNoChanges() {
         mMediator.setUrlHasFocus(true);
         mMediator.setShowIconsWhenUrlFocused(false);
         mMediator.updateSecurityIcon(0, 0, 0);
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
 
         Assert.assertFalse(mMediator.maybeUpdateStatusIconForSearchEngineIcon());
     }
@@ -353,8 +318,6 @@
     public void testIncognitoStateChange_goingToIncognito() {
         mMediator.setShowIconsWhenUrlFocused(true);
 
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
         doReturn(true).when(mLocationBarDataProvider).isIncognito();
         mMediator.onIncognitoStateChanged();
         Assert.assertEquals(null, mModel.get(StatusProperties.STATUS_ICON_RESOURCE));
@@ -366,8 +329,6 @@
     public void testIncognitoStateChange_backFromIncognito() {
         mMediator.setShowIconsWhenUrlFocused(true);
 
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
         doReturn(true).when(mLocationBarDataProvider).isIncognito();
         mMediator.onIncognitoStateChanged();
         doReturn(false).when(mLocationBarDataProvider).isIncognito();
@@ -412,9 +373,6 @@
     @Test
     @SmallTest
     public void testTemplateUrlServiceChanged() {
-        setupSearchEngineLogoForTesting(
-                /* showLogo= */ true, /* isGoogle= */ true, /* loupeEverywhere= */ false);
-
         mMediator.setShowIconsWhenUrlFocused(true);
         mMediator.setUrlHasFocus(true);
 
@@ -513,17 +471,6 @@
     }
 
     /**
-     * @param showLogo Whether the search engine logo should be shown.
-     * @param isGoogle Whether the search engine is Google.
-     * @param loupeEverywhere Whether to show the loupe everywhere.
-     */
-    private void setupSearchEngineLogoForTesting(
-            boolean showLogo, boolean isGoogle, boolean loupeEverywhere) {
-        doReturn(showLogo).when(mSearchEngineLogoUtils).shouldShowSearchEngineLogo(false);
-        doReturn(false).when(mSearchEngineLogoUtils).shouldShowSearchEngineLogo(true);
-    }
-
-    /**
      * @param currentUrl Url of current page.
      * @param isIncognito Whether the current page is in an incognito mode.
      */
diff --git a/chrome/browser/ui/android/overlay/overlay_window_android.cc b/chrome/browser/ui/android/overlay/overlay_window_android.cc
index 8d580cb..534059d73a 100644
--- a/chrome/browser/ui/android/overlay/overlay_window_android.cc
+++ b/chrome/browser/ui/android/overlay/overlay_window_android.cc
@@ -36,6 +36,7 @@
       compositor_view_(nullptr),
       surface_layer_(cc::SurfaceLayer::Create()),
       bounds_(gfx::Rect(0, 0)),
+      update_action_timer_(std::make_unique<base::OneShotTimer>()),
       controller_(controller) {
   surface_layer_->SetIsDrawable(true);
   surface_layer_->SetStretchContentToFillBounds(true);
@@ -93,7 +94,13 @@
 
   Java_PictureInPictureActivity_setPlaybackState(env, java_ref_.get(env),
                                                  playback_state_);
-  MaybeNotifyVisibleActionsChanged();
+  Java_PictureInPictureActivity_setMicrophoneMuted(env, java_ref_.get(env),
+                                                   microphone_muted_);
+  Java_PictureInPictureActivity_setCameraState(env, java_ref_.get(env),
+                                               camera_on_);
+
+  if (!update_action_timer_->IsRunning())
+    MaybeNotifyVisibleActionsChanged();
 
   if (video_size_.IsEmpty())
     return;
@@ -124,7 +131,11 @@
     window_android_ = nullptr;
   }
 
-  controller_->OnWindowDestroyed(/*should_pause_video=*/true);
+  // Only pause the video when play/pause button is visible.
+  controller_->OnWindowDestroyed(
+      /*should_pause_video=*/visible_actions_.find(
+          static_cast<int>(media_session::mojom::MediaSessionAction::kPlay)) !=
+      visible_actions_.end());
 }
 
 void OverlayWindowAndroid::TogglePlayPause(JNIEnv* env) {
@@ -140,6 +151,18 @@
   controller_->PreviousTrack();
 }
 
+void OverlayWindowAndroid::ToggleMicrophone(JNIEnv* env) {
+  controller_->ToggleMicrophone();
+}
+
+void OverlayWindowAndroid::ToggleCamera(JNIEnv* env) {
+  controller_->ToggleCamera();
+}
+
+void OverlayWindowAndroid::HangUp(JNIEnv* env) {
+  controller_->HangUp();
+}
+
 void OverlayWindowAndroid::CompositorViewCreated(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& compositor_view) {
@@ -229,30 +252,61 @@
                                                  playback_state);
 }
 
-void OverlayWindowAndroid::SetPlayPauseButtonVisibility(bool is_visible) {
-  if (!MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kPlay,
-                                is_visible)) {
+void OverlayWindowAndroid::SetMicrophoneMuted(bool muted) {
+  if (microphone_muted_ == muted)
     return;
-  }
 
-  MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kPause,
+  microphone_muted_ = muted;
+  if (java_ref_.is_uninitialized())
+    return;
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_PictureInPictureActivity_setMicrophoneMuted(env, java_ref_.get(env),
+                                                   microphone_muted_);
+}
+
+void OverlayWindowAndroid::SetCameraState(bool turned_on) {
+  if (camera_on_ == turned_on)
+    return;
+
+  camera_on_ = turned_on;
+  if (java_ref_.is_uninitialized())
+    return;
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_PictureInPictureActivity_setCameraState(env, java_ref_.get(env),
+                                               camera_on_);
+}
+
+void OverlayWindowAndroid::SetPlayPauseButtonVisibility(bool is_visible) {
+  MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kPlay,
                            is_visible);
-  MaybeNotifyVisibleActionsChanged();
 }
 
 void OverlayWindowAndroid::SetNextTrackButtonVisibility(bool is_visible) {
-  if (MaybeUpdateVisibleAction(
-          media_session::mojom::MediaSessionAction::kNextTrack, is_visible)) {
-    MaybeNotifyVisibleActionsChanged();
-  }
+  MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kNextTrack,
+                           is_visible);
 }
 
 void OverlayWindowAndroid::SetPreviousTrackButtonVisibility(bool is_visible) {
-  if (MaybeUpdateVisibleAction(
-          media_session::mojom::MediaSessionAction::kPreviousTrack,
-          is_visible)) {
-    MaybeNotifyVisibleActionsChanged();
-  }
+  MaybeUpdateVisibleAction(
+      media_session::mojom::MediaSessionAction::kPreviousTrack, is_visible);
+}
+
+void OverlayWindowAndroid::SetToggleMicrophoneButtonVisibility(
+    bool is_visible) {
+  MaybeUpdateVisibleAction(
+      media_session::mojom::MediaSessionAction::kToggleMicrophone, is_visible);
+}
+
+void OverlayWindowAndroid::SetToggleCameraButtonVisibility(bool is_visible) {
+  MaybeUpdateVisibleAction(
+      media_session::mojom::MediaSessionAction::kToggleCamera, is_visible);
+}
+
+void OverlayWindowAndroid::SetHangUpButtonVisibility(bool is_visible) {
+  MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kHangUp,
+                           is_visible);
 }
 
 void OverlayWindowAndroid::SetSurfaceId(const viz::SurfaceId& surface_id) {
@@ -289,13 +343,13 @@
           std::vector<int>(visible_actions_.begin(), visible_actions_.end())));
 }
 
-bool OverlayWindowAndroid::MaybeUpdateVisibleAction(
+void OverlayWindowAndroid::MaybeUpdateVisibleAction(
     const media_session::mojom::MediaSessionAction& action,
     bool is_visible) {
   int action_code = static_cast<int>(action);
   if ((visible_actions_.find(action_code) != visible_actions_.end()) ==
       is_visible) {
-    return false;
+    return;
   }
 
   if (is_visible)
@@ -303,5 +357,10 @@
   else
     visible_actions_.erase(action_code);
 
-  return true;
+  if (!update_action_timer_->IsRunning()) {
+    update_action_timer_->Start(
+        FROM_HERE, base::Seconds(1),
+        base::BindOnce(&OverlayWindowAndroid::MaybeNotifyVisibleActionsChanged,
+                       base::Unretained(this)));
+  }
 }
diff --git a/chrome/browser/ui/android/overlay/overlay_window_android.h b/chrome/browser/ui/android/overlay/overlay_window_android.h
index b655bad..b1daeb1 100644
--- a/chrome/browser/ui/android/overlay/overlay_window_android.h
+++ b/chrome/browser/ui/android/overlay/overlay_window_android.h
@@ -8,6 +8,7 @@
 #include "base/android/jni_weak_ref.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/raw_ptr.h"
+#include "base/timer/timer.h"
 #include "content/public/browser/overlay_window.h"
 #include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 #include "ui/android/window_android.h"
@@ -39,6 +40,9 @@
   void TogglePlayPause(JNIEnv* env);
   void NextTrack(JNIEnv* env);
   void PreviousTrack(JNIEnv* env);
+  void ToggleMicrophone(JNIEnv* env);
+  void ToggleCamera(JNIEnv* env);
+  void HangUp(JNIEnv* env);
   void CompositorViewCreated(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& compositor_view);
@@ -70,12 +74,11 @@
   void SetSkipAdButtonVisibility(bool is_visible) override {}
   void SetNextTrackButtonVisibility(bool is_visible) override;
   void SetPreviousTrackButtonVisibility(bool is_visible) override;
-  // TODO(crbug.com/1331269): Implement video conferencing actions.
-  void SetMicrophoneMuted(bool muted) override {}
-  void SetCameraState(bool turned_on) override {}
-  void SetToggleMicrophoneButtonVisibility(bool is_visible) override {}
-  void SetToggleCameraButtonVisibility(bool is_visible) override {}
-  void SetHangUpButtonVisibility(bool is_visible) override {}
+  void SetMicrophoneMuted(bool muted) override;
+  void SetCameraState(bool turned_on) override;
+  void SetToggleMicrophoneButtonVisibility(bool is_visible) override;
+  void SetToggleCameraButtonVisibility(bool is_visible) override;
+  void SetHangUpButtonVisibility(bool is_visible) override;
   void SetSurfaceId(const viz::SurfaceId& surface_id) override;
   cc::Layer* GetLayerForTesting() override;
 
@@ -84,7 +87,7 @@
   void MaybeNotifyVisibleActionsChanged();
 
   // Maybe update visible actions. Returns true if update happened.
-  bool MaybeUpdateVisibleAction(
+  void MaybeUpdateVisibleAction(
       const media_session::mojom::MediaSessionAction& action,
       bool is_visible);
   void CloseInternal();
@@ -100,6 +103,11 @@
   PlaybackState playback_state_ = PlaybackState::kEndOfVideo;
   std::unordered_set<int> visible_actions_;
 
+  bool microphone_muted_ = false;
+  bool camera_on_ = false;
+
+  std::unique_ptr<base::OneShotTimer> update_action_timer_;
+
   raw_ptr<content::VideoPictureInPictureWindowController> controller_;
 };
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 334ed75..60ba4b6 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -204,7 +204,7 @@
      */
     public void initializeWithNative() {
         mOptimizationsEnabled =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.LOCATION_BAR_MODEL_OPTIMIZATIONS);
+                ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_SCROLL_OPTIMIZATIONS);
         mLastUsedNonOTRProfile = Profile.getLastUsedRegularProfile();
         if (mOptimizationsEnabled) {
             mSpannableDisplayTextCache = new LruCache<>(LRU_CACHE_SIZE);
diff --git a/chrome/browser/ui/ash/assistant/device_actions.cc b/chrome/browser/ui/ash/assistant/device_actions.cc
index 582e785..963a8a3 100644
--- a/chrome/browser/ui/ash/assistant/device_actions.cc
+++ b/chrome/browser/ui/ash/assistant/device_actions.cc
@@ -31,9 +31,9 @@
 #include "components/user_manager/user_manager.h"
 #include "ui/display/types/display_constants.h"
 
+using ::ash::NetworkHandler;
+using ::ash::NetworkStateHandler;
 using ::ash::NetworkTypePattern;
-using chromeos::NetworkHandler;
-using chromeos::NetworkStateHandler;
 using chromeos::assistant::AndroidAppInfo;
 using chromeos::assistant::AppStatus;
 
diff --git a/chrome/browser/ui/ash/network/enrollment_dialog_view.cc b/chrome/browser/ui/ash/network/enrollment_dialog_view.cc
index 76bb5c3..5759048 100644
--- a/chrome/browser/ui/ash/network/enrollment_dialog_view.cc
+++ b/chrome/browser/ui/ash/network/enrollment_dialog_view.cc
@@ -260,7 +260,7 @@
   if (!policy)
     return false;
 
-  client_cert::ClientCertConfig cert_config;
+  ash::client_cert::ClientCertConfig cert_config;
   OncToClientCertConfig(onc_source, policy->GetDict(), &cert_config);
 
   if (cert_config.client_cert_type != onc::client_cert::kPattern)
diff --git a/chrome/browser/ui/ash/network/mobile_data_notifications.cc b/chrome/browser/ui/ash/network/mobile_data_notifications.cc
index f8709fb..7702e145 100644
--- a/chrome/browser/ui/ash/network/mobile_data_notifications.cc
+++ b/chrome/browser/ui/ash/network/mobile_data_notifications.cc
@@ -26,9 +26,9 @@
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/base/l10n/l10n_util.h"
 
+using ::ash::NetworkHandler;
 using ::ash::NetworkState;
-using chromeos::NetworkHandler;
-using chromeos::NetworkStateHandler;
+using ::ash::NetworkStateHandler;
 using session_manager::SessionManager;
 using user_manager::UserManager;
 
diff --git a/chrome/browser/ui/ash/network/mobile_data_notifications.h b/chrome/browser/ui/ash/network/mobile_data_notifications.h
index 2fe7744..84a4d65 100644
--- a/chrome/browser/ui/ash/network/mobile_data_notifications.h
+++ b/chrome/browser/ui/ash/network/mobile_data_notifications.h
@@ -82,7 +82,7 @@
 
   base::OneShotTimer one_shot_notification_check_delay_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ui/ash/network/network_state_notifier.cc b/chrome/browser/ui/ash/network/network_state_notifier.cc
index 262256ba..04ff974 100644
--- a/chrome/browser/ui/ash/network/network_state_notifier.cc
+++ b/chrome/browser/ui/ash/network/network_state_notifier.cc
@@ -556,7 +556,7 @@
       network_name = *esim_name;
   }
   if (network_name.empty() && shill_properties) {
-    network_name = shill_property_util::GetNameFromProperties(
+    network_name = ash::shill_property_util::GetNameFromProperties(
         service_path, shill_properties.value());
   }
 
diff --git a/chrome/browser/ui/ash/network/network_state_notifier.h b/chrome/browser/ui/ash/network/network_state_notifier.h
index 2ecc160..49e9575 100644
--- a/chrome/browser/ui/ash/network/network_state_notifier.h
+++ b/chrome/browser/ui/ash/network/network_state_notifier.h
@@ -140,7 +140,7 @@
   // Tracks GUIDs of activating cellular networks for activation notification.
   std::set<std::string> cellular_activating_guids_;
 
-  base::ScopedObservation<chromeos::NetworkStateHandler,
+  base::ScopedObservation<ash::NetworkStateHandler,
                           ash::NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
index 530195d..efa9434 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.cc
@@ -91,8 +91,9 @@
   app_client_->OnSodaInstalled();
 }
 
-void ProjectorSodaInstallationController::OnSodaError(
-    speech::LanguageCode language_code) {
+void ProjectorSodaInstallationController::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   // Check that language code matches the selected language for projector or is
   // LanguageCode::kNone (signifying the SODA binary failed).
   if (language_code != speech::GetLanguageCode(GetLocale()) &&
diff --git a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.h b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.h
index 0056eb0..49772f2 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller.h
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller.h
@@ -50,7 +50,8 @@
  protected:
   // speech::SodaInstaller::Observer:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override;
 
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 9311b7c..e534f77 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -68,6 +68,7 @@
 #include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_manager.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -1198,12 +1199,17 @@
                                   public ::testing::WithParamInterface<bool> {
  public:
   ChromeShelfControllerTest() {
+    // `media_router::kMediaRouter` is disabled because it has unmet
+    // dependencies and is unrelated to this unit test.
     if (ShouldEnableSyncSettingsCategorization()) {
-      feature_list_.InitAndEnableFeature(
-          chromeos::features::kSyncSettingsCategorization);
+      feature_list_.InitWithFeatures(
+          /*enabled=*/{chromeos::features::kSyncSettingsCategorization},
+          /*disabled=*/{media_router::kMediaRouter});
     } else {
-      feature_list_.InitAndDisableFeature(
-          chromeos::features::kSyncSettingsCategorization);
+      feature_list_.InitWithFeatures(
+          /*enabled=*/{},
+          /*disabled=*/{chromeos::features::kSyncSettingsCategorization,
+                        media_router::kMediaRouter});
     }
   }
   ~ChromeShelfControllerTest() override = default;
@@ -1387,9 +1393,13 @@
     : public ChromeShelfControllerTestBase {
  protected:
   MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest() {
-    // Lacros does not support the ChromeOS Legacy multi profile feature.
-    scoped_feature_list_.InitAndDisableFeature(
-        chromeos::features::kLacrosSupport);
+    // `kLacrosSupport` is disabled since Lacros does not support the ChromeOS
+    // Legacy multi profile feature.
+    // `kMediaRouter` is disabled because it has unmet dependencies and is
+    // unrelated to this unit test.
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled=*/{}, /*disabled=*/{chromeos::features::kLacrosSupport,
+                                      media_router::kMediaRouter});
   }
   MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest(
       const MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest&) =
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.cc b/chrome/browser/ui/ash/system_tray_client_impl.cc
index 3692578..ea2ae6a 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.cc
+++ b/chrome/browser/ui/ash/system_tray_client_impl.cc
@@ -123,7 +123,7 @@
 const ash::NetworkState* GetNetworkState(const std::string& network_id) {
   if (network_id.empty())
     return nullptr;
-  return chromeos::NetworkHandler::Get()
+  return ash::NetworkHandler::Get()
       ->network_state_handler()
       ->GetNetworkStateFromGuid(network_id);
 }
@@ -564,7 +564,7 @@
   if (SessionManager::Get()->IsScreenLocked())
     return;
 
-  DCHECK(chromeos::NetworkHandler::IsInitialized());
+  DCHECK(ash::NetworkHandler::IsInitialized());
   const ash::NetworkState* network_state = GetNetworkState(network_id);
   if (!network_state) {
     LOG(ERROR) << "Network not found: " << network_id;
diff --git a/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm b/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm
index 209b8c6d1..1e9dd96 100644
--- a/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm
+++ b/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h"
 #include "chrome/browser/ui/cocoa/screentime/screentime_features.h"
 #include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
@@ -27,7 +28,10 @@
     ::testing::Test::SetUp();
 
     TabHelper::UseFakeWebpageControllerForTesting();
-    features_.InitAndEnableFeature(kScreenTime);
+    // `kMediaRouter` is disabled because it has unmet dependencies and is
+    // unrelated to this unit test.
+    features_.InitWithFeatures(/*enabled=*/{kScreenTime},
+                               /*disabled=*/{media_router::kMediaRouter});
     profile_ = std::make_unique<TestingProfile>();
   }
 
diff --git a/chrome/browser/ui/extensions/extensions_dialogs.h b/chrome/browser/ui/extensions/extensions_dialogs.h
index d000e88..0a2e532 100644
--- a/chrome/browser/ui/extensions/extensions_dialogs.h
+++ b/chrome/browser/ui/extensions/extensions_dialogs.h
@@ -54,11 +54,6 @@
     content::WebContents* contents,
     base::OnceCallback<void(bool)> callback);
 
-// Shows a dialog indicating that an extension has overridden a setting.
-void ShowExtensionSettingsOverriddenDialog(
-    std::unique_ptr<SettingsOverriddenDialogController> controller,
-    Browser* browser);
-
 // Shows a dialog when extensions require a refresh for their action
 // to be run or blocked. The dialog content is based on whether caller
 // `is_updating_permissions`. When the dialog is accepted, `callback` is
@@ -69,6 +64,12 @@
     bool is_updating_permissions,
     base::OnceClosure callback);
 
+// Shows a dialog with a warning to the user that their settings have been
+// overridden by an extension.
+void ShowSettingsOverriddenDialog(
+    std::unique_ptr<SettingsOverriddenDialogController> controller,
+    Browser* browser);
+
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
 
 // The type of action that the ExtensionInstalledBlockedByParentDialog
diff --git a/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc b/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
index 23d38aa..2ec03e1 100644
--- a/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
+++ b/chrome/browser/ui/extensions/settings_api_bubble_helpers.cc
@@ -153,7 +153,7 @@
   if (!dialog->ShouldShow())
     return;
 
-  ShowExtensionSettingsOverriddenDialog(std::move(dialog), browser);
+  ShowSettingsOverriddenDialog(std::move(dialog), browser);
 #endif
 }
 
@@ -201,7 +201,7 @@
   if (!dialog->ShouldShow())
     return;
 
-  ShowExtensionSettingsOverriddenDialog(std::move(dialog), browser);
+  ShowSettingsOverriddenDialog(std::move(dialog), browser);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.cc b/chrome/browser/ui/global_media_controls/media_notification_service.cc
index c1c8a5d..e80f153 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.cc
@@ -45,15 +45,6 @@
       message));
 }
 
-base::WeakPtr<media_router::WebContentsPresentationManager>
-GetPresentationManager(content::WebContents* web_contents) {
-  if (!web_contents ||
-      !media_router::MediaRouterEnabled(web_contents->GetBrowserContext())) {
-    return nullptr;
-  }
-  return media_router::WebContentsPresentationManager::Get(web_contents);
-}
-
 // Here we check to see if the WebContents is focused. Note that we can't just
 // use |WebContentsObserver::OnWebContentsFocused()| and
 // |WebContentsObserver::OnWebContentsLostFocus()| because focusing the
@@ -76,47 +67,6 @@
 
 }  // namespace
 
-MediaNotificationService::PresentationManagerObservation::
-    PresentationManagerObservation(base::RepeatingClosure cast_started_callback,
-                                   content::WebContents* web_contents)
-    : cast_started_callback_(cast_started_callback),
-      presentation_manager_(GetPresentationManager(web_contents)) {
-  if (presentation_manager_)
-    presentation_manager_->AddObserver(this);
-
-  bool has_presentation_request =
-      presentation_manager_ &&
-      presentation_manager_->HasDefaultPresentationRequest();
-  base::UmaHistogramBoolean(
-      "Media.GlobalMediaControls.HasDefaultPresentationRequest",
-      has_presentation_request);
-}
-
-MediaNotificationService::PresentationManagerObservation::
-    ~PresentationManagerObservation() {
-  if (presentation_manager_)
-    presentation_manager_->RemoveObserver(this);
-}
-
-void MediaNotificationService::PresentationManagerObservation::
-    OnPresentationsChanged(bool has_presentation) {
-  // If there is no presentation, then casting hasn't started.
-  if (!has_presentation)
-    return;
-
-  // This will dismiss the backing item and therefore delete |this|. Do not use
-  // |this| after this call.
-  cast_started_callback_.Run();
-}
-
-void MediaNotificationService::PresentationManagerObservation::
-    SetPresentationManagerForTesting(
-        base::WeakPtr<media_router::WebContentsPresentationManager>
-            presentation_manager) {
-  presentation_manager_ = presentation_manager;
-  presentation_manager_->AddObserver(this);
-}
-
 MediaNotificationService::MediaNotificationService(
     Profile* profile,
     bool show_from_all_profiles) {
@@ -167,7 +117,6 @@
 
 MediaNotificationService::~MediaNotificationService() {
   media_session_item_producer_->RemoveObserver(this);
-  presentation_manager_observations_.clear();
   item_manager_->RemoveItemProducer(media_session_item_producer_.get());
 }
 
@@ -211,25 +160,6 @@
           id, std::move(callback));
 }
 
-void MediaNotificationService::OnMediaSessionItemCreated(
-    const std::string& id) {
-  auto* web_contents = content::MediaSession::GetWebContentsFromRequestId(id);
-
-  // base::Unretained is safe here since we own the object that owns this
-  // callback.
-  presentation_manager_observations_.emplace(
-      std::piecewise_construct, std::forward_as_tuple(id),
-      std::forward_as_tuple(
-          base::BindRepeating(&MediaNotificationService::OnCastStarted,
-                              base::Unretained(this), web_contents),
-          web_contents));
-}
-
-void MediaNotificationService::OnMediaSessionItemDestroyed(
-    const std::string& id) {
-  presentation_manager_observations_.erase(id);
-}
-
 void MediaNotificationService::OnMediaSessionActionButtonPressed(
     const std::string& id,
     media_session::mojom::MediaSessionAction action) {
@@ -367,27 +297,6 @@
   device_provider_ = std::move(device_provider);
 }
 
-void MediaNotificationService::OnCastStarted(
-    content::WebContents* web_contents) {
-  // Hide the dialog.
-  item_manager_->HideDialog();
-
-  if (!web_contents)
-    return;
-
-  // If there is a media item associated with this WebContents, dismiss it.
-  auto request_id =
-      content::MediaSession::GetRequestIdFromWebContents(web_contents);
-  if (!request_id)
-    return;
-
-  auto item = media_session_item_producer_->GetMediaItem(request_id.ToString());
-  if (!item)
-    return;
-
-  item->Dismiss();
-}
-
 bool MediaNotificationService::HasCastNotificationsForWebContents(
     content::WebContents* web_contents) const {
   return !media_router::WebContentsPresentationManager::Get(web_contents)
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.h b/chrome/browser/ui/global_media_controls/media_notification_service.h
index 38c9ba2d..b2cf8a1 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.h
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.h
@@ -65,8 +65,8 @@
       base::RepeatingCallback<void(bool)> callback) override;
 
   // global_media_controls::MediaSessionItemProducerObserver:
-  void OnMediaSessionItemCreated(const std::string& id) override;
-  void OnMediaSessionItemDestroyed(const std::string& id) override;
+  void OnMediaSessionItemCreated(const std::string& id) override {}
+  void OnMediaSessionItemDestroyed(const std::string& id) override {}
   void OnMediaSessionActionButtonPressed(
       const std::string& id,
       media_session::mojom::MediaSessionAction action) override;
@@ -119,33 +119,6 @@
   FRIEND_TEST_ALL_PREFIXES(MediaNotificationServiceCastTest,
                            ShowSupplementalNotifications);
 
-  class PresentationManagerObservation : public content::PresentationObserver {
-   public:
-    PresentationManagerObservation(base::RepeatingClosure cast_started_callback,
-                                   content::WebContents* web_contents);
-    PresentationManagerObservation(const PresentationManagerObservation&) =
-        delete;
-    PresentationManagerObservation& operator=(
-        const PresentationManagerObservation&) = delete;
-    ~PresentationManagerObservation() override;
-
-    // content::PresentationObserver:
-    void OnPresentationsChanged(bool has_presentation) override;
-
-    void SetPresentationManagerForTesting(
-        base::WeakPtr<media_router::WebContentsPresentationManager>
-            presentation_manager);
-
-   private:
-    base::RepeatingClosure cast_started_callback_;
-    base::WeakPtr<media_router::WebContentsPresentationManager>
-        presentation_manager_;
-  };
-
-  // Called by PresentationManagerObservation when casting starts for its
-  // WebContents.
-  void OnCastStarted(content::WebContents* web_contents);
-
   // True if there are cast notifications associated with |web_contents|.
   bool HasCastNotificationsForWebContents(
       content::WebContents* web_contents) const;
@@ -164,11 +137,6 @@
   std::unique_ptr<PresentationRequestNotificationProducer>
       presentation_request_notification_producer_;
 
-  // Observes media_router::WebContentsPresentationManagers so we can dismiss
-  // the dialog when casting starts.
-  std::map<std::string, PresentationManagerObservation>
-      presentation_manager_observations_;
-
   // Used to initialize a MediaRouterUI.
   std::unique_ptr<media_router::StartPresentationContext> context_;
 
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
index 6027cf0..4ab32b7 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
@@ -161,14 +161,6 @@
     service_->cast_notification_producer_->OnRoutesUpdated(routes);
   }
 
-  MediaNotificationService::PresentationManagerObservation*
-  GetPresentationObservation(const base::UnguessableToken& id) {
-    auto it = service_->presentation_manager_observations_.find(id.ToString());
-    return (it == service_->presentation_manager_observations_.end())
-               ? nullptr
-               : &it->second;
-  }
-
   MediaNotificationService* service() { return service_.get(); }
 
  private:
@@ -265,28 +257,6 @@
 };
 
 TEST_F(MediaNotificationServiceCastTest,
-       HideNotification_NewCastSessionStarted) {
-  // If a new cast session starts, hide the media dialog.
-  base::UnguessableToken id = SimulatePlayingControllableMedia();
-  NiceMock<global_media_controls::test::MockMediaDialogDelegate>
-      dialog_delegate;
-  SimulateDialogOpened(&dialog_delegate);
-  EXPECT_TRUE(HasOpenDialog());
-
-  auto presentation_manager =
-      std::make_unique<MockWebContentsPresentationManager>();
-  auto media_route = CreateMediaRoute("id");
-  auto* observation = GetPresentationObservation(id);
-  observation->SetPresentationManagerForTesting(
-      presentation_manager.get()->GetWeakPtr());
-
-  EXPECT_CALL(dialog_delegate, HideMediaDialog());
-  presentation_manager->NotifyMediaRoutesChanged({media_route});
-
-  task_environment()->RunUntilIdle();
-}
-
-TEST_F(MediaNotificationServiceCastTest,
        ShowCastSessionsForPresentationRequest) {
   NiceMock<global_media_controls::test::MockMediaDialogDelegate>
       dialog_delegate;
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
index 3c6b55c..0a9a2acd 100644
--- a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
+++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
@@ -20,6 +20,8 @@
 
 namespace {
 
+content::MediaSession* g_media_session_for_test = nullptr;
+
 content::WebContents* GetWebContentsFromPresentationRequest(
     const content::PresentationRequest& request) {
   auto* rfh = content::RenderFrameHost::FromID(request.render_frame_host_id);
@@ -45,6 +47,13 @@
   return gfx::ImageSkia::CreateFrom1xBitmap(color_type_copy);
 }
 
+content::MediaSession* GetMediaSession(content::WebContents* web_contents) {
+  if (g_media_session_for_test) {
+    return g_media_session_for_test;
+  }
+  return content::MediaSession::Get(web_contents);
+}
+
 }  // namespace
 
 PresentationRequestNotificationItem::PresentationRequestNotificationItem(
@@ -67,7 +76,7 @@
   // the page has no media players.
   auto* web_contents = GetWebContentsFromPresentationRequest(request_);
   DCHECK(web_contents);
-  auto* media_session = content::MediaSession::Get(web_contents);
+  auto* media_session = GetMediaSession(web_contents);
   DCHECK(media_session);
   media_session->AddObserver(observer_receiver_.BindNewPipeAndPassRemote());
 }
@@ -104,7 +113,7 @@
                          std::vector<media_session::MediaImage>>& images) {
   auto* web_contents = GetWebContentsFromPresentationRequest(request_);
   DCHECK(web_contents);
-  auto* media_session = content::MediaSession::Get(web_contents);
+  auto* media_session = GetMediaSession(web_contents);
   DCHECK(media_session);
   media_session::MediaImageManager manager(
       global_media_controls::kMediaItemArtworkMinSize,
@@ -152,6 +161,12 @@
     UpdateViewWithImages();
 }
 
+// static
+void PresentationRequestNotificationItem::SetMediaSessionForTest(
+    content::MediaSession* media_session) {
+  g_media_session_for_test = media_session;
+}
+
 media_message_center::SourceType
 PresentationRequestNotificationItem::SourceType() {
   return media_message_center::SourceType::kPresentationRequest;
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.h b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.h
index f5f9df06..a3e3e28 100644
--- a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.h
+++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.h
@@ -15,6 +15,10 @@
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "ui/gfx/image/image_skia.h"
 
+namespace content {
+class MediaSession;
+}  // namespace content
+
 namespace global_media_controls {
 class MediaItemManager;
 }  // namespace global_media_controls
@@ -63,6 +67,8 @@
     return weak_ptr_factory_.GetWeakPtr();
   }
 
+  static void SetMediaSessionForTest(content::MediaSession* media_session);
+
   const std::string& id() const { return id_; }
   media_router::StartPresentationContext* context() const {
     return context_.get();
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc b/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
index 654744e4..7f6e479 100644
--- a/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
@@ -9,10 +9,66 @@
 #include "components/media_message_center/mock_media_notification_view.h"
 #include "components/media_router/common/mojom/media_router.mojom.h"
 #include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/media_session.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/web_contents.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+class MockMediaSession : public content::MediaSession {
+ public:
+  MOCK_METHOD(void,
+              DidReceiveAction,
+              (media_session::mojom::MediaSessionAction action),
+              (override));
+  MOCK_METHOD(void,
+              SetDuckingVolumeMultiplier,
+              (double multiplier),
+              (override));
+  MOCK_METHOD(void,
+              SetAudioFocusGroupId,
+              (const base::UnguessableToken& group_id),
+              (override));
+  MOCK_METHOD(void, Suspend, (SuspendType suspend_type), (override));
+  MOCK_METHOD(void, Resume, (SuspendType suspend_type), (override));
+  MOCK_METHOD(void, StartDucking, (), (override));
+  MOCK_METHOD(void, StopDucking, (), (override));
+  MOCK_METHOD(void,
+              GetMediaSessionInfo,
+              (GetMediaSessionInfoCallback callback),
+              (override));
+  MOCK_METHOD(void, GetDebugInfo, (GetDebugInfoCallback callback), (override));
+  MOCK_METHOD(void,
+              AddObserver,
+              (mojo::PendingRemote<media_session::mojom::MediaSessionObserver>
+                   observer),
+              (override));
+  MOCK_METHOD(void, PreviousTrack, (), (override));
+  MOCK_METHOD(void, NextTrack, (), (override));
+  MOCK_METHOD(void, SkipAd, (), (override));
+  MOCK_METHOD(void, Seek, (base::TimeDelta seek_time), (override));
+  MOCK_METHOD(void, Stop, (SuspendType suspend_type), (override));
+  MOCK_METHOD(void,
+              GetMediaImageBitmap,
+              (const media_session::MediaImage& image,
+               int minimum_size_px,
+               int desired_size_px,
+               GetMediaImageBitmapCallback callback),
+              (override));
+  MOCK_METHOD(void, SeekTo, (base::TimeDelta seek_time), (override));
+  MOCK_METHOD(void, ScrubTo, (base::TimeDelta seek_time), (override));
+  MOCK_METHOD(void, EnterPictureInPicture, (), (override));
+  MOCK_METHOD(void, ExitPictureInPicture, (), (override));
+  MOCK_METHOD(void,
+              SetAudioSinkId,
+              (const absl::optional<std::string>& id),
+              (override));
+  MOCK_METHOD(void, ToggleMicrophone, (), (override));
+  MOCK_METHOD(void, ToggleCamera, (), (override));
+  MOCK_METHOD(void, HangUp, (), (override));
+  MOCK_METHOD(void, Raise, (), (override));
+  MOCK_METHOD(void, SetMute, (bool mute), (override));
+};
+
 class PresentationRequestNotificationItemTest
     : public ChromeRenderViewHostTestHarness {
  public:
@@ -23,11 +79,25 @@
       const PresentationRequestNotificationItemTest&) = delete;
   ~PresentationRequestNotificationItemTest() override = default;
 
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    PresentationRequestNotificationItem::SetMediaSessionForTest(
+        &media_session_);
+  }
+
+  void TearDown() override {
+    PresentationRequestNotificationItem::SetMediaSessionForTest(nullptr);
+    ChromeRenderViewHostTestHarness::TearDown();
+  }
+
   content::PresentationRequest CreatePresentationRequest() {
     return content::PresentationRequest(
         main_rfh()->GetGlobalId(), {GURL("http://presentation.com")},
         url::Origin::Create(GURL("http://google2.com")));
   }
+
+ private:
+  MockMediaSession media_session_;
 };
 
 TEST_F(PresentationRequestNotificationItemTest, NotificationHeader) {
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
index 62ebbfc7..bc05769 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -81,6 +81,16 @@
     return;
   }
 
+  bool is_disabled = GetVisualState() == Button::STATE_DISABLED;
+  SkColor background_color =
+      is_disabled ? GetForegroundColor(ButtonState::STATE_DISABLED)
+                  : GetColorProvider()->GetColor(
+                        kColorDownloadToolbarButtonRingBackground);
+  SkColor progress_color =
+      is_disabled
+          ? GetForegroundColor(ButtonState::STATE_DISABLED)
+          : GetColorProvider()->GetColor(kColorDownloadToolbarButtonActive);
+
   int x = width() / 2 - kProgressRingRadius;
   int y = height() / 2 - kProgressRingRadius;
   int diameter = 2 * kProgressRingRadius;
@@ -92,20 +102,16 @@
       scanning_animation_.Reset();
       scanning_animation_.Show();
     }
-    views::DrawSpinningRing(
-        canvas, gfx::RectFToSkRect(ring_bounds),
-        GetColorProvider()->GetColor(kColorDownloadToolbarButtonRingBackground),
-        GetColorProvider()->GetColor(kColorDownloadToolbarButtonActive),
-        kProgressRingStrokeWidth, /*start_angle=*/
-        gfx::Tween::IntValueBetween(scanning_animation_.GetCurrentValue(), 0,
-                                    360));
+    views::DrawSpinningRing(canvas, gfx::RectFToSkRect(ring_bounds),
+                            background_color, progress_color,
+                            kProgressRingStrokeWidth, /*start_angle=*/
+                            gfx::Tween::IntValueBetween(
+                                scanning_animation_.GetCurrentValue(), 0, 360));
     return;
   }
 
   views::DrawProgressRing(
-      canvas, gfx::RectFToSkRect(ring_bounds),
-      GetColorProvider()->GetColor(kColorDownloadToolbarButtonRingBackground),
-      GetColorProvider()->GetColor(kColorDownloadToolbarButtonActive),
+      canvas, gfx::RectFToSkRect(ring_bounds), background_color, progress_color,
       kProgressRingStrokeWidth, /*start_angle=*/-90,
       /*sweep_angle=*/360 * progress_info.progress_percentage / 100.0);
 }
@@ -175,12 +181,16 @@
     new_icon = &kDownloadToolbarButtonIcon;
   }
 
-  if (icon_color != gfx::kPlaceholderColor) {
-    for (auto state : kButtonStates) {
-      SetImageModel(state,
-                    ui::ImageModel::FromVectorIcon(*new_icon, icon_color));
-    }
-  }
+  SetImageModel(ButtonState::STATE_NORMAL,
+                ui::ImageModel::FromVectorIcon(*new_icon, icon_color));
+  SetImageModel(ButtonState::STATE_HOVERED,
+                ui::ImageModel::FromVectorIcon(*new_icon, icon_color));
+  SetImageModel(ButtonState::STATE_PRESSED,
+                ui::ImageModel::FromVectorIcon(*new_icon, icon_color));
+  SetImageModel(
+      Button::STATE_DISABLED,
+      ui::ImageModel::FromVectorIcon(
+          *new_icon, GetForegroundColor(ButtonState::STATE_DISABLED)));
 }
 
 std::unique_ptr<views::View> DownloadToolbarButtonView::GetPrimaryView() {
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog.cc
new file mode 100644
index 0000000..c78246e
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog.cc
@@ -0,0 +1,115 @@
+// 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/ui/views/extensions/settings_overridden_dialog.h"
+
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/dialog_model.h"
+#include "ui/base/models/image_model.h"
+#include "ui/color/color_id.h"
+#include "ui/gfx/paint_vector_icon.h"
+
+using DialogResult = SettingsOverriddenDialogController::DialogResult;
+
+namespace {
+
+// Model delegate that notifies the `controller_` when a click event occurs in
+// the settings overriden dialog.
+class SettingsOverriddenDialogDelegate : public ui::DialogModelDelegate {
+ public:
+  explicit SettingsOverriddenDialogDelegate(
+      std::unique_ptr<SettingsOverriddenDialogController> controller)
+      : controller_(std::move(controller)) {}
+
+  void OnDialogAccepted() {
+    HandleDialogResult(DialogResult::kChangeSettingsBack);
+  }
+  void OnDialogCancelled() {
+    HandleDialogResult(DialogResult::kKeepNewSettings);
+  }
+  void OnDialogClosed() { HandleDialogResult(DialogResult::kDialogDismissed); }
+  void OnDialogDestroyed() {
+    if (!result_) {
+      // The dialog may close without firing any of the [accept | cancel |
+      // close] callbacks if e.g. the parent window closes. In this case, notify
+      // the controller that the dialog closed without user action.
+      HandleDialogResult(DialogResult::kDialogClosedWithoutUserAction);
+    }
+  }
+
+  SettingsOverriddenDialogController* controller() { return controller_.get(); }
+
+ private:
+  void HandleDialogResult(DialogResult result) {
+    DCHECK(!result_)
+        << "Trying to re-notify controller of result. Previous result: "
+        << static_cast<int>(*result_)
+        << ", new result: " << static_cast<int>(result);
+    result_ = result;
+    controller_->HandleDialogResult(result);
+  }
+  std::unique_ptr<SettingsOverriddenDialogController> controller_;
+  absl::optional<DialogResult> result_;
+};
+
+}  // namespace
+
+namespace extensions {
+
+void ShowSettingsOverriddenDialog(
+    std::unique_ptr<SettingsOverriddenDialogController> controller,
+    Browser* browser) {
+  SettingsOverriddenDialogController::ShowParams show_params =
+      controller->GetShowParams();
+
+  auto dialog_delegate_unique =
+      std::make_unique<SettingsOverriddenDialogDelegate>(std::move(controller));
+  SettingsOverriddenDialogDelegate* dialog_delegate =
+      dialog_delegate_unique.get();
+
+  ui::DialogModel::Builder dialog_builder =
+      ui::DialogModel::Builder(std::move(dialog_delegate_unique));
+  dialog_builder.SetInternalName(kExtensionSettingsOverridenDialogName)
+      .SetTitle(show_params.dialog_title)
+      .AddBodyText(ui::DialogModelLabel(show_params.message))
+      .AddOkButton(
+          base::BindOnce(&SettingsOverriddenDialogDelegate::OnDialogAccepted,
+                         base::Unretained(dialog_delegate)),
+          l10n_util::GetStringUTF16(
+              IDS_EXTENSION_SETTINGS_OVERRIDDEN_DIALOG_CHANGE_IT_BACK))
+      .AddCancelButton(
+          base::BindOnce(&SettingsOverriddenDialogDelegate::OnDialogCancelled,
+                         base::Unretained(dialog_delegate)),
+          l10n_util::GetStringUTF16(
+              IDS_EXTENSION_SETTINGS_OVERRIDDEN_DIALOG_KEEP_IT))
+      .SetCloseActionCallback(
+          base::BindOnce(&SettingsOverriddenDialogDelegate::OnDialogClosed,
+                         base::Unretained(dialog_delegate)))
+      .SetDialogDestroyingCallback(
+          base::BindOnce(&SettingsOverriddenDialogDelegate::OnDialogDestroyed,
+                         base::Unretained(dialog_delegate)))
+      .OverrideShowCloseButton(false);
+
+  if (show_params.icon) {
+    gfx::ImageSkia icon =
+        gfx::CreateVectorIcon(*show_params.icon,
+                              ChromeLayoutProvider::Get()->GetDistanceMetric(
+                                  DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE),
+                              ui::kColorIcon);
+
+    dialog_builder.SetIcon(ui::ImageModel::FromImageSkia(icon));
+  }
+
+  constrained_window::ShowBrowserModal(dialog_builder.Build(),
+                                       browser->window()->GetNativeWindow());
+  dialog_delegate->controller()->OnDialogShown();
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog.h b/chrome/browser/ui/views/extensions/settings_overridden_dialog.h
new file mode 100644
index 0000000..589ea2b
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog.h
@@ -0,0 +1,11 @@
+// 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 CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_H_
+#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_H_
+
+static constexpr char kExtensionSettingsOverridenDialogName[] =
+    "ExtensionSettingsOverridenDialog";
+
+#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_H_
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog_browsertest.cc
similarity index 90%
rename from chrome/browser/ui/views/extensions/settings_overridden_dialog_view_browsertest.cc
rename to chrome/browser/ui/views/extensions/settings_overridden_dialog_browsertest.cc
index 2537224..e239f8cb 100644
--- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog_browsertest.cc
@@ -2,19 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h"
+#include "chrome/browser/ui/views/extensions/settings_overridden_dialog.h"
 
 #include "base/memory/raw_ptr.h"
 #include "base/path_service.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/extensions/extensions_dialogs.h"
 #include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
 #include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -29,6 +27,10 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "ui/views/test/widget_test.h"
+#include "ui/views/widget/any_widget_observer.h"
+#include "ui/views/widget/widget.h"
+
+using DialogResult = SettingsOverriddenDialogController::DialogResult;
 
 namespace {
 
@@ -100,22 +102,23 @@
     }
   }
 
-  // Creates, shows, and returns a dialog anchored to the given |browser|. The
+  // Creates, shows, and returns a dialog anchored to the given `browser`. The
   // dialog is owned by the views framework.
-  SettingsOverriddenDialogView* ShowSimpleDialog(bool show_icon,
-                                                 Browser* browser) {
+  views::Widget* ShowSimpleDialog(bool show_icon, Browser* browser) {
     SettingsOverriddenDialogController::ShowParams params{
         u"Settings overridden dialog title",
         u"Settings overriden dialog body, which is quite a bit "
         u"longer than the title alone"};
     if (show_icon)
       params.icon = &kProductIcon;
-    auto* dialog =
-        new SettingsOverriddenDialogView(std::make_unique<TestDialogController>(
-            std::move(params), &dialog_result_));
-    dialog->Show(browser->window()->GetNativeWindow());
 
-    return dialog;
+    views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                         kExtensionSettingsOverridenDialogName);
+    extensions::ShowSettingsOverriddenDialog(
+        std::make_unique<TestDialogController>(std::move(params),
+                                               &dialog_result_),
+        browser);
+    return waiter.WaitIfNeededAndGet();
   }
 
   void ShowNtpOverriddenDefaultDialog() {
@@ -156,10 +159,7 @@
     return true;
   }
 
-  absl::optional<SettingsOverriddenDialogController::DialogResult>
-  dialog_result() const {
-    return dialog_result_;
-  }
+  absl::optional<DialogResult> dialog_result() const { return dialog_result_; }
 
  private:
   void LoadExtensionOverridingNewTab() {
@@ -231,9 +231,7 @@
   }
 
   std::string test_name_;
-
-  absl::optional<SettingsOverriddenDialogController::DialogResult>
-      dialog_result_;
+  absl::optional<DialogResult> dialog_result_;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -296,15 +294,11 @@
   Browser* second_browser = CreateBrowser(browser()->profile());
   ASSERT_TRUE(second_browser);
 
-  SettingsOverriddenDialogView* dialog =
-      ShowSimpleDialog(false, second_browser);
-
-  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
-      dialog->GetWidget());
+  views::Widget* dialog = ShowSimpleDialog(false, second_browser);
+  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(dialog);
   CloseBrowserSynchronously(second_browser);
   widget_destroyed_waiter.Wait();
+
   ASSERT_TRUE(dialog_result());
-  EXPECT_EQ(SettingsOverriddenDialogController::DialogResult::
-                kDialogClosedWithoutUserAction,
-            *dialog_result());
+  EXPECT_EQ(DialogResult::kDialogClosedWithoutUserAction, *dialog_result());
 }
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_unittest.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog_unittest.cc
new file mode 100644
index 0000000..23bcd8f5
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/extensions/settings_overridden_dialog.h"
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/extensions/extensions_dialogs.h"
+#include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/any_widget_observer.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_delegate.h"
+
+using DialogResult = SettingsOverriddenDialogController::DialogResult;
+
+namespace {
+
+struct DialogState {
+  bool shown = false;
+  absl::optional<DialogResult> result;
+};
+
+// A dialog controller that updates the provided DialogState when the dialog
+// is interacted with.
+class TestDialogController : public SettingsOverriddenDialogController {
+ public:
+  explicit TestDialogController(DialogState* state)
+      : state_(state), show_params_{u"Dialog Title", u"Dialog Body"} {}
+  TestDialogController(const TestDialogController&) = delete;
+  TestDialogController& operator=(const TestDialogController&) = delete;
+  ~TestDialogController() override = default;
+
+ private:
+  bool ShouldShow() override { return true; }
+  ShowParams GetShowParams() override { return show_params_; }
+  void OnDialogShown() override {
+    EXPECT_FALSE(state_->shown) << "OnDialogShown() called more than once!";
+    state_->shown = true;
+  }
+  void HandleDialogResult(DialogResult result) override {
+    state_->result = result;
+  }
+
+  const raw_ptr<DialogState> state_;
+  const ShowParams show_params_;
+};
+
+}  // namespace
+
+class SettingsOverriddenDialogViewUnitTest : public TestWithBrowserView {
+ public:
+  SettingsOverriddenDialogViewUnitTest() = default;
+  SettingsOverriddenDialogViewUnitTest(
+      const SettingsOverriddenDialogViewUnitTest&) = delete;
+  const SettingsOverriddenDialogViewUnitTest& operator=(
+      const SettingsOverriddenDialogViewUnitTest&) = delete;
+  ~SettingsOverriddenDialogViewUnitTest() override = default;
+
+  views::Widget* ShowDialog(DialogState* state) {
+    auto controller = std::make_unique<TestDialogController>(state);
+    EXPECT_FALSE(state->shown);
+
+    views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                         kExtensionSettingsOverridenDialogName);
+    extensions::ShowSettingsOverriddenDialog(std::move(controller),
+                                             browser_view()->browser());
+    views::Widget* dialog = waiter.WaitIfNeededAndGet();
+    EXPECT_TRUE(state->shown);
+
+    return dialog;
+  }
+};
+
+TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_ChangeSettingsBack) {
+  DialogState state;
+  views::Widget* dialog = ShowDialog(&state);
+
+  views::test::WidgetDestroyedWaiter dialog_waiter(dialog);
+  dialog->widget_delegate()->AsDialogDelegate()->AcceptDialog();
+  dialog_waiter.Wait();
+
+  ASSERT_TRUE(state.result);
+  EXPECT_EQ(DialogResult::kChangeSettingsBack, state.result);
+}
+
+TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_KeepNewSettings) {
+  DialogState state;
+  views::Widget* dialog = ShowDialog(&state);
+
+  views::test::WidgetDestroyedWaiter dialog_waiter(dialog);
+  dialog->widget_delegate()->AsDialogDelegate()->CancelDialog();
+  dialog_waiter.Wait();
+
+  ASSERT_TRUE(state.result);
+  EXPECT_EQ(DialogResult::kKeepNewSettings, state.result);
+}
+
+TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_DismissDialog) {
+  DialogState state;
+  views::Widget* dialog = ShowDialog(&state);
+
+  views::test::WidgetDestroyedWaiter dialog_waiter(dialog);
+  dialog->Close();
+  dialog_waiter.Wait();
+
+  ASSERT_TRUE(state.result);
+  EXPECT_EQ(DialogResult::kDialogDismissed, state.result);
+}
+
+TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_CloseParentWidget) {
+  DialogState state;
+  views::Widget* dialog = ShowDialog(&state);
+
+  views::test::WidgetDestroyedWaiter dialog_waiter(dialog);
+  dialog->CloseNow();
+  dialog_waiter.Wait();
+
+  ASSERT_TRUE(state.result);
+  EXPECT_EQ(DialogResult::kDialogClosedWithoutUserAction, state.result);
+}
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.cc
deleted file mode 100644
index 354c39d..0000000
--- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h"
-
-#include "base/bind.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/views/chrome_layout_provider.h"
-#include "chrome/browser/ui/views/chrome_typography.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/color/color_id.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/layout/fill_layout.h"
-
-namespace extensions {
-
-void ShowExtensionSettingsOverriddenDialog(
-    std::unique_ptr<SettingsOverriddenDialogController> controller,
-    Browser* browser) {
-  // Note: ownership is taken by the view hierarchy.
-  auto* dialog_view = new SettingsOverriddenDialogView(std::move(controller));
-  dialog_view->Show(browser->window()->GetNativeWindow());
-}
-
-}  // namespace extensions
-
-SettingsOverriddenDialogView::SettingsOverriddenDialogView(
-    std::unique_ptr<SettingsOverriddenDialogController> controller)
-    : controller_(std::move(controller)) {
-  SetButtonLabel(ui::DIALOG_BUTTON_OK,
-                 l10n_util::GetStringUTF16(
-                     IDS_EXTENSION_SETTINGS_OVERRIDDEN_DIALOG_CHANGE_IT_BACK));
-  SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
-                 l10n_util::GetStringUTF16(
-                     IDS_EXTENSION_SETTINGS_OVERRIDDEN_DIALOG_KEEP_IT));
-  SetLayoutManager(std::make_unique<views::FillLayout>());
-  set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
-      views::DialogContentType::kText, views::DialogContentType::kText));
-
-  using DialogResult = SettingsOverriddenDialogController::DialogResult;
-  auto make_result_callback = [this](DialogResult result) {
-    // NOTE: The following Bind's are safe because the callback is
-    // owned by this object (indirectly, as a DialogDelegate).
-    return base::BindOnce(
-        &SettingsOverriddenDialogView::NotifyControllerOfResult,
-        base::Unretained(this), result);
-  };
-  SetAcceptCallback(make_result_callback(DialogResult::kChangeSettingsBack));
-  SetCancelCallback(make_result_callback(DialogResult::kKeepNewSettings));
-  SetCloseCallback(make_result_callback(DialogResult::kDialogDismissed));
-
-  SetModalType(ui::MODAL_TYPE_WINDOW);
-  SetShowCloseButton(false);
-  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
-      views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
-
-  SettingsOverriddenDialogController::ShowParams show_params =
-      controller_->GetShowParams();
-  SetTitle(show_params.dialog_title);
-  if (show_params.icon)
-    SetShowIcon(true);
-
-  auto message_label = std::make_unique<views::Label>(
-      show_params.message, views::style::CONTEXT_DIALOG_BODY_TEXT,
-      views::style::STYLE_SECONDARY);
-  message_label->SetMultiLine(true);
-  message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  AddChildView(std::move(message_label));
-}
-
-SettingsOverriddenDialogView::~SettingsOverriddenDialogView() {
-  if (!result_) {
-    // The dialog may close without firing any of the [accept | cancel | close]
-    // callbacks if e.g. the parent window closes. In this case, notify the
-    // controller that the dialog closed without user action.
-    controller_->HandleDialogResult(
-        SettingsOverriddenDialogController::DialogResult::
-            kDialogClosedWithoutUserAction);
-  }
-}
-
-void SettingsOverriddenDialogView::OnThemeChanged() {
-  views::DialogDelegateView::OnThemeChanged();
-
-  const gfx::VectorIcon* icon = controller_->GetShowParams().icon;
-  if (icon) {
-    SetIcon(
-        gfx::CreateVectorIcon(*icon,
-                              ChromeLayoutProvider::Get()->GetDistanceMetric(
-                                  DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE),
-                              GetColorProvider()->GetColor(ui::kColorIcon)));
-  }
-}
-
-void SettingsOverriddenDialogView::Show(gfx::NativeWindow parent) {
-  constrained_window::CreateBrowserModalDialogViews(this, parent)->Show();
-  controller_->OnDialogShown();
-}
-
-void SettingsOverriddenDialogView::NotifyControllerOfResult(
-    SettingsOverriddenDialogController::DialogResult result) {
-  DCHECK(!result_)
-      << "Trying to re-notify controller of result. Previous result: "
-      << static_cast<int>(*result_)
-      << ", new result: " << static_cast<int>(result);
-  result_ = result;
-  controller_->HandleDialogResult(result);
-}
-
-BEGIN_METADATA(SettingsOverriddenDialogView, views::DialogDelegateView)
-END_METADATA
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h
deleted file mode 100644
index 2c8ae93..0000000
--- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_VIEW_H_
-
-#include <memory>
-
-#include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/gfx/native_widget_types.h"
-#include "ui/views/window/dialog_delegate.h"
-
-// A dialog that displays a warning to the user that their settings have been
-// overridden by an extension.
-class SettingsOverriddenDialogView : public views::DialogDelegateView {
- public:
-  METADATA_HEADER(SettingsOverriddenDialogView);
-  explicit SettingsOverriddenDialogView(
-      std::unique_ptr<SettingsOverriddenDialogController> controller);
-  SettingsOverriddenDialogView(const SettingsOverriddenDialogView&) = delete;
-  SettingsOverriddenDialogView& operator=(const SettingsOverriddenDialogView&) =
-      delete;
-  ~SettingsOverriddenDialogView() override;
-
-  void OnThemeChanged() override;
-
-  // Displays the dialog with the given |parent|.
-  void Show(gfx::NativeWindow parent);
-
- private:
-  // Notifies the |controller_| of the |result|.
-  void NotifyControllerOfResult(
-      SettingsOverriddenDialogController::DialogResult result);
-
-  // The result of the dialog; set when notifying the controller.
-  absl::optional<SettingsOverriddenDialogController::DialogResult> result_;
-
-  std::unique_ptr<SettingsOverriddenDialogController> controller_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_SETTINGS_OVERRIDDEN_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_unittest.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_unittest.cc
deleted file mode 100644
index 821d987..0000000
--- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_unittest.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h"
-
-#include "base/memory/raw_ptr.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
-#include "chrome/browser/ui/views/chrome_constrained_window_views_client.h"
-#include "chrome/test/views/chrome_views_test_base.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/views/test/widget_test.h"
-
-namespace {
-
-struct DialogState {
-  bool shown = false;
-  absl::optional<SettingsOverriddenDialogController::DialogResult> result;
-};
-
-// A dialog controller that updates the provided DialogState when the dialog
-// is interacted with.
-class TestDialogController : public SettingsOverriddenDialogController {
- public:
-  explicit TestDialogController(DialogState* state)
-      : state_(state), show_params_{u"Dialog Title", u"Dialog Body"} {}
-  TestDialogController(const TestDialogController&) = delete;
-  TestDialogController& operator=(const TestDialogController&) = delete;
-  ~TestDialogController() override = default;
-
- private:
-  bool ShouldShow() override { return true; }
-  ShowParams GetShowParams() override { return show_params_; }
-  void OnDialogShown() override {
-    EXPECT_FALSE(state_->shown) << "OnDialogShown() called more than once!";
-    state_->shown = true;
-  }
-  void HandleDialogResult(DialogResult result) override {
-    state_->result = result;
-  }
-
-  const raw_ptr<DialogState> state_;
-  const ShowParams show_params_;
-};
-
-}  // namespace
-
-class SettingsOverriddenDialogViewUnitTest : public ChromeViewsTestBase {
- public:
-  SettingsOverriddenDialogViewUnitTest() = default;
-
-  void SetUp() override {
-    ChromeViewsTestBase::SetUp();
-    SetConstrainedWindowViewsClient(CreateChromeConstrainedWindowViewsClient());
-
-    // Create a widget to host the anchor view.
-    anchor_widget_ = CreateTestWidget();
-    anchor_widget_->Show();
-  }
-
-  void TearDown() override {
-    anchor_widget_ = nullptr;
-    ChromeViewsTestBase::TearDown();
-  }
-
-  SettingsOverriddenDialogView* CreateAndShowDialog(
-      std::unique_ptr<TestDialogController> controller) {
-    auto* dialog = new SettingsOverriddenDialogView(std::move(controller));
-    dialog->Show(GetNativeAnchorWindow());
-    return dialog;
-  }
-
-  gfx::NativeWindow GetNativeAnchorWindow() {
-    return anchor_widget_->GetNativeWindow();
-  }
-
-  void CloseAnchorWindow() {
-    // Move out the anchor widget since we'll be closing it.
-    auto anchor_widget = std::move(anchor_widget_);
-    views::test::WidgetDestroyedWaiter destroyed_waiter(anchor_widget.get());
-    anchor_widget->Close();
-    destroyed_waiter.Wait();
-  }
-
- private:
-  std::unique_ptr<views::Widget> anchor_widget_;
-};
-
-TEST_F(SettingsOverriddenDialogViewUnitTest,
-       DialogControllerIsNotifiedWhenShown) {
-  DialogState state;
-  auto controller = std::make_unique<TestDialogController>(&state);
-  auto* dialog = new SettingsOverriddenDialogView(std::move(controller));
-
-  EXPECT_FALSE(state.shown);
-  dialog->Show(GetNativeAnchorWindow());
-  EXPECT_TRUE(state.shown);
-
-  dialog->GetWidget()->CloseNow();
-}
-
-TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_ChangeSettingsBack) {
-  DialogState state;
-  auto controller = std::make_unique<TestDialogController>(&state);
-  auto* dialog = CreateAndShowDialog(std::move(controller));
-
-  dialog->AcceptDialog();
-  ASSERT_TRUE(state.result);
-  EXPECT_EQ(
-      SettingsOverriddenDialogController::DialogResult::kChangeSettingsBack,
-      *state.result);
-}
-
-TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_KeepNewSettings) {
-  DialogState state;
-  auto controller = std::make_unique<TestDialogController>(&state);
-  auto* dialog = CreateAndShowDialog(std::move(controller));
-
-  dialog->CancelDialog();
-  ASSERT_TRUE(state.result);
-  EXPECT_EQ(SettingsOverriddenDialogController::DialogResult::kKeepNewSettings,
-            *state.result);
-}
-
-TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_DismissDialog) {
-  DialogState state;
-  auto controller = std::make_unique<TestDialogController>(&state);
-  auto* dialog = CreateAndShowDialog(std::move(controller));
-
-  views::test::WidgetDestroyedWaiter destroyed_waiter(dialog->GetWidget());
-  dialog->GetWidget()->Close();
-  destroyed_waiter.Wait();
-  ASSERT_TRUE(state.result);
-  EXPECT_EQ(SettingsOverriddenDialogController::DialogResult::kDialogDismissed,
-            *state.result);
-}
-
-TEST_F(SettingsOverriddenDialogViewUnitTest, DialogResult_CloseParentWidget) {
-  DialogState state;
-  auto controller = std::make_unique<TestDialogController>(&state);
-  CreateAndShowDialog(std::move(controller));
-
-  CloseAnchorWindow();
-  ASSERT_TRUE(state.result);
-  EXPECT_EQ(SettingsOverriddenDialogController::DialogResult::
-                kDialogClosedWithoutUserAction,
-            *state.result);
-}
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_factory_chromeos.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_factory_chromeos.cc
index 171a590..336f7b6 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_factory_chromeos.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_factory_chromeos.cc
@@ -3,12 +3,19 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h"
 
 namespace chrome {
 
 std::unique_ptr<BrowserNonClientFrameView> CreateBrowserNonClientFrameView(
     BrowserFrame* frame,
     BrowserView* browser_view) {
+  if (browser_view->browser()->is_type_picture_in_picture()) {
+    return std::make_unique<PictureInPictureBrowserFrameView>(frame,
+                                                              browser_view);
+  }
+
   auto frame_view =
       std::make_unique<BrowserNonClientFrameViewChromeOS>(frame, browser_view);
   frame_view->Init();
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 0f310d1f..a17261ba 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -3575,7 +3575,13 @@
     panes->push_back(infobar_container_);
   if (download_shelf_)
     panes->push_back(download_shelf_->GetView());
-// TODO(crbug.com/1055150): Implement for mac.
+  if (right_aligned_side_panel_)
+    panes->push_back(right_aligned_side_panel_);
+  if (lens_side_panel_)
+    panes->push_back(lens_side_panel_);
+  if (side_search_side_panel_)
+    panes->push_back(side_search_side_panel_);
+  // TODO(crbug.com/1055150): Implement for mac.
   panes->push_back(contents_web_view_);
   if (devtools_web_view_->GetVisible())
     panes->push_back(devtools_web_view_);
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index f30f5c0..cf669ce 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -33,7 +33,9 @@
 constexpr int kWindowBorderThickness = 5;
 constexpr int kResizeAreaCornerSize = 10;
 
-constexpr int kMinWindowWidth = 500;
+// The window has a standard Chrome minimum size and does not have a maximum
+// size.
+constexpr gfx::Size kMinWindowSize(500, 500);
 
 class BackToTabButton : public OverlayWindowImageButton {
  public:
@@ -146,10 +148,6 @@
   return 0;
 }
 
-void PictureInPictureBrowserFrameView::UpdateMinimumSize() {
-  GetWidget()->OnSizeConstraintsChanged();
-}
-
 gfx::Rect PictureInPictureBrowserFrameView::GetBoundsForClientView() const {
   return bounds();
 }
@@ -196,13 +194,7 @@
 }
 
 gfx::Size PictureInPictureBrowserFrameView::GetMinimumSize() const {
-  // TODO(https://crbug.com/1346734): Calculate the size as OverlayWindowViews.
-  return gfx::Size(kMinWindowWidth, kMinWindowWidth);
-}
-
-gfx::Size PictureInPictureBrowserFrameView::GetMaximumSize() const {
-  // TODO(https://crbug.com/1346734): Calculate the size as OverlayWindowViews.
-  return browser_view()->GetMaximumSize();
+  return kMinWindowSize;
 }
 
 void PictureInPictureBrowserFrameView::OnThemeChanged() {
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index 90bfd0e..f8bc64a 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -33,7 +33,6 @@
       const gfx::Size& tabstrip_minimum_size) const override;
   int GetTopInset(bool restored) const override;
   int GetThemeBackgroundXInset() const override;
-  void UpdateMinimumSize() override;
   void UpdateThrobber(bool running) override {}
   gfx::Rect GetBoundsForClientView() const override;
   gfx::Rect GetWindowBoundsForClientBounds(
@@ -45,7 +44,6 @@
   void UpdateWindowTitle() override;
   void SizeConstraintsChanged() override {}
   gfx::Size GetMinimumSize() const override;
-  gfx::Size GetMaximumSize() const override;
   void OnThemeChanged() override;
 
   // Gets the bounds of the controls.
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
index 2124afd..04ba278 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
@@ -381,7 +381,9 @@
   live_caption_title_->SetText(GetLiveCaptionTitle(profile_->GetPrefs()));
 }
 
-void MediaDialogView::OnSodaError(speech::LanguageCode language_code) {
+void MediaDialogView::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   // Check that language code matches the selected language for Live Caption or
   // is LanguageCode::kNone (signifying the SODA binary failed).
   if (!prefs::IsLanguageCodeForLiveCaption(language_code,
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
index a50139a..c2d93a41 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
@@ -128,7 +128,8 @@
 
   // SodaInstaller::Observer overrides:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override;
 
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.cc b/chrome/browser/ui/views/intent_picker_bubble_view.cc
index 3b5bb55..ec58b98b 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.cc
@@ -525,7 +525,7 @@
   }
 
   DCHECK(intent_picker_bubble_->HasCandidates());
-  widget->Show();
+  intent_picker_bubble_->ShowForReason(DisplayReason::USER_GESTURE);
 
   intent_picker_bubble_->SelectDefaultItem();
   return widget;
diff --git a/chrome/browser/ui/views/lens/OWNERS b/chrome/browser/ui/views/lens/OWNERS
index cc13b6e6..93aa6fa 100644
--- a/chrome/browser/ui/views/lens/OWNERS
+++ b/chrome/browser/ui/views/lens/OWNERS
@@ -1,3 +1,4 @@
 benwgold@google.com
 juanmojica@google.com
+stanfield@google.com
 yusuyoutube@google.com
diff --git a/chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.cc
index 5f5f2e5..16c000a 100644
--- a/chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.cc
@@ -30,7 +30,8 @@
   global_registry->Register(std::make_unique<SidePanelEntry>(
       SidePanelEntry::Id::kHistoryClusters,
       l10n_util::GetStringUTF16(IDS_HISTORY_CLUSTERS_JOURNEYS_TAB_LABEL),
-      ui::ImageModel::FromVectorIcon(kJourneysIcon, ui::kColorIcon),
+      ui::ImageModel::FromVectorIcon(kJourneysIcon, ui::kColorIcon,
+                                     /*icon_size=*/16),
       base::BindRepeating(
           &HistoryClustersSidePanelCoordinator::CreateHistoryClustersWebView,
           base::Unretained(this))));
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index ee4a525..c078616 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -8,13 +8,13 @@
 #include "base/memory/raw_ptr.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/accessible_pane_view.h"
 #include "ui/views/controls/resize_area_delegate.h"
-#include "ui/views/view.h"
 #include "ui/views/view_observer.h"
 
 class BrowserView;
 
-class SidePanel : public views::View,
+class SidePanel : public views::AccessiblePaneView,
                   public views::ViewObserver,
                   public views::ResizeAreaDelegate {
  public:
diff --git a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
index 4431bf9..cd9ca53 100644
--- a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
+++ b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/memory/ref_counted.h"
-#include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
@@ -37,38 +36,38 @@
 // consisting of a title and a list of fields. Returns a pointer to the new
 // section's contents, for use with |AddSectionEntry| below. Note that
 // |parent_list|, not the caller, owns the newly added section.
-base::Value::List* AddSection(base::Value::List* parent_list,
-                              base::StringPiece title) {
-  base::Value::Dict section;
-  base::Value::List section_contents;
-  section.Set("title", title);
+base::ListValue* AddSection(base::ListValue* parent_list,
+                            const std::string& title) {
+  std::unique_ptr<base::DictionaryValue> section(new base::DictionaryValue);
+  std::unique_ptr<base::ListValue> section_contents(new base::ListValue);
+  section->SetStringKey("title", title);
   // Grab a raw pointer to the result before |Pass()|ing it on.
-  base::Value::List* result =
-      section.Set("data", std::move(section_contents))->GetIfList();
-  parent_list->Append(std::move(section));
+  base::ListValue* result =
+      section->SetList("data", std::move(section_contents));
+  parent_list->Append(base::Value::FromUniquePtrValue(std::move(section)));
   return result;
 }
 
 // Adds a bool entry to a section (created with |AddSection| above).
-void AddSectionEntry(base::Value::List* section_list,
-                     base::StringPiece name,
+void AddSectionEntry(base::ListValue* section_list,
+                     const std::string& name,
                      bool value) {
   base::Value::Dict entry;
   entry.Set("stat_name", name);
   entry.Set("stat_value", value);
   entry.Set("is_valid", true);
-  section_list->Append(std::move(entry));
+  section_list->GetList().Append(std::move(entry));
 }
 
 // Adds a string entry to a section (created with |AddSection| above).
-void AddSectionEntry(base::Value::List* section_list,
-                     base::StringPiece name,
-                     base::StringPiece value) {
+void AddSectionEntry(base::ListValue* section_list,
+                     const std::string& name,
+                     const std::string& value) {
   base::Value::Dict entry;
   entry.Set("stat_name", name);
   entry.Set("stat_value", value);
   entry.Set("is_valid", true);
-  section_list->Append(std::move(entry));
+  section_list->GetList().Append(std::move(entry));
 }
 
 std::string FilteringBehaviorToString(
@@ -203,21 +202,21 @@
 }
 
 void FamilyLinkUserInternalsMessageHandler::SendBasicInfo() {
-  base::Value::List section_list;
+  base::ListValue section_list;
 
-  base::Value::List* section_general = AddSection(&section_list, "General");
+  base::ListValue* section_general = AddSection(&section_list, "General");
   AddSectionEntry(section_general, "Child detection enabled",
                   ChildAccountService::IsChildAccountDetectionEnabled());
 
   Profile* profile = Profile::FromWebUI(web_ui());
 
-  base::Value::List* section_profile = AddSection(&section_list, "Profile");
+  base::ListValue* section_profile = AddSection(&section_list, "Profile");
   AddSectionEntry(section_profile, "Account", profile->GetProfileUserName());
   AddSectionEntry(section_profile, "Child", profile->IsChild());
 
   SupervisedUserURLFilter* filter = GetSupervisedUserService()->GetURLFilter();
 
-  base::Value::List* section_filter = AddSection(&section_list, "Filter");
+  base::ListValue* section_filter = AddSection(&section_list, "Filter");
   AddSectionEntry(section_filter, "Denylist active", filter->HasDenylist());
   AddSectionEntry(section_filter, "Online checks active",
                   filter->HasAsyncURLChecker());
@@ -232,7 +231,7 @@
     for (const auto& account :
          identity_manager
              ->GetExtendedAccountInfoForAccountsWithRefreshToken()) {
-      base::Value::List* section_user = AddSection(
+      base::ListValue* section_user = AddSection(
           &section_list, "User Information for " + account.full_name);
       AddSectionEntry(section_user, "Account id",
                       account.account_id.ToString());
@@ -247,8 +246,8 @@
     }
   }
 
-  base::Value::Dict result;
-  result.Set("sections", std::move(section_list));
+  base::DictionaryValue result;
+  result.SetKey("sections", std::move(section_list));
   FireWebUIListener("basic-info-received", result);
 
   // Trigger retrieval of the user settings
@@ -272,10 +271,11 @@
     SupervisedUserURLFilter::FilteringBehavior behavior,
     supervised_user_error_page::FilteringBehaviorReason reason,
     bool uncertain) {
-  base::Value::Dict result;
-  result.Set("allowResult", FilteringBehaviorToString(behavior, uncertain));
-  result.Set("manual", reason == supervised_user_error_page::MANUAL &&
-                           behavior == SupervisedUserURLFilter::ALLOW);
+  base::DictionaryValue result;
+  result.SetStringKey("allowResult",
+                      FilteringBehaviorToString(behavior, uncertain));
+  result.SetBoolKey("manual", reason == supervised_user_error_page::MANUAL &&
+                                  behavior == SupervisedUserURLFilter::ALLOW);
   ResolveJavascriptCallback(base::Value(callback_id), result);
 }
 
@@ -287,9 +287,9 @@
     supervised_user_error_page::FilteringBehaviorReason reason,
     bool uncertain) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  base::Value::Dict result;
-  result.Set("url", url.possibly_invalid_spec());
-  result.Set("result", FilteringBehaviorToString(behavior, uncertain));
-  result.Set("reason", FilteringBehaviorReasonToString(reason));
+  base::DictionaryValue result;
+  result.SetStringKey("url", url.possibly_invalid_spec());
+  result.SetStringKey("result", FilteringBehaviorToString(behavior, uncertain));
+  result.SetStringKey("reason", FilteringBehaviorReasonToString(reason));
   FireWebUIListener("filtering-result-received", result);
 }
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos.cc b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
index 9b6c008..f89dae8 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos.cc
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
@@ -108,8 +108,8 @@
     return false;
   }
 
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   const ash::NetworkState* network = network_state_handler->DefaultNetwork();
   const bool metered = network_state_handler->default_network_is_metered();
   // Don't allow an update if we're currently offline or connected
diff --git a/chrome/browser/ui/webui/management/management_ui_handler.cc b/chrome/browser/ui/webui/management/management_ui_handler.cc
index abe2956..0bfb7d4 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler.cc
@@ -724,18 +724,17 @@
 void ManagementUIHandler::AddProxyServerPrivacyDisclosure(
     base::Value::Dict* response) const {
   bool showProxyDisclosure = false;
-  chromeos::NetworkHandler* network_handler = chromeos::NetworkHandler::Get();
+  ash::NetworkHandler* network_handler = ash::NetworkHandler::Get();
   base::Value proxy_settings(base::Value::Type::DICTIONARY);
   // |ui_proxy_config_service| may be missing in tests. If the device is offline
   // (no network connected) the |DefaultNetwork| is null.
-  if (chromeos::NetworkHandler::HasUiProxyConfigService() &&
+  if (ash::NetworkHandler::HasUiProxyConfigService() &&
       network_handler->network_state_handler()->DefaultNetwork()) {
     // Check if proxy is enforced by user policy, a forced install extension or
     // ONC policies. This will only read managed settings.
-    chromeos::NetworkHandler::GetUiProxyConfigService()
-        ->MergeEnforcedProxyConfig(
-            network_handler->network_state_handler()->DefaultNetwork()->guid(),
-            &proxy_settings);
+    ash::NetworkHandler::GetUiProxyConfigService()->MergeEnforcedProxyConfig(
+        network_handler->network_state_handler()->DefaultNetwork()->guid(),
+        &proxy_settings);
   }
   if (!proxy_settings.DictEmpty()) {
     // Proxies can be specified by web server url, via a PAC script or via the
diff --git a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
index f878429..ee9363d 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
@@ -1093,8 +1093,8 @@
   ResetTestConfig();
   // Set pref to use a proxy.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
-  chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
-                                                          &local_state_);
+  ash::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
+                                                     &local_state_);
   base::Value policy_prefs_config = ProxyConfigDictionary::CreateAutoDetect();
   user_prefs_.SetUserPref(
       proxy_config::prefs::kProxy,
@@ -1111,16 +1111,15 @@
   ResetTestConfig();
   // Simulate network disconnected state.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
-  chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
-                                                          &local_state_);
-  chromeos::NetworkStateHandler::NetworkStateList networks;
-  chromeos::NetworkHandler::Get()
-      ->network_state_handler()
-      ->GetNetworkListByType(ash::NetworkTypePattern::Default(),
-                             true,   // configured_only
-                             false,  // visible_only,
-                             0,      // no limit to number of results
-                             &networks);
+  ash::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
+                                                     &local_state_);
+  ash::NetworkStateHandler::NetworkStateList networks;
+  ash::NetworkHandler::Get()->network_state_handler()->GetNetworkListByType(
+      ash::NetworkTypePattern::Default(),
+      true,   // configured_only
+      false,  // visible_only,
+      0,      // no limit to number of results
+      &networks);
   chromeos::ShillServiceClient::TestInterface* service =
       chromeos::ShillServiceClient::Get()->GetTestInterface();
   for (const auto* const network : networks) {
@@ -1134,15 +1133,15 @@
 
   EXPECT_FALSE(GetShowProxyServerPrivacyDisclosure());
 
-  chromeos::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
+  ash::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
 }
 
 TEST_F(ManagementUIHandlerTests, HideProxyServerDisclosureForDirectProxy) {
   ResetTestConfig();
   // Set pref not to use proxy.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
-  chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
-                                                          &local_state_);
+  ash::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
+                                                     &local_state_);
   base::Value policy_prefs_config = ProxyConfigDictionary::CreateDirect();
   user_prefs_.SetUserPref(
       proxy_config::prefs::kProxy,
@@ -1154,7 +1153,7 @@
 
   EXPECT_FALSE(GetShowProxyServerPrivacyDisclosure());
 
-  chromeos::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
+  ash::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
 }
 
 #endif
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index e3ab2f7c..6395ac0 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -22,6 +22,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
+#include "base/values.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -105,7 +106,7 @@
 std::u16string GetAllowedConnectionTypesMessage() {
   if (help_utils_chromeos::IsUpdateOverCellularAllowed(
           /*interactive=*/true)) {
-    const bool metered = chromeos::NetworkHandler::Get()
+    const bool metered = ash::NetworkHandler::Get()
                              ->network_state_handler()
                              ->default_network_is_metered();
     return metered
@@ -200,12 +201,16 @@
   return std::string();
 }
 
-base::Value::Dict GetVersionInfo() {
-  base::Value::Dict version_info;
-  version_info.Set("osVersion", chromeos::version_loader::GetVersion(
-                                    chromeos::version_loader::VERSION_FULL));
-  version_info.Set("arcVersion", chromeos::version_loader::GetARCVersion());
-  version_info.Set("osFirmware", chromeos::version_loader::GetFirmware());
+std::unique_ptr<base::DictionaryValue> GetVersionInfo() {
+  std::unique_ptr<base::DictionaryValue> version_info(
+      new base::DictionaryValue);
+  version_info->SetStringKey("osVersion",
+                             chromeos::version_loader::GetVersion(
+                                 chromeos::version_loader::VERSION_FULL));
+  version_info->SetStringKey("arcVersion",
+                             chromeos::version_loader::GetARCVersion());
+  version_info->SetStringKey("osFirmware",
+                             chromeos::version_loader::GetFirmware());
   return version_info;
 }
 
@@ -480,8 +485,8 @@
   CHECK_EQ(1U, args.size());
   const std::string& callback_id = args[0].GetString();
 
-  chromeos::NetworkStateHandler* network_state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* network_state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   const ash::NetworkState* network = network_state_handler->DefaultNetwork();
   ResolveJavascriptCallback(base::Value(callback_id),
                             base::Value(network && network->IsOnline()));
@@ -537,9 +542,10 @@
                      weak_factory_.GetWeakPtr(), callback_id));
 }
 
-void AboutHandler::OnGetVersionInfoReady(std::string callback_id,
-                                         base::Value::Dict version_info) {
-  ResolveJavascriptCallback(base::Value(callback_id), version_info);
+void AboutHandler::OnGetVersionInfoReady(
+    std::string callback_id,
+    std::unique_ptr<base::DictionaryValue> version_info) {
+  ResolveJavascriptCallback(base::Value(callback_id), *version_info);
 }
 
 void AboutHandler::HandleGetFirmwareUpdateCount(const base::Value::List& args) {
@@ -591,18 +597,19 @@
 void AboutHandler::OnGetTargetChannel(std::string callback_id,
                                       const std::string& current_channel,
                                       const std::string& target_channel) {
-  base::Value::Dict channel_info;
-  channel_info.Set("currentChannel", current_channel);
-  channel_info.Set("targetChannel", target_channel);
+  std::unique_ptr<base::DictionaryValue> channel_info(
+      new base::DictionaryValue);
+  channel_info->SetStringKey("currentChannel", current_channel);
+  channel_info->SetStringKey("targetChannel", target_channel);
 
   // For the LTS pilot simply check whether the device policy is set and ignore
   // its value.
   std::string value;
   bool is_lts =
       ash::CrosSettings::Get()->GetString(ash::kReleaseLtsTag, &value);
-  channel_info.Set("isLts", is_lts);
+  channel_info->SetBoolKey("isLts", is_lts);
 
-  ResolveJavascriptCallback(base::Value(callback_id), channel_info);
+  ResolveJavascriptCallback(base::Value(callback_id), *channel_info);
 }
 
 void AboutHandler::HandleApplyDeferredUpdate(const base::Value::List& args) {
@@ -643,9 +650,9 @@
 
 void AboutHandler::RefreshTPMFirmwareUpdateStatus(
     const std::set<ash::tpm_firmware_update::Mode>& modes) {
-  base::Value::Dict event;
-  event.Set("updateAvailable", !modes.empty());
-  FireWebUIListener("tpm-firmware-update-status-changed", event);
+  std::unique_ptr<base::DictionaryValue> event(new base::DictionaryValue);
+  event->SetBoolKey("updateAvailable", !modes.empty());
+  FireWebUIListener("tpm-firmware-update-status-changed", *event);
 }
 
 void AboutHandler::HandleGetEndOfLifeInfo(const base::Value::List& args) {
@@ -659,14 +666,14 @@
 void AboutHandler::OnGetEndOfLifeInfo(
     std::string callback_id,
     ash::UpdateEngineClient::EolInfo eol_info) {
-  base::Value::Dict response;
+  base::Value response(base::Value::Type::DICTIONARY);
   if (!eol_info.eol_date.is_null()) {
     bool has_eol_passed = eol_info.eol_date <= clock_->Now();
-    response.Set("hasEndOfLife", has_eol_passed);
+    response.SetBoolKey("hasEndOfLife", has_eol_passed);
     int eol_string_id =
         has_eol_passed ? IDS_SETTINGS_ABOUT_PAGE_END_OF_LIFE_MESSAGE_PAST
                        : IDS_SETTINGS_ABOUT_PAGE_END_OF_LIFE_MESSAGE_FUTURE;
-    response.Set(
+    response.SetStringKey(
         "aboutPageEndOfLifeMessage",
         l10n_util::GetStringFUTF16(
             eol_string_id,
@@ -675,8 +682,8 @@
             base::ASCIIToUTF16(has_eol_passed ? chrome::kEolNotificationURL
                                               : chrome::kAutoUpdatePolicyURL)));
   } else {
-    response.Set("hasEndOfLife", false);
-    response.Set("aboutPageEndOfLifeMessage", "");
+    response.SetBoolKey("hasEndOfLife", false);
+    response.SetStringKey("aboutPageEndOfLifeMessage", "");
   }
   ResolveJavascriptCallback(base::Value(callback_id), response);
 }
@@ -748,29 +755,29 @@
   // Only UPDATING state should have progress set.
   DCHECK(status == VersionUpdater::UPDATING || progress == 0);
 
-  base::Value::Dict event;
-  event.Set("status", UpdateStatusToString(status));
-  event.Set("message", message);
-  event.Set("progress", progress);
-  event.Set("rollback", rollback);
-  event.Set("powerwash", powerwash);
-  event.Set("version", version);
+  std::unique_ptr<base::DictionaryValue> event(new base::DictionaryValue);
+  event->SetStringKey("status", UpdateStatusToString(status));
+  event->SetStringKey("message", message);
+  event->SetIntKey("progress", progress);
+  event->SetBoolKey("rollback", rollback);
+  event->SetBoolKey("powerwash", powerwash);
+  event->SetStringKey("version", version);
   // DictionaryValue does not support int64_t, so convert to string.
-  event.Set("size", base::NumberToString(size));
+  event->SetStringKey("size", base::NumberToString(size));
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (status == VersionUpdater::FAILED_OFFLINE ||
       status == VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED) {
     std::u16string types_msg = GetAllowedConnectionTypesMessage();
     if (!types_msg.empty())
-      event.Set("connectionTypes", types_msg);
+      event->SetStringKey("connectionTypes", types_msg);
     else
-      event.Set("connectionTypes", base::Value());
+      event->Set("connectionTypes", std::make_unique<base::Value>());
   } else {
-    event.Set("connectionTypes", base::Value());
+    event->Set("connectionTypes", std::make_unique<base::Value>());
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-  FireWebUIListener("update-status-changed", event);
+  FireWebUIListener("update-status-changed", *event);
 }
 
 #if BUILDFLAG(IS_MAC)
@@ -790,12 +797,12 @@
   else if (state == VersionUpdater::PROMOTED)
     text = l10n_util::GetStringUTF16(IDS_ABOUT_CHROME_AUTOUPDATE_ALL_IS_ON);
 
-  base::Value::Dict promo_state;
-  promo_state.Set("hidden", hidden);
-  promo_state.Set("disabled", disabled);
-  promo_state.Set("actionable", actionable);
+  base::DictionaryValue promo_state;
+  promo_state.SetBoolKey("hidden", hidden);
+  promo_state.SetBoolKey("disabled", disabled);
+  promo_state.SetBoolKey("actionable", actionable);
   if (!text.empty())
-    promo_state.Set("text", text);
+    promo_state.SetStringKey("text", text);
 
   FireWebUIListener("promotion-state-changed", promo_state);
 }
@@ -821,17 +828,19 @@
     std::string callback_id,
     const base::FilePath& label_dir_path,
     const std::string& text) {
-  base::Value::Dict regulatory_info;
+  std::unique_ptr<base::DictionaryValue> regulatory_info(
+      new base::DictionaryValue);
   // Remove unnecessary whitespace.
-  regulatory_info.Set("text", base::CollapseWhitespaceASCII(text, true));
+  regulatory_info->SetStringKey("text",
+                                base::CollapseWhitespaceASCII(text, true));
 
   std::string image_path =
       label_dir_path.AppendASCII(kRegulatoryLabelImageFilename).MaybeAsASCII();
   std::string url =
       std::string("chrome://") + chrome::kChromeOSAssetHost + "/" + image_path;
-  regulatory_info.Set("url", url);
+  regulatory_info->SetStringKey("url", url);
 
-  ResolveJavascriptCallback(base::Value(callback_id), regulatory_info);
+  ResolveJavascriptCallback(base::Value(callback_id), *regulatory_info);
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/ui/webui/settings/about_handler.h b/chrome/browser/ui/webui/settings/about_handler.h
index e3ce694..090e1d0 100644
--- a/chrome/browser/ui/webui/settings/about_handler.h
+++ b/chrome/browser/ui/webui/settings/about_handler.h
@@ -10,7 +10,6 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/webui/help/version_updater.h"
@@ -25,6 +24,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace base {
+class DictionaryValue;
 class FilePath;
 class Clock;
 }  // namespace base
@@ -102,8 +102,9 @@
 
   // Retrieves OS, ARC and firmware versions.
   void HandleGetVersionInfo(const base::Value::List& args);
-  void OnGetVersionInfoReady(std::string callback_id,
-                             base::Value::Dict version_info);
+  void OnGetVersionInfoReady(
+      std::string callback_id,
+      std::unique_ptr<base::DictionaryValue> version_info);
 
   // Retrieves the number of firmware updates available.
   void HandleGetFirmwareUpdateCount(const base::Value::List& args);
diff --git a/chrome/browser/ui/webui/settings/captions_handler.cc b/chrome/browser/ui/webui/settings/captions_handler.cc
index ed49c21..ba3c326 100644
--- a/chrome/browser/ui/webui/settings/captions_handler.cc
+++ b/chrome/browser/ui/webui/settings/captions_handler.cc
@@ -93,7 +93,9 @@
                     base::Value(speech::GetLanguageName(language_code)));
 }
 
-void CaptionsHandler::OnSodaError(speech::LanguageCode language_code) {
+void CaptionsHandler::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   // If multi-language is disabled and the language code received is not for
   // Live Caption (perhaps it is downloading because another feature, such as
   // dictation on ChromeOS, has a different language selected), then return
diff --git a/chrome/browser/ui/webui/settings/captions_handler.h b/chrome/browser/ui/webui/settings/captions_handler.h
index 421951d..104de16 100644
--- a/chrome/browser/ui/webui/settings/captions_handler.h
+++ b/chrome/browser/ui/webui/settings/captions_handler.h
@@ -34,7 +34,8 @@
 
   // SodaInstaller::Observer overrides:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override;
 
diff --git a/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc b/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
index ec9aa61..3581c03 100644
--- a/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
+++ b/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
@@ -61,13 +61,14 @@
   return value;
 }
 
-base::Value::Dict GetScannerResultsAsDictionary(
+base::DictionaryValue GetScannerResultsAsDictionary(
     const safe_browsing::ChromeCleanerScannerResults& scanner_results,
     Profile* profile) {
-  base::Value::Dict value;
-  value.Set("files", GetFilesAsListStorage(scanner_results.files_to_delete()));
-  value.Set("registryKeys",
-            GetStringSetAsListStorage(scanner_results.registry_keys()));
+  base::DictionaryValue value;
+  value.GetDict().Set("files",
+                      GetFilesAsListStorage(scanner_results.files_to_delete()));
+  value.GetDict().Set("registryKeys", GetStringSetAsListStorage(
+                                          scanner_results.registry_keys()));
   return value;
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
index 5b99d9a..659783d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
@@ -192,7 +192,9 @@
           progress)));
 }
 
-void AccessibilityHandler::OnSodaError(speech::LanguageCode language_code) {
+void AccessibilityHandler::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   if (language_code != speech::LanguageCode::kNone &&
       language_code != GetDictationLocale()) {
     return;
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.h b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.h
index b64f767..c21494b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.h
@@ -53,7 +53,8 @@
   void OnSodaInstalled(speech::LanguageCode language_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override;
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
 
   void MaybeAddDictationLocales();
   speech::LanguageCode GetDictationLocale();
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
index 234f5ae..c2dc469 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
 #include "chrome/browser/ash/guest_os/guest_os_terminal.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
@@ -765,8 +766,8 @@
     base::Value::Dict container_info_value;
     container_info_value.Set(kIdKey, container_id.ToDictValue());
     auto info =
-        crostini::CrostiniManager::GetForProfile(profile_)->GetContainerInfo(
-            container_id);
+        guest_os::GuestOsSessionTracker::GetForProfile(profile_)->GetInfo(
+            crostini::DefaultContainerId());
     if (info) {
       container_info_value.Set(kIpv4Key, info->ipv4_address);
     }
diff --git a/chrome/browser/ui/webui/settings/downloads_handler.cc b/chrome/browser/ui/webui/settings/downloads_handler.cc
index ed960d3..a918e80 100644
--- a/chrome/browser/ui/webui/settings/downloads_handler.cc
+++ b/chrome/browser/ui/webui/settings/downloads_handler.cc
@@ -210,17 +210,17 @@
                                    settings.value(), profile_->GetPrefs()))
                                   .has_value();
   // Dict to match the fields used in downloads_page.html.
-  base::Value::Dict dict;
-  dict.Set("linked", got_linked_account);
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetBoolKey("linked", got_linked_account);
   if (got_linked_account) {
-    base::Value::Dict account;
-    account.Set("name", info->account_name);
-    account.Set("login", info->account_login);
-    dict.Set("account", std::move(account));
-    base::Value::Dict folder;
-    folder.Set("name", info->folder_name);
-    folder.Set("link", info->folder_link);
-    dict.Set("folder", std::move(folder));
+    base::Value account(base::Value::Type::DICTIONARY);
+    account.SetStringKey("name", info->account_name);
+    account.SetStringKey("login", info->account_login);
+    dict.SetKey("account", std::move(account));
+    base::Value folder(base::Value::Type::DICTIONARY);
+    folder.SetStringKey("name", info->folder_name);
+    folder.SetStringKey("link", info->folder_link);
+    dict.SetKey("folder", std::move(folder));
   }
   FireWebUIListener("downloads-connection-link-changed", dict);
 }
diff --git a/chrome/browser/ui/webui/settings/font_handler.cc b/chrome/browser/ui/webui/settings/font_handler.cc
index e57acb5..9b7ac45 100644
--- a/chrome/browser/ui/webui/settings/font_handler.cc
+++ b/chrome/browser/ui/webui/settings/font_handler.cc
@@ -74,7 +74,8 @@
   base::Value::Dict response;
   response.Set("fontList", std::move(list));
 
-  ResolveJavascriptCallback(base::Value(callback_id), response);
+  ResolveJavascriptCallback(base::Value(callback_id),
+                            base::Value(std::move(response)));
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/import_data_handler.cc b/chrome/browser/ui/webui/settings/import_data_handler.cc
index 042b1f1..1615e8d 100644
--- a/chrome/browser/ui/webui/settings/import_data_handler.cc
+++ b/chrome/browser/ui/webui/settings/import_data_handler.cc
@@ -216,7 +216,8 @@
     browser_profiles.Append(std::move(browser_profile));
   }
 
-  ResolveJavascriptCallback(base::Value(callback_id), browser_profiles);
+  ResolveJavascriptCallback(base::Value(callback_id),
+                            base::Value(std::move(browser_profiles)));
 }
 
 void ImportDataHandler::ImportStarted() {
diff --git a/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc b/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
index b960d6720..e0a91ae 100644
--- a/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
+++ b/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
@@ -75,7 +75,7 @@
       incompatible_applications =
           IncompatibleApplicationsUpdater::GetCachedApplications();
 
-  base::Value::List application_list;
+  base::Value application_list(base::Value::Type::LIST);
 
   for (const auto& application : incompatible_applications) {
     // Set up a registry watcher for each problem application.
@@ -99,10 +99,12 @@
     }
 
     // Also add the application to the list that is passed to the javascript.
-    base::Value::Dict dict;
-    dict.Set("name", base::WideToUTF8(application.info.name));
-    dict.Set("type", application.blocklist_action->message_type());
-    dict.Set("url", application.blocklist_action->message_url());
+    base::Value dict(base::Value::Type::DICTIONARY);
+    dict.SetKey("name", base::Value(base::WideToUTF8(application.info.name)));
+    dict.SetKey("type",
+                base::Value(application.blocklist_action->message_type()));
+    dict.SetKey("url",
+                base::Value(application.blocklist_action->message_url()));
     application_list.Append(std::move(dict));
   }
 
diff --git a/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc b/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc
index 9b6206f..d5d9c0e 100644
--- a/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc
+++ b/chrome/browser/ui/webui/settings/metrics_reporting_handler.cc
@@ -60,21 +60,24 @@
   AllowJavascript();
   CHECK_GT(args.size(), 0u);
   const base::Value& callback_id = args[0];
-  ResolveJavascriptCallback(callback_id, CreateMetricsReportingDict());
+  ResolveJavascriptCallback(callback_id, *CreateMetricsReportingDict());
 }
 
-base::Value::Dict MetricsReportingHandler::CreateMetricsReportingDict() {
-  base::Value::Dict dict;
-  dict.Set("enabled",
-           ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled());
+std::unique_ptr<base::DictionaryValue>
+    MetricsReportingHandler::CreateMetricsReportingDict() {
+  std::unique_ptr<base::DictionaryValue> dict(
+      std::make_unique<base::DictionaryValue>());
+  dict->SetBoolKey(
+      "enabled",
+      ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled());
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   // To match the pre-Lacros settings UX, we show the managed icon if the ash
   // device-level metrics reporting pref is managed. https://crbug.com/1148604
   bool managed = chromeos::BrowserParamsProxy::Get()->AshMetricsManaged() ==
                  crosapi::mojom::MetricsReportingManaged::kManaged;
-  dict.Set("managed", managed);
+  dict->SetBoolKey("managed", managed);
 #else
-  dict.Set("managed", IsMetricsReportingPolicyManaged());
+  dict->SetBoolKey("managed", IsMetricsReportingPolicyManaged());
 #endif
   return dict;
 }
@@ -126,7 +129,7 @@
 }
 
 void MetricsReportingHandler::SendMetricsReportingChange() {
-  FireWebUIListener("metrics-reporting-change", CreateMetricsReportingDict());
+  FireWebUIListener("metrics-reporting-change", *CreateMetricsReportingDict());
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/on_startup_handler.cc b/chrome/browser/ui/webui/settings/on_startup_handler.cc
index f4227e3..dbc605a 100644
--- a/chrome/browser/ui/webui/settings/on_startup_handler.cc
+++ b/chrome/browser/ui/webui/settings/on_startup_handler.cc
@@ -62,19 +62,20 @@
   FireWebUIListener(kOnStartupNtpExtensionEventName, GetNtpExtension());
 }
 
-base::Value::Dict OnStartupHandler::GetNtpExtension() {
+base::Value OnStartupHandler::GetNtpExtension() {
   const extensions::Extension* ntp_extension =
       extensions::GetExtensionOverridingNewTabPage(profile_);
   if (!ntp_extension) {
-    return base::Value::Dict();
+    return base::Value();
   }
 
-  base::Value::Dict dict;
-  dict.Set("id", ntp_extension->id());
-  dict.Set("name", ntp_extension->name());
-  dict.Set("canBeDisabled", !extensions::ExtensionSystem::Get(profile_)
-                                 ->management_policy()
-                                 ->MustRemainEnabled(ntp_extension, nullptr));
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetStringKey("id", ntp_extension->id());
+  dict.SetStringKey("name", ntp_extension->name());
+  dict.SetBoolKey("canBeDisabled",
+                  !extensions::ExtensionSystem::Get(profile_)
+                       ->management_policy()
+                       ->MustRemainEnabled(ntp_extension, nullptr));
   return dict;
 }
 
diff --git a/chrome/browser/ui/webui/settings/on_startup_handler.h b/chrome/browser/ui/webui/settings/on_startup_handler.h
index bc9fb7b7..98003851 100644
--- a/chrome/browser/ui/webui/settings/on_startup_handler.h
+++ b/chrome/browser/ui/webui/settings/on_startup_handler.h
@@ -8,7 +8,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
-#include "base/values.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
@@ -42,7 +41,7 @@
                            HandleValidateStartupPage_Invalid);
 
   // Info for extension controlling the NTP or empty value.
-  base::Value::Dict GetNtpExtension();
+  base::Value GetNtpExtension();
 
   // Handler for the "getNtpExtension" message. No arguments.
   void HandleGetNtpExtension(const base::Value::List& args);
diff --git a/chrome/browser/ui/webui/settings/search_engines_handler.cc b/chrome/browser/ui/webui/settings/search_engines_handler.cc
index 5cd9340..cd480a2 100644
--- a/chrome/browser/ui/webui/settings/search_engines_handler.cc
+++ b/chrome/browser/ui/webui/settings/search_engines_handler.cc
@@ -207,7 +207,8 @@
   Profile* profile = Profile::FromWebUI(web_ui());
   dict.Set("url",
            template_url->url_ref().DisplayURL(UIThreadSearchTermsData()));
-  dict.Set("urlLocked", template_url->prepopulate_id() > 0);
+  dict.Set("urlLocked", ((template_url->prepopulate_id() > 0) ||
+                         (template_url->starter_pack_id() > 0)));
   GURL icon_url = template_url->favicon_url();
   if (icon_url.is_valid())
     dict.Set("iconURL", icon_url.spec());
diff --git a/chrome/browser/vr/test/gl_test_environment.h b/chrome/browser/vr/test/gl_test_environment.h
index 2cf3249..6a0101c 100644
--- a/chrome/browser/vr/test/gl_test_environment.h
+++ b/chrome/browser/vr/test/gl_test_environment.h
@@ -12,8 +12,8 @@
 #include "ui/gfx/geometry/size.h"
 
 namespace gl {
-class GLSurface;
 class GLContext;
+class GLSurface;
 }  // namespace gl
 
 namespace gpu {
diff --git a/chrome/browser/vr/test/gl_test_environment_native_gl.cc b/chrome/browser/vr/test/gl_test_environment_native_gl.cc
index 32369bd..4d308f1 100644
--- a/chrome/browser/vr/test/gl_test_environment_native_gl.cc
+++ b/chrome/browser/vr/test/gl_test_environment_native_gl.cc
@@ -6,6 +6,7 @@
 
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gl_version_info.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/gl/test/gl_test_helper.h"
@@ -14,7 +15,8 @@
 
 GlTestEnvironment::GlTestEnvironment(const gfx::Size frame_buffer_size) {
   // Setup offscreen GL context.
-  surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface_ =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                        gl::GLContextAttribs());
   context_->MakeCurrent(surface_.get());
diff --git a/chrome/browser/web_applications/app_service/README.md b/chrome/browser/web_applications/app_service/README.md
index c4c8158..f032921 100644
--- a/chrome/browser/web_applications/app_service/README.md
+++ b/chrome/browser/web_applications/app_service/README.md
@@ -2,7 +2,7 @@
 
 This directory contains the App Service publisher classes for web apps (Desktop PWAs and shortcut apps).
 
-App Service [publisher](../../../../components/services/app_service/public/cpp/publisher_base.h)s keep the App Service updates with the set of installed apps, and implement commands such as launching.
+App Service [publishers](../../../../components/services/app_service/public/cpp/publisher_base.h) keep the App Service [updates](../../../../components/services/app_service/public/cpp/app_update.h) with the set of installed apps, and implement commands such as launching.
 
 For Ash, Linux, Mac and Windows, the publisher is [WebApps](web_apps.h). (This is currently also used to support the chrome://apps page in the Lacros browser.)
 
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command.cc b/chrome/browser/web_applications/commands/install_isolated_app_command.cc
index b0f38c3b6..f549515a 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command.cc
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/web_applications/commands/install_isolated_app_command.h"
 
+#include <string>
 #include <utility>
 
 #include "base/bind.h"
@@ -12,21 +13,41 @@
 #include "base/check.h"
 #include "base/containers/flat_set.h"
 #include "base/sequence_checker.h"
+#include "base/strings/string_piece_forward.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/browser/web_applications/commands/web_app_command.h"
 #include "chrome/browser/web_applications/web_app_data_retriever.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
+#include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_url_loader.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 namespace web_app {
 
+namespace {
+
+bool IsUrlLoadingResultSuccess(WebAppUrlLoader::Result result) {
+  return result == WebAppUrlLoader::Result::kUrlLoaded;
+}
+
+absl::optional<std::string> UTF16ToUTF8(base::StringPiece16 src) {
+  std::string dest;
+  if (!base::UTF16ToUTF8(src.data(), src.length(), &dest)) {
+    return absl::nullopt;
+  }
+  return dest;
+}
+
+}  // namespace
+
 InstallIsolatedAppCommand::InstallIsolatedAppCommand(
     base::StringPiece url,
     WebAppUrlLoader& url_loader,
@@ -58,14 +79,6 @@
   DCHECK(callback_.is_null());
 }
 
-namespace {
-
-bool IsUrlLoadingResultSuccess(WebAppUrlLoader::Result result) {
-  return result == WebAppUrlLoader::Result::kUrlLoaded;
-}
-
-}  // namespace
-
 void InstallIsolatedAppCommand::Start() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -75,6 +88,12 @@
     return;
   }
 
+  LoadUrl(url);
+}
+
+void InstallIsolatedAppCommand::LoadUrl(GURL url) {
+  DCHECK(url.is_valid());
+
   url_loader_.LoadUrl(url, shared_web_contents(),
                       WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
                       base::BindOnce(&InstallIsolatedAppCommand::OnLoadUrl,
@@ -89,6 +108,10 @@
     return;
   }
 
+  CheckInstallabilityAndRetrieveManifest();
+}
+
+void InstallIsolatedAppCommand::CheckInstallabilityAndRetrieveManifest() {
   // TODO(kuragin): Fix order of calls to the data retrieve.
   //
   // The order should be:
@@ -106,6 +129,29 @@
           weak_factory_.GetWeakPtr()));
 }
 
+absl::optional<WebAppInstallInfo>
+InstallIsolatedAppCommand::CreateInstallInfoFromManifest(
+    const blink::mojom::Manifest& manifest,
+    const GURL& manifest_url) {
+  WebAppInstallInfo info;
+  UpdateWebAppInfoFromManifest(manifest, manifest_url, &info);
+
+  if (!manifest.id.has_value()) {
+    return absl::nullopt;
+  }
+
+  // In other installations the best-effort encoding is fine, but for isolated
+  // apps we have the opportunity to report this error.
+  if (absl::optional<std::string> encoded_id = UTF16ToUTF8(*manifest.id);
+      encoded_id.has_value()) {
+    info.manifest_id = *encoded_id;
+  } else {
+    return absl::nullopt;
+  }
+
+  return info;
+}
+
 void InstallIsolatedAppCommand::OnCheckInstallabilityAndRetrieveManifest(
     blink::mojom::ManifestPtr opt_manifest,
     const GURL& manifest_url,
@@ -138,12 +184,18 @@
   DCHECK(!manifest_url.is_empty())
       << "must not be empty if manifest is not empty.";
 
-  FinalizeInstall();
+  if (absl::optional<WebAppInstallInfo> install_info =
+          CreateInstallInfoFromManifest(*opt_manifest, manifest_url);
+      install_info.has_value()) {
+    FinalizeInstall(*install_info);
+  } else {
+    ReportFailure();
+  }
 }
 
-void InstallIsolatedAppCommand::FinalizeInstall() {
+void InstallIsolatedAppCommand::FinalizeInstall(const WebAppInstallInfo& info) {
   install_finalizer_.FinalizeInstall(
-      WebAppInstallInfo{},
+      info,
       // TODO(kuragin): Add Isolated app specific install source
       // `WebappInstallSource::ISOLATED_APP_DEV_INSTALL`.
       WebAppInstallFinalizer::FinalizeOptions{
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command.h b/chrome/browser/web_applications/commands/install_isolated_app_command.h
index 465f63c..d7f49cb 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command.h
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command.h
@@ -13,6 +13,8 @@
 #include "base/strings/string_piece_forward.h"
 #include "base/values.h"
 #include "chrome/browser/web_applications/commands/web_app_command.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 class GURL;
@@ -57,13 +59,19 @@
 
   void DownloadIcons();
 
+  void LoadUrl(GURL url);
   void OnLoadUrl(WebAppUrlLoaderResult result);
+
+  void CheckInstallabilityAndRetrieveManifest();
   void OnCheckInstallabilityAndRetrieveManifest(
       blink::mojom::ManifestPtr opt_manifest,
       const GURL& manifest_url,
       bool valid_manifest_for_web_app,
       bool is_installable);
-  void FinalizeInstall();
+  absl::optional<WebAppInstallInfo> CreateInstallInfoFromManifest(
+      const blink::mojom::Manifest& manifest,
+      const GURL& manifest_url);
+  void FinalizeInstall(const WebAppInstallInfo& info);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc b/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
index 6ec1f6d..5d32976 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_piece_forward.h"
+#include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_future.h"
@@ -27,6 +28,7 @@
 #include "chrome/browser/web_applications/web_app_data_retriever.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
+#include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_url_loader.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
@@ -40,23 +42,31 @@
 namespace web_app {
 namespace {
 
+using ::base::BucketsAre;
 using ::base::test::IsNotNullCallback;
 using ::base::test::RunOnceCallback;
 using ::testing::_;
+using ::testing::AllOf;
 using ::testing::Eq;
 using ::testing::Field;
+using ::testing::IsEmpty;
 using ::testing::IsFalse;
+using ::testing::IsNull;
+using ::testing::IsTrue;
 using ::testing::NiceMock;
 using ::testing::Not;
+using ::testing::Optional;
 using ::testing::Pair;
+using ::testing::Pointee;
 using ::testing::UnorderedElementsAre;
 
 blink::mojom::ManifestPtr CreateDefaultManifest() {
   auto manifest = blink::mojom::Manifest::New();
-  manifest->start_url = GURL{"http://test.com/"},
-  manifest->scope = GURL{"http://test.com/scope"},
+  manifest->id = u"some default test manifest id";
+  manifest->start_url = GURL{"http://default-test.com/"},
+  manifest->scope = GURL{"/scope"},
   manifest->display = DisplayMode::kStandalone;
-  manifest->short_name = u"Manifest Name";
+  manifest->short_name = u"test short manifest name";
   return manifest;
 }
 
@@ -145,6 +155,26 @@
     return test_future.Get();
   }
 
+  InstallIsolatedAppCommandResult ExecuteCommandWithManifest(
+      const blink::mojom::ManifestPtr& manifest) {
+    SetPrepareForLoadResultLoaded();
+
+    ExpectLoadedForURL("http://manifest-test-url.com");
+
+    std::unique_ptr<MockDataRetriever> fake_data_retriever =
+        std::make_unique<NiceMock<MockDataRetriever>>();
+
+    ON_CALL(*fake_data_retriever,
+            CheckInstallabilityAndRetrieveManifest(_, _, IsNotNullCallback()))
+        .WillByDefault(RunOnceCallback<2>(
+            /*manifest=*/manifest.Clone(),
+            /*manifest_url=*/CreateDefaultManifestURL(),
+            /*valid_manifest_for_web_app=*/true,
+            /*is_installable=*/true));
+    return ExecuteCommand("http://manifest-test-url.com",
+                          std::move(fake_data_retriever));
+  }
+
   TestingProfile* profile() const { return profile_.get(); }
 
   FakeInstallFinalizer& install_finalizer() {
@@ -281,13 +311,95 @@
           /*manifest=*/nullptr,
           /*manifest_url=*/CreateDefaultManifestURL(),
           /*valid_manifest_for_web_app=*/true,
-          /*is_installable=*/false));
+          /*is_installable=*/true));
 
   EXPECT_THAT(ExecuteCommand("http://test-url-example.com",
                              std::move(fake_data_retriever)),
               Not(IsInstallationOk()));
 }
 
+using InstallIsolatedAppCommandManifestTest = InstallIsolatedAppCommandTest;
+
+TEST_F(InstallIsolatedAppCommandManifestTest,
+       InstallationFailsWhenManifestHasNoId) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->id = absl::nullopt;
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()),
+              Not(IsInstallationOk()));
+
+  EXPECT_THAT(install_finalizer().web_app_info(), IsNull());
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest,
+       FailsWhenManifestIdHasInvalidUTF8Character) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  char16_t invalid_utf8_chars = {0xD801};
+  manifest->id = std::u16string{invalid_utf8_chars};
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()),
+              Not(IsInstallationOk()));
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest, PassesManifestIdToFinalizer) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->id = u"test manifest id";
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()), IsInstallationOk());
+
+  EXPECT_THAT(install_finalizer().web_app_info(),
+              Pointee(Field(&WebAppInstallInfo::manifest_id,
+                            Optional(std::string{"test manifest id"}))));
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest, PassesManifestNameAsTitle) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->name = u"test application name";
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()), IsInstallationOk());
+
+  EXPECT_THAT(
+      install_finalizer().web_app_info(),
+      Pointee(Field(&WebAppInstallInfo::title, u"test application name")));
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest,
+       UseShortNameAsTitleWhenNameIsNotPresent) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->name = absl::nullopt;
+  manifest->short_name = u"test short name";
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()), IsInstallationOk());
+
+  EXPECT_THAT(install_finalizer().web_app_info(),
+              Pointee(Field(&WebAppInstallInfo::title, u"test short name")));
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest,
+       UseShortNameAsTitleWhenNameIsEmpty) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->name = u"";
+  manifest->short_name = u"other test short name";
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()), IsInstallationOk());
+
+  EXPECT_THAT(
+      install_finalizer().web_app_info(),
+      Pointee(Field(&WebAppInstallInfo::title, u"other test short name")));
+}
+
+TEST_F(InstallIsolatedAppCommandManifestTest,
+       TitleIsmptyWhenNameAndShortNameAreNotPresent) {
+  blink::mojom::ManifestPtr manifest = CreateDefaultManifest();
+  manifest->name = absl::nullopt;
+  manifest->short_name = absl::nullopt;
+
+  EXPECT_THAT(ExecuteCommandWithManifest(manifest.Clone()), IsInstallationOk());
+
+  EXPECT_THAT(install_finalizer().web_app_info(),
+              Pointee(Field(&WebAppInstallInfo::title, IsEmpty())));
+}
+
 using InstallIsolatedAppCommandMetricsTest = InstallIsolatedAppCommandTest;
 
 TEST_F(InstallIsolatedAppCommandMetricsTest,
@@ -302,7 +414,7 @@
               IsInstallationOk());
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
-              base::BucketsAre(base::Bucket(true, 1)));
+              BucketsAre(base::Bucket(true, 1)));
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest, ReportFailureWhenURLIsInvalid) {
@@ -314,7 +426,7 @@
               Not(IsInstallationOk()));
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
-              base::BucketsAre(base::Bucket(false, 1)));
+              BucketsAre(base::Bucket(false, 1)));
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest, ReportErrorWhenUrlLoaderFails) {
@@ -328,7 +440,7 @@
               Not(IsInstallationOk()));
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
-              base::BucketsAre(base::Bucket(false, 1)));
+              BucketsAre(base::Bucket(false, 1)));
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest,
@@ -356,7 +468,7 @@
               Not(IsInstallationOk()));
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
-              base::BucketsAre(base::Bucket(false, 1)));
+              BucketsAre(base::Bucket(false, 1)));
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest, ReportFailureWhenManifestIsNull) {
@@ -382,7 +494,7 @@
               Not(IsInstallationOk()));
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
-              base::BucketsAre(base::Bucket(false, 1)));
+              BucketsAre(base::Bucket(false, 1)));
 }
 
 }  // namespace
diff --git a/chrome/browser/web_applications/externally_installed_web_app_prefs.cc b/chrome/browser/web_applications/externally_installed_web_app_prefs.cc
index 29ce6c6..506c61e 100644
--- a/chrome/browser/web_applications/externally_installed_web_app_prefs.cc
+++ b/chrome/browser/web_applications/externally_installed_web_app_prefs.cc
@@ -93,14 +93,6 @@
 }
 
 // static
-// TODO(crbug.com/1236159): Can be removed after M99.
-void ExternallyInstalledWebAppPrefs::RemoveTerminalPWA(
-    PrefService* pref_service) {
-  DictionaryPrefUpdate update(pref_service, prefs::kWebAppsExtensionIDs);
-  update->RemoveKey("chrome-untrusted://terminal/html/pwa.html");
-}
-
-// static
 bool ExternallyInstalledWebAppPrefs::HasAppIdWithInstallSource(
     const PrefService* pref_service,
     const AppId& app_id,
diff --git a/chrome/browser/web_applications/externally_installed_web_app_prefs.h b/chrome/browser/web_applications/externally_installed_web_app_prefs.h
index 0e9702c..bf22a0d 100644
--- a/chrome/browser/web_applications/externally_installed_web_app_prefs.h
+++ b/chrome/browser/web_applications/externally_installed_web_app_prefs.h
@@ -44,9 +44,6 @@
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  // Removes invalid registration for Terminal System App.
-  static void RemoveTerminalPWA(PrefService* pref_service);
-
   explicit ExternallyInstalledWebAppPrefs(PrefService* pref_service);
   ExternallyInstalledWebAppPrefs(const ExternallyInstalledWebAppPrefs&) =
       delete;
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index afe437c4..ed7d5ac2 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1660046387-d50623958aedd9baa0527aeab3831bf051513904.profdata
+chrome-mac-arm-main-1660067998-752bed295d7eb2a9aa79bdd97f2eed52f0d5dcdd.profdata
diff --git a/chrome/chrome_cleaner/test/test_util.cc b/chrome/chrome_cleaner/test/test_util.cc
index f536c93b..bd5c85a6 100644
--- a/chrome/chrome_cleaner/test/test_util.cc
+++ b/chrome/chrome_cleaner/test/test_util.cc
@@ -17,6 +17,7 @@
 
 #include "base/base_paths.h"
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -166,7 +167,7 @@
       argc, argv,
       /*parallel_jobs=*/1U,        // Like LaunchUnitTestsSerially
       /*default_batch_limit=*/10,  // Like LaunchUnitTestsSerially
-      use_job_objects,
+      use_job_objects, base::DoNothing(),
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 
   if (!IsSandboxedProcess())
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index c018b45..eb28697 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -3423,12 +3423,6 @@
 // this pref is set to 0, the action happens immediately.
 const char kSecurityTokenSessionNotificationSeconds[] =
     "security_token_session_notification_seconds";
-// In addition to the notification described directly above, another
-// notification will be displayed after the action happened. This only happens
-// once for a user. This boolean pref saves whether this notification was
-// already displayed for a user.
-const char kSecurityTokenSessionNotificationDisplayed[] =
-    "security_token_session_notification_displayed";
 // This string pref is set when the notification after the action mentioned
 // above is about to be displayed. It contains the domain that manages the user
 // who was logged out, to be used as part of the notification message.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 8b685ac..4d8afaaa 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1190,7 +1190,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kSecurityTokenSessionBehavior[];
 extern const char kSecurityTokenSessionNotificationSeconds[];
-extern const char kSecurityTokenSessionNotificationDisplayed[];
 extern const char kSecurityTokenSessionNotificationScheduledDomain[];
 #endif
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index d20af19..90df675a 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3308,7 +3308,7 @@
         "../browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc",
         "../browser/ui/views/extensions/media_galleries_dialog_views_browsertest.cc",
         "../browser/ui/views/extensions/reload_page_dialog_view_browsertest.cc",
-        "../browser/ui/views/extensions/settings_overridden_dialog_view_browsertest.cc",
+        "../browser/ui/views/extensions/settings_overridden_dialog_browsertest.cc",
         "../browser/ui/views/external_protocol_dialog_browsertest.cc",
         "../browser/ui/views/file_system_access/file_system_access_browsertest.cc",
         "../browser/ui/views/file_system_access/file_system_access_permission_view_browsertest.cc",
@@ -8577,7 +8577,7 @@
       "../browser/ui/views/extensions/extensions_toolbar_unittest.cc",
       "../browser/ui/views/extensions/extensions_toolbar_unittest.h",
       "../browser/ui/views/extensions/media_galleries_dialog_views_unittest.cc",
-      "../browser/ui/views/extensions/settings_overridden_dialog_view_unittest.cc",
+      "../browser/ui/views/extensions/settings_overridden_dialog_unittest.cc",
       "../browser/ui/views/frame/browser_non_client_frame_view_unittest.cc",
       "../browser/ui/views/frame/browser_view_layout_unittest.cc",
       "../browser/ui/views/frame/browser_view_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/request_quota_background/background.js b/chrome/test/data/extensions/api_test/request_quota_background/background.js
index 2f14925f..93cc58c 100644
--- a/chrome/test/data/extensions/api_test/request_quota_background/background.js
+++ b/chrome/test/data/extensions/api_test/request_quota_background/background.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.
 
-webkitStorageInfo.requestQuota(PERSISTENT, 1, pass, fail);
+navigator.webkitPersistentStorage.requestQuota(1, pass, fail);
 
 function pass() {
   console.log("PASS");
diff --git a/chrome/test/data/extensions/platform_apps/web_view/filesystem/main/guest_main.html b/chrome/test/data/extensions/platform_apps/web_view/filesystem/main/guest_main.html
index b499ebf..0991f07 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/filesystem/main/guest_main.html
+++ b/chrome/test/data/extensions/platform_apps/web_view/filesystem/main/guest_main.html
@@ -53,7 +53,7 @@
       };
 
       var requestFileSystemAccess = function() {
-        window.webkitStorageInfo.requestQuota(PERSISTENT, 1024 * 1024,
+        navigator.webkitPersistentStorage.requestQuota(1024 * 1024,
           function(grantedBytes) {
             window.console.log('request Quota granted.');
             window.requestFileSystem(window.PERSISTENT, 1024*1024,
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index fde492a..15defc2 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -9301,6 +9301,7 @@
         },
         "prefs": {
           "security_token_session_behavior": {
+            "location": "local_state",
             "value": "LOGOUT"
           }
         }
@@ -9309,6 +9310,7 @@
         "policies": {},
         "prefs": {
           "security_token_session_behavior": {
+            "location": "local_state",
             "default_value": "IGNORE"
           }
         }
@@ -9326,6 +9328,7 @@
         },
         "prefs": {
           "security_token_session_notification_seconds": {
+            "location": "local_state",
             "value": 10
           }
         }
@@ -9334,6 +9337,7 @@
         "policies": {},
         "prefs": {
           "security_token_session_notification_seconds": {
+            "location": "local_state",
             "default_value": 0
           }
         }
@@ -19131,5 +19135,38 @@
         }
       }
     ]
+  },
+  "UnmanagedDeviceSignalsConsentFlowEnabled": {
+    "os": [
+      "win",
+      "linux",
+      "mac"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "device_signals.consent_collection_enabled": {
+            "default_value": false
+          }
+        }
+      },
+      {
+        "policies": { "UnmanagedDeviceSignalsConsentFlowEnabled": false },
+        "prefs": {
+          "device_signals.consent_collection_enabled": {
+            "value": false
+          }
+        }
+      },
+      {
+        "policies": { "UnmanagedDeviceSignalsConsentFlowEnabled": true },
+        "prefs": {
+          "device_signals.consent_collection_enabled": {
+            "value": true
+          }
+        }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/updater/puffin_patch_test/BUILD.gn b/chrome/test/data/updater/puffin_patch_test/BUILD.gn
deleted file mode 100644
index 8911d0e..0000000
--- a/chrome/test/data/updater/puffin_patch_test/BUILD.gn
+++ /dev/null
@@ -1,73 +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.
-
-import("//components/crx_file/crx3.gni")
-
-group("puffin_patch_test_files") {
-  testonly = true
-  data_deps = [
-    ":puff_patch_v1_to_v2",
-    ":puff_patch_v2_to_v1",
-    ":puffin_app_v1_crx",
-    ":puffin_app_v2_crx",
-  ]
-}
-
-executable("puffin_app_v2") {
-  testonly = true
-  sources = [ "puffin_app_v2/main.cc" ]
-}
-
-executable("puffin_app_v1") {
-  testonly = true
-  sources = [ "puffin_app_v1/main.cc" ]
-}
-
-crx3("puffin_app_v1_crx") {
-  base_dir = "$root_build_dir"
-  key = "//chrome/updater/test/data/selfupdate_test_key.der"
-  output = "$root_build_dir/puffin_app_v1.crx3"
-  testonly = true
-  deps = [ ":puffin_app_v1" ]
-  if (is_win) {
-    inputs = [ "$root_build_dir/puffin_app_v1.exe" ]
-  } else {
-    inputs = [ "$root_build_dir/puffin_app_v1" ]
-  }
-}
-
-copy("puff_patch_v1_to_v2") {
-  if (is_win) {
-    sources = [ "win_v1_to_v2.puff" ]
-  } else if (is_linux) {
-    sources = [ "linux_v1_to_v2.puff" ]
-  } else if (is_mac) {
-    sources = [ "mac_v1_to_v2.puff" ]
-  }
-  outputs = [ "$root_build_dir/puffin_app_v1_to_v2.puff" ]
-}
-
-copy("puff_patch_v2_to_v1") {
-  if (is_win) {
-    sources = [ "win_v2_to_v1.puff" ]
-  } else if (is_linux) {
-    sources = [ "linux_v2_to_v1.puff" ]
-  } else if (is_mac) {
-    sources = [ "mac_v2_to_v1.puff" ]
-  }
-  outputs = [ "$root_build_dir/puffin_app_v2_to_v1.puff" ]
-}
-
-crx3("puffin_app_v2_crx") {
-  base_dir = "$root_build_dir"
-  key = "//chrome/updater/test/data/selfupdate_test_key.der"
-  output = "$root_build_dir/puffin_app_v2.crx3"
-  testonly = true
-  deps = [ ":puffin_app_v2" ]
-  if (is_win) {
-    inputs = [ "$root_build_dir/puffin_app_v2.exe" ]
-  } else {
-    inputs = [ "$root_build_dir/puffin_app_v2" ]
-  }
-}
diff --git a/chrome/test/data/updater/puffin_patch_test/README.md b/chrome/test/data/updater/puffin_patch_test/README.md
deleted file mode 100644
index ce5d3b6a..0000000
--- a/chrome/test/data/updater/puffin_patch_test/README.md
+++ /dev/null
@@ -1,47 +0,0 @@
-## How to regenerate linux, mac and windows test puff files:
-
-If changes are made to puffin_app_v1/main.cc or puffin_app_v2/main.cc, the various puff files which represent a patch between the crx's produced by each of these sources. Thus, we'll need to regenerate these on each platform.
-
-This README assumes you are already in your Chromium repo's src directory, that your gn args were generated in "out/Default", and that you are able to build the third_party/puffin:puffin target. Eventually, puffin will always be a valid target, but currently it requires you to link it into the build somehow. For now, you may need to temporarily add the following to your "//third_party/BUILD.gn"
-
-    group("puffin") {
-      testonly = true
-      deps = [ "//third_party/puffin"]
-    }
-
-*Be careful not to submit this change in the final cl!*
-
-**Linux commands**
-
-    autoninja -C out/Default puffin puffin_app_v1_crx puffin_app_v2_crx
-    rm chrome/test/data/updater/puffin_patch_test/linux_v1_to_v2.puff
-    rm chrome/test/data/updater/puffin_patch_test/linux_v2_to_v1.puff
-    out/Default/puffin -puffdiff out/Default/puffin_app_v1.crx3 out/Default/puffin_app_v2.crx3 chrome/test/data/updater/puffin_patch_test/linux_v1_to_v2.puff
-    out/Default/puffin -puffdiff out/Default/puffin_app_v2.crx3 out/Default/puffin_app_v1.crx3 chrome/test/data/updater/puffin_patch_test/linux_v2_to_v1.puff
-
-**Mac commands**
-
-    autoninja -C out/Default puffin puffin_app_v1_crx puffin_app_v2_crx
-    rm chrome/test/data/updater/puffin_patch_test/mac_v1_to_v2.puff
-    rm chrome/test/data/updater/puffin_patch_test/mac_v2_to_v1.puff
-    out/Default/puffin -puffdiff out/Default/puffin_app_v1.crx3 out/Default/puffin_app_v2.crx3 chrome/test/data/updater/puffin_patch_test/mac_v1_to_v2.puff
-    out/Default/puffin -puffdiff out/Default/puffin_app_v2.crx3 out/Default/puffin_app_v1.crx3 chrome/test/data/updater/puffin_patch_test/mac_v2_to_v1.puff
-
-**Windows commands**
-
-    autoninja -C out\Default puffin puffin_app_v1_crx puffin_app_v2_crx
-    del /f  chrome\test\data\updater\puffin_patch_test\win_v1_to_v2.puff
-    del /f  chrome\test\data\updater\puffin_patch_test\win_v2_to_v1.puff
-    out\Default\puffin.exe -puffdiff out\Default\puffin_app_v1.crx3 out\Default\puffin_app_v2.crx3 chrome\test\data\updater\puffin_patch_test\win_v1_to_v2.puff
-    out\Default\puffin.exe -puffdiff out\Default\puffin_app_v2.crx3 out\Default\puffin_app_v1.crx3 chrome\test\data\updater\puffin_patch_test\win_v2_to_v1.puff
-
-## Testing the new patches
-You can test but running the following commands to verify if all tests pass, on each platform. Specifically the "PatchingTest.ApplyPuffPatchTest":
-
-**Mac and Linux:**
-    autoninja -C out/Default puffin_unittest
-    out/Default/puffin_unittest
-
-**Windows:**
-    autoninja -C out\Default puffin_unittest
-    out\Default\puffin_unittest.exe
\ No newline at end of file
diff --git a/chrome/test/data/updater/puffin_patch_test/linux_v1_to_v2.puff b/chrome/test/data/updater/puffin_patch_test/linux_v1_to_v2.puff
deleted file mode 100644
index 7aaef8b..0000000
--- a/chrome/test/data/updater/puffin_patch_test/linux_v1_to_v2.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/linux_v2_to_v1.puff b/chrome/test/data/updater/puffin_patch_test/linux_v2_to_v1.puff
deleted file mode 100644
index 418392a..0000000
--- a/chrome/test/data/updater/puffin_patch_test/linux_v2_to_v1.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/mac_v1_to_v2.puff b/chrome/test/data/updater/puffin_patch_test/mac_v1_to_v2.puff
deleted file mode 100644
index 059c122..0000000
--- a/chrome/test/data/updater/puffin_patch_test/mac_v1_to_v2.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/mac_v2_to_v1.puff b/chrome/test/data/updater/puffin_patch_test/mac_v2_to_v1.puff
deleted file mode 100644
index 91b92c52..0000000
--- a/chrome/test/data/updater/puffin_patch_test/mac_v2_to_v1.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/win_v1_to_v2.puff b/chrome/test/data/updater/puffin_patch_test/win_v1_to_v2.puff
deleted file mode 100644
index 617a490..0000000
--- a/chrome/test/data/updater/puffin_patch_test/win_v1_to_v2.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/win_v2_to_v1.puff b/chrome/test/data/updater/puffin_patch_test/win_v2_to_v1.puff
deleted file mode 100644
index 1449eac..0000000
--- a/chrome/test/data/updater/puffin_patch_test/win_v2_to_v1.puff
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js
index 53ddbe5..25658ad 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {fakeCalibrationComponentsWithFails, fakeCalibrationComponentsWithoutFails} from 'chrome://shimless-rma/fake_data.js';
 import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
 import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
@@ -175,7 +176,7 @@
 
     let startCalibrationCalls = 0;
     service.startCalibration = (components) => {
-      assertEquals(5, components.length);
+      assertEquals(7, components.length);
       components.forEach(
           component => assertEquals(
               component.component === ComponentType.kCamera ?
@@ -302,4 +303,90 @@
 
     component.removeEventListener('disable-next-button', disableHandler);
   });
+
+  test('CalibrationFailedPageKeyboardNavigationWorks', async () => {
+    await initializeCalibrationPage(fakeCalibrationComponentsWithFails);
+
+    const componentLidAccelerometerButton =
+        component.shadowRoot.querySelector('#componentLidAccelerometer')
+            .shadowRoot.querySelector('#componentButton');
+    const componentScreenButton =
+        component.shadowRoot.querySelector('#componentScreen')
+            .shadowRoot.querySelector('#componentButton');
+    // There are two screens, so we can only get the first one by the id. We get
+    // the second one by the unique id.
+    const componentSecondScreenButton =
+        component.shadowRoot.querySelector('[unique-id="6"]')
+            .shadowRoot.querySelector('#componentButton');
+
+    await flushTasks();
+
+    // At the beginning we should be focused on the first clickable component,
+    // which is the camera.
+    assertDeepEquals(componentLidAccelerometerButton, getDeepActiveElement());
+    // We are at the beginning of the list, so left arrow should do nothing.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
+    await flushTasks();
+    assertDeepEquals(componentLidAccelerometerButton, getDeepActiveElement());
+
+    // Skip disabled buttons until an enable button is found.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+
+    // If the next component is not disabled, we don't skip it.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
+    await flushTasks();
+    assertDeepEquals(componentSecondScreenButton, getDeepActiveElement());
+
+    // We have reached the end of the list, so we can't go any further.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
+    await flushTasks();
+    assertDeepEquals(componentSecondScreenButton, getDeepActiveElement());
+
+    // Check that we can go backwards the same way.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
+    await flushTasks();
+    assertDeepEquals(componentLidAccelerometerButton, getDeepActiveElement());
+
+    // Check that the down button navigates down the column. There is only one
+    // column, so it should be the same as the right button.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowDown'}));
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowDown'}));
+    await flushTasks();
+    assertDeepEquals(componentSecondScreenButton, getDeepActiveElement());
+
+
+    // The up button should work in a similar way.
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowUp'}));
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+    window.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowUp'}));
+    await flushTasks();
+    assertDeepEquals(componentLidAccelerometerButton, getDeepActiveElement());
+
+    // Click on the screen button. It should come into focus.
+    componentScreenButton.click();
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+
+    // Click on the battery button. It's disabled, so we shouldn't focus on it.
+    const componentBatteryButton =
+        component.shadowRoot.querySelector('#componentBattery')
+            .shadowRoot.querySelector('#componentButton');
+    componentBatteryButton.click();
+    await flushTasks();
+    assertDeepEquals(componentScreenButton, getDeepActiveElement());
+
+    // Make sure we can bring both screens into focus, even though they have the
+    // same id.
+    componentSecondScreenButton.click();
+    await flushTasks();
+    assertDeepEquals(componentSecondScreenButton, getDeepActiveElement());
+  });
 }
diff --git a/chrome/test/data/webui/cr_elements/cr_action_menu_test.ts b/chrome/test/data/webui/cr_elements/cr_action_menu_test.ts
index 65bef15..059c7c3 100644
--- a/chrome/test/data/webui/cr_elements/cr_action_menu_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_action_menu_test.ts
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 // clang-format off
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 
 import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {isMac, isWindows} from 'chrome://resources/js/cr.m.js';
 import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
diff --git a/chrome/test/data/webui/cr_elements/cr_checkbox_test.ts b/chrome/test/data/webui/cr_elements/cr_checkbox_test.ts
index a085574..ee45be7 100644
--- a/chrome/test/data/webui/cr_elements/cr_checkbox_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_checkbox_test.ts
@@ -3,11 +3,10 @@
 // found in the LICENSE file.
 
 // clang-format off
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {keyDownOn, keyUpOn, pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
diff --git a/chrome/test/data/webui/cr_elements/cr_lazy_render_tests.ts b/chrome/test/data/webui/cr_elements/cr_lazy_render_tests.ts
index 40b7a27..61cb2d1 100644
--- a/chrome/test/data/webui/cr_elements/cr_lazy_render_tests.ts
+++ b/chrome/test/data/webui/cr_elements/cr_lazy_render_tests.ts
@@ -4,7 +4,7 @@
 
 // clang-format off
 import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 
 import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
diff --git a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config.js b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config.js
index fa60999..420cf7d 100644
--- a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config.js
+++ b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config.js
@@ -140,7 +140,7 @@
   }
 
   /**
-   * Sets the outputVolumePercent to the desired volume.
+   * Sets audioSystemProperties to the desired properties.
    * @param {!ash.audioConfig.mojom.AudioSystemProperties} properties
    */
   setAudioSystemProperties(properties) {
@@ -149,6 +149,15 @@
   }
 
   /**
+   * Sets the outputVolumePercent to the desired volume.
+   * @param {!number} volume
+   */
+  setOutputVolumePercent(volume) {
+    this.audioSystemProperties_.outputVolumePercent = volume;
+    this.notifyAudioSystemPropertiesUpdated_();
+  }
+
+  /**
    * @private
    * Notifies the observer list that audioSystemProperties_ has changed.
    */
diff --git a/chrome/test/data/webui/settings/chromeos/test_device_name_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_device_name_browser_proxy.js
index 39d81379..91f9cf4 100644
--- a/chrome/test/data/webui/settings/chromeos/test_device_name_browser_proxy.js
+++ b/chrome/test/data/webui/settings/chromeos/test_device_name_browser_proxy.js
@@ -6,7 +6,6 @@
 
 import {TestBrowserProxy} from '../../test_browser_proxy.js';
 
-/** @implements {DeviceNameBrowserProxy} */
 export class TestDeviceNameBrowserProxy extends TestBrowserProxy {
   constructor() {
     super([
diff --git a/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts b/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
index e45f142..5cbc764 100644
--- a/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
+++ b/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
@@ -135,7 +135,7 @@
 
   // <if expr="_google_chrome">
   suite(spell_check_page_metrics_test_browser.TestNames.SpellCheckMetricsOfficialBuild, function() {
-    test('records when selecing basic spell check', async () => {
+    test('records when selecting basic spell check', async () => {
       spellCheckPage.setPrefValue('spellcheck.use_spelling_service', true);
       const basicServiceSelect = spellCheckPage.shadowRoot!
           .querySelector<HTMLElement>('#spellingServiceDisable');
@@ -148,7 +148,7 @@
         await languageSettingsMetricsProxy.whenCalled('recordSettingsMetric'));
     });
 
-    test('records when selecing enhanced spell check', async () => {
+    test('records when selecting enhanced spell check', async () => {
       spellCheckPage.setPrefValue('spellcheck.use_spelling_service', false);
       const enhancedServiceSelect = spellCheckPage.shadowRoot!
           .querySelector<HTMLElement>('#spellingServiceEnable');
diff --git a/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts b/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts
index 3cb59ab..d31fd49 100644
--- a/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts
+++ b/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts
@@ -6,7 +6,7 @@
 
 import {EnterpriseProfileWelcomeAppElement} from 'chrome://enterprise-profile-welcome/enterprise_profile_welcome_app.js';
 import {EnterpriseProfileInfo, EnterpriseProfileWelcomeBrowserProxyImpl} from 'chrome://enterprise-profile-welcome/enterprise_profile_welcome_browser_proxy.js';
-import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 8518d7d..9e592ac 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -114,6 +114,8 @@
       "prefs.cc",
       "prefs.h",
       "prefs_impl.h",
+      "refresh_dm_policies_task.cc",
+      "refresh_dm_policies_task.h",
       "registration_data.cc",
       "registration_data.h",
       "remove_uninstalled_apps_task.cc",
diff --git a/chrome/updater/configurator.cc b/chrome/updater/configurator.cc
index 834f96c..1f648f7 100644
--- a/chrome/updater/configurator.cc
+++ b/chrome/updater/configurator.cc
@@ -213,6 +213,10 @@
   return policy_service_;
 }
 
+void Configurator::ResetPolicyService() {
+  policy_service_ = PolicyService::Create(external_constants_);
+}
+
 crx_file::VerifierFormat Configurator::GetCrxVerifierFormat() const {
   return external_constants_->CrxVerifierFormat();
 }
diff --git a/chrome/updater/configurator.h b/chrome/updater/configurator.h
index 29558ae..443498af 100644
--- a/chrome/updater/configurator.h
+++ b/chrome/updater/configurator.h
@@ -81,6 +81,9 @@
   scoped_refptr<PolicyService> GetPolicyService() const;
   crx_file::VerifierFormat GetCrxVerifierFormat() const;
 
+  // This reloads the policy managers.
+  void ResetPolicyService();
+
  private:
   friend class base::RefCountedThreadSafe<Configurator>;
   ~Configurator() override;
diff --git a/chrome/updater/device_management/dm_storage.cc b/chrome/updater/device_management/dm_storage.cc
index 58e27d1..1cfa1f3 100644
--- a/chrome/updater/device_management/dm_storage.cc
+++ b/chrome/updater/device_management/dm_storage.cc
@@ -16,22 +16,16 @@
 #include "base/files/important_file_writer.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
-#include "base/path_service.h"
 #include "base/strings/sys_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/updater/device_management/dm_cached_policy_info.h"
 #include "chrome/updater/device_management/dm_message.h"
 #include "chrome/updater/protos/omaha_settings.pb.h"
-#include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/util.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-#if BUILDFLAG(IS_WIN)
-#include "base/base_paths_win.h"
-#endif  // BUILDFLAG(IS_WIN)
-
 namespace updater {
 
 namespace {
@@ -51,9 +45,6 @@
 // {policy_type} that it receives from the DMServer.
 constexpr char kPolicyFileName[] = "PolicyFetchResponse";
 
-// Policy subfolder in the updater installation path.
-constexpr char kPolicyCacheSubfolder[] = "Policies";
-
 // Deletes the child directories in cache root if they do not appear in
 // set |policy_types_base64|.
 bool DeleteObsoletePolicies(const base::FilePath& cache_root,
@@ -79,7 +70,7 @@
 }  // namespace
 
 #if BUILDFLAG(IS_LINUX)
-// crbug.com/1276162 - implement.
+// TODO(crbug.com/1276162) - implement.
 DMStorage::DMStorage(const base::FilePath& policy_cache_root)
     : policy_cache_root_(policy_cache_root) {
   NOTIMPLEMENTED();
@@ -216,28 +207,12 @@
   return omaha_settings;
 }
 
+#if BUILDFLAG(IS_LINUX)
+// TODO(crbug.com/1276162) - implement.
 scoped_refptr<DMStorage> GetDefaultDMStorage() {
-#if BUILDFLAG(IS_WIN)
-  base::FilePath program_filesx86_dir;
-  if (!base::PathService::Get(base::DIR_PROGRAM_FILESX86,
-                              &program_filesx86_dir)) {
-    return nullptr;
-  }
-
-  return base::MakeRefCounted<DMStorage>(
-      program_filesx86_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
-          .AppendASCII(kPolicyCacheSubfolder));
-
-#else   // BUILDFLAG(IS_WIN)
-
-  const absl::optional<base::FilePath> updater_versioned_path =
-      GetVersionedDataDirectory(GetUpdaterScope());
-  if (!updater_versioned_path)
-    return nullptr;
-  base::FilePath policy_cache_folder =
-      updater_versioned_path->AppendASCII(kPolicyCacheSubfolder);
-  return base::MakeRefCounted<DMStorage>(policy_cache_folder);
-#endif  // BUILDFLAG(IS_WIN)
+  NOTIMPLEMENTED();
+  return nullptr;
 }
+#endif
 
 }  // namespace updater
diff --git a/chrome/updater/device_management/dm_storage.h b/chrome/updater/device_management/dm_storage.h
index dca25dd..f8987d1b 100644
--- a/chrome/updater/device_management/dm_storage.h
+++ b/chrome/updater/device_management/dm_storage.h
@@ -145,9 +145,8 @@
 };
 
 // Returns the DMStorage under which the Device Management policies are
-// persisted. For Windows, this is `%ProgramFiles(x86)%\{CompanyName}\Policies`
-// for security.
-// For other platforms, this is `{UpdaterVersionedPath}\Policies`.
+// persisted. For Windows, this is `%ProgramFiles(x86)%\{CompanyName}\Policies`.
+// For macOS, this is `/Library/{CompanyName}/{KEYSTONE_NAME}/DeviceManagement`.
 scoped_refptr<DMStorage> GetDefaultDMStorage();
 
 }  // namespace updater
diff --git a/chrome/updater/device_management/dm_storage_mac.mm b/chrome/updater/device_management/dm_storage_mac.mm
index 725a27c4..303575f 100644
--- a/chrome/updater/device_management/dm_storage_mac.mm
+++ b/chrome/updater/device_management/dm_storage_mac.mm
@@ -15,8 +15,10 @@
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_ioobject.h"
 #include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
+#include "chrome/updater/mac/mac_util.h"
 #include "chrome/updater/updater_branding.h"
 
 namespace updater {
@@ -151,4 +153,16 @@
 DMStorage::DMStorage(const base::FilePath& policy_cache_root)
     : DMStorage(policy_cache_root, std::make_unique<TokenService>()) {}
 
+scoped_refptr<DMStorage> GetDefaultDMStorage() {
+  absl::optional<base::FilePath> sys_library_path =
+      GetLibraryFolderPath(UpdaterScope::kSystem);
+  if (!sys_library_path)
+    return nullptr;
+
+  return base::MakeRefCounted<DMStorage>(
+      sys_library_path->AppendASCII(COMPANY_SHORTNAME_STRING)
+          .Append(FILE_PATH_LITERAL(KEYSTONE_NAME))
+          .AppendASCII("DeviceManagement"));
+}
+
 }  // namespace updater
diff --git a/chrome/updater/device_management/dm_storage_win.cc b/chrome/updater/device_management/dm_storage_win.cc
index a0e2c5c4..3ee5082 100644
--- a/chrome/updater/device_management/dm_storage_win.cc
+++ b/chrome/updater/device_management/dm_storage_win.cc
@@ -6,8 +6,13 @@
 
 #include <string>
 
+#include "base/base_paths_win.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/path_service.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/win/registry.h"
+#include "chrome/updater/updater_branding.h"
 #include "chrome/updater/win/win_constants.h"
 #include "chrome/updater/win/win_util.h"
 
@@ -106,4 +111,16 @@
 DMStorage::DMStorage(const base::FilePath& policy_cache_root)
     : DMStorage(policy_cache_root, std::make_unique<TokenService>()) {}
 
+scoped_refptr<DMStorage> GetDefaultDMStorage() {
+  base::FilePath program_filesx86_dir;
+  if (!base::PathService::Get(base::DIR_PROGRAM_FILESX86,
+                              &program_filesx86_dir)) {
+    return nullptr;
+  }
+
+  return base::MakeRefCounted<DMStorage>(
+      program_filesx86_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
+          .AppendASCII("Policies"));
+}
+
 }  // namespace updater
diff --git a/chrome/updater/refresh_dm_policies_task.cc b/chrome/updater/refresh_dm_policies_task.cc
new file mode 100644
index 0000000..46de97e
--- /dev/null
+++ b/chrome/updater/refresh_dm_policies_task.cc
@@ -0,0 +1,82 @@
+// 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/updater/refresh_dm_policies_task.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/thread_pool.h"
+#include "chrome/updater/configurator.h"
+#include "chrome/updater/device_management/dm_client.h"
+#include "chrome/updater/device_management/dm_response_validator.h"
+#include "chrome/updater/device_management/dm_storage.h"
+#include "chrome/updater/policy/service.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <shlobj.h>
+#endif  // BUILDFLAG(IS_WIN)
+
+namespace updater {
+
+RefreshDMPoliciesTask::RefreshDMPoliciesTask(scoped_refptr<Configurator> config)
+    : config_(config) {}
+
+RefreshDMPoliciesTask::~RefreshDMPoliciesTask() = default;
+
+void RefreshDMPoliciesTask::Run(base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  VLOG(1) << __func__;
+
+#if BUILDFLAG(IS_WIN)
+  // Returning early, because we cannot write to the DM cache in Windows under
+  // %ProgramFiles(x86)% without having administrative privileges.
+  if (!::IsUserAnAdmin()) {
+    std::move(callback).Run();
+    return;
+  }
+
+  FetchPolicy();
+  std::move(callback).Run();
+  return;
+
+#else   // BUILDFLAG(IS_WIN)
+
+  // `RefreshDMPoliciesTask::FetchPolicy` can block and therefore is running
+  // under a task runner with `base::MayBlock()`.
+  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
+      ->PostTaskAndReply(
+          FROM_HERE, base::BindOnce(&RefreshDMPoliciesTask::FetchPolicy, this),
+          std::move(callback));
+#endif  // BUILDFLAG(IS_WIN)
+}
+
+void RefreshDMPoliciesTask::FetchPolicy() {
+  VLOG(1) << __func__;
+
+  DMClient::FetchPolicy(
+      DMClient::CreateDefaultConfigurator(config_->GetPolicyService()),
+      GetDefaultDMStorage(),
+      base::BindOnce(&RefreshDMPoliciesTask::OnRequestComplete, this));
+}
+
+void RefreshDMPoliciesTask::OnRequestComplete(
+    DMClient::RequestResult result,
+    const std::vector<PolicyValidationResult>& validation_results) {
+  VLOG(1) << __func__;
+
+  // TODO(crbug.com/1345407) : call ReportPolicyValidationErrors() when there's
+  // an error.
+  if (result != DMClient::RequestResult::kSuccess)
+    return;
+
+  config_->ResetPolicyService();
+  VLOG(1) << "Policies are now reloaded.";
+}
+
+}  // namespace updater
diff --git a/chrome/updater/refresh_dm_policies_task.h b/chrome/updater/refresh_dm_policies_task.h
new file mode 100644
index 0000000..4b2697c
--- /dev/null
+++ b/chrome/updater/refresh_dm_policies_task.h
@@ -0,0 +1,44 @@
+// 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 CHROME_UPDATER_REFRESH_DM_POLICIES_TASK_H_
+#define CHROME_UPDATER_REFRESH_DM_POLICIES_TASK_H_
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/updater/configurator.h"
+#include "chrome/updater/device_management/dm_client.h"
+#include "chrome/updater/device_management/dm_response_validator.h"
+
+namespace updater {
+
+// The RefreshDMPolicies refreshes the DM policies from the DM server, and if
+// successful, reloads the policy service with the new policies.
+// TODO(crbug.com/1345407) : do device registration.
+class RefreshDMPoliciesTask
+    : public base::RefCountedThreadSafe<RefreshDMPoliciesTask> {
+ public:
+  explicit RefreshDMPoliciesTask(scoped_refptr<Configurator> config);
+  void Run(base::OnceClosure callback);
+
+ private:
+  friend class base::RefCountedThreadSafe<RefreshDMPoliciesTask>;
+  virtual ~RefreshDMPoliciesTask();
+
+  void FetchPolicy();
+  void OnRequestComplete(
+      DMClient::RequestResult result,
+      const std::vector<PolicyValidationResult>& validation_results);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  scoped_refptr<Configurator> config_;
+};
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_REFRESH_DM_POLICIES_TASK_H_
diff --git a/chrome/updater/run_all_unittests.cc b/chrome/updater/run_all_unittests.cc
index d526c98..697d27e 100644
--- a/chrome/updater/run_all_unittests.cc
+++ b/chrome/updater/run_all_unittests.cc
@@ -14,6 +14,7 @@
 #include "base/test/test_switches.h"
 #include "build/build_config.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/updater/test/integration_test_commands.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <shlobj.h>
@@ -102,7 +103,9 @@
 
   base::TestSuite test_suite(argc, argv);
   chrome::RegisterPathProvider();
-  return base::LaunchUnitTestsSerially(
-      argc, argv,
+  return base::LaunchUnitTestsWithOptions(
+      argc, argv, 1, 10, true, base::BindRepeating([]() {
+        updater::test::CreateIntegrationTestCommands()->PrintLog();
+      }),
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/chrome/updater/update_service_impl.cc b/chrome/updater/update_service_impl.cc
index 8fba9827..5b81e6bd 100644
--- a/chrome/updater/update_service_impl.cc
+++ b/chrome/updater/update_service_impl.cc
@@ -38,6 +38,7 @@
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/policy/service.h"
 #include "chrome/updater/prefs.h"
+#include "chrome/updater/refresh_dm_policies_task.h"
 #include "chrome/updater/registration_data.h"
 #include "chrome/updater/remove_uninstalled_apps_task.h"
 #include "chrome/updater/update_block_check.h"
@@ -301,6 +302,9 @@
                                      base::MakeRefCounted<UpdateUsageStatsTask>(
                                          GetUpdaterScope(), persisted_data_)));
 
+  new_tasks.push_back(
+      base::BindOnce(&RefreshDMPoliciesTask::Run,
+                     base::MakeRefCounted<RefreshDMPoliciesTask>(config_)));
   new_tasks.push_back(base::BindOnce(
       &CheckForUpdatesTask::Run,
       base::MakeRefCounted<CheckForUpdatesTask>(
diff --git a/chrome/updater/zip.gni b/chrome/updater/zip.gni
index 2b818442..2b718ac 100644
--- a/chrome/updater/zip.gni
+++ b/chrome/updater/zip.gni
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/zip.gni")
-import("//build/util/version.gni")
+import("//chrome/version.gni")
 
 # Creates a zip archive of the inputs with a build information stamp.
 #
diff --git a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
index 1910cb6..317ca20 100644
--- a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
+++ b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
@@ -355,6 +355,7 @@
     const vm_tools::cicerone::SetUpLxdContainerUserRequest& request,
     DBusMethodCallback<vm_tools::cicerone::SetUpLxdContainerUserResponse>
         callback) {
+  setup_lxd_container_user_request_ = request;
   last_container_username_ = request.container_username();
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
diff --git a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
index 04709392..313ca60 100644
--- a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
+++ b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
@@ -9,6 +9,7 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
+#include "chromeos/ash/components/dbus/cicerone/cicerone_service.pb.h"
 
 namespace ash {
 
@@ -408,6 +409,11 @@
     send_stop_lxd_container_response_delay_ = delay;
   }
 
+  vm_tools::cicerone::SetUpLxdContainerUserRequest
+  get_setup_lxd_container_user_request() {
+    return setup_lxd_container_user_request_;
+  }
+
   // Returns true if the method has been invoked at least once, false otherwise.
   bool configure_for_arc_sideload_called() {
     return configure_for_arc_sideload_called_;
@@ -554,6 +560,9 @@
   base::TimeDelta send_start_lxd_container_response_delay_;
   base::TimeDelta send_stop_lxd_container_response_delay_;
 
+  vm_tools::cicerone::SetUpLxdContainerUserRequest
+      setup_lxd_container_user_request_;
+
   vm_tools::cicerone::OsRelease lxd_container_os_release_;
 
   LaunchContainerApplicationCallback launch_container_application_callback_;
diff --git a/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc b/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
index b382fc9..2d9d9aa 100644
--- a/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
+++ b/chromeos/ash/components/network/cellular_metrics_logger_unittest.cc
@@ -100,7 +100,7 @@
 
     cellular_inhibitor_ = std::make_unique<CellularInhibitor>();
     mock_managed_network_configuration_manager_ = base::WrapUnique(
-        new testing::NiceMock<ash::MockManagedNetworkConfigurationHandler>);
+        new testing::NiceMock<MockManagedNetworkConfigurationHandler>);
     cellular_esim_profile_handler_ =
         std::make_unique<TestCellularESimProfileHandler>();
     network_config_helper_ = std::make_unique<
@@ -278,7 +278,7 @@
   std::unique_ptr<TestCellularESimProfileHandler>
       cellular_esim_profile_handler_;
   std::unique_ptr<CellularMetricsLogger> cellular_metrics_logger_;
-  std::unique_ptr<ash::MockManagedNetworkConfigurationHandler>
+  std::unique_ptr<MockManagedNetworkConfigurationHandler>
       mock_managed_network_configuration_manager_;
 };
 
diff --git a/chromeos/ash/components/network/cellular_utils.cc b/chromeos/ash/components/network/cellular_utils.cc
index b2ec46c..610cac5 100644
--- a/chromeos/ash/components/network/cellular_utils.cc
+++ b/chromeos/ash/components/network/cellular_utils.cc
@@ -166,7 +166,7 @@
     return euicc_paths[0];
 
   bool use_second_euicc =
-      base::FeatureList::IsEnabled(chromeos::features::kCellularUseSecondEuicc);
+      base::FeatureList::IsEnabled(features::kCellularUseSecondEuicc);
   return use_second_euicc ? euicc_paths[1] : euicc_paths[0];
 }
 
diff --git a/chromeos/ash/components/network/cellular_utils.h b/chromeos/ash/components/network/cellular_utils.h
index 64a1e9c..b8d8ce9 100644
--- a/chromeos/ash/components/network/cellular_utils.h
+++ b/chromeos/ash/components/network/cellular_utils.h
@@ -54,12 +54,9 @@
 
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos {
-using ::ash::GenerateProfilesFromHermes;
-using ::ash::GenerateStubCellularServicePath;
 using ::ash::GetCurrentEuiccPath;
 using ::ash::GetSimSlotInfosWithUpdatedEid;
 using ::ash::IsSimPrimary;
-using ::ash::IsStubCellularServicePath;
 }  // namespace chromeos
 
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_CELLULAR_UTILS_H_
diff --git a/chromeos/ash/components/network/cellular_utils_unittest.cc b/chromeos/ash/components/network/cellular_utils_unittest.cc
index 2ecf668..85ca048a 100644
--- a/chromeos/ash/components/network/cellular_utils_unittest.cc
+++ b/chromeos/ash/components/network/cellular_utils_unittest.cc
@@ -43,7 +43,7 @@
 
 TEST_F(CellularUtilsTest, GetCurrentEuiccPath) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(ash::features::kCellularUseSecondEuicc);
+  feature_list.InitAndEnableFeature(features::kCellularUseSecondEuicc);
   HermesManagerClient::Get()->GetTestInterface()->ClearEuiccs();
   EXPECT_FALSE(GetCurrentEuiccPath());
   // Verify that use-second-euicc flag should be ignored when Hermes only
diff --git a/chromeos/ash/components/network/client_cert_util.h b/chromeos/ash/components/network/client_cert_util.h
index 351a634..7351e91 100644
--- a/chromeos/ash/components/network/client_cert_util.h
+++ b/chromeos/ash/components/network/client_cert_util.h
@@ -181,17 +181,4 @@
 
 }  // namespace ash::client_cert
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos::client_cert {
-using ::ash::client_cert::ClientCertConfig;
-using ::ash::client_cert::ConfigType;
-using ::ash::client_cert::GetClientCertFromShillProperties;
-using ::ash::client_cert::kDefaultTPMPin;
-using ::ash::client_cert::OncToClientCertConfig;
-using ::ash::client_cert::ResolvedCert;
-using ::ash::client_cert::SetEmptyShillProperties;
-using ::ash::client_cert::SetResolvedCertInOnc;
-using ::ash::client_cert::SetShillProperties;
-}  // namespace chromeos::client_cert
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_CLIENT_CERT_UTIL_H_
diff --git a/chromeos/ash/components/network/fake_network_connection_handler.h b/chromeos/ash/components/network/fake_network_connection_handler.h
index ae6a692..6ed2e53 100644
--- a/chromeos/ash/components/network/fake_network_connection_handler.h
+++ b/chromeos/ash/components/network/fake_network_connection_handler.h
@@ -94,9 +94,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::FakeNetworkConnectionHandler;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_FAKE_NETWORK_CONNECTION_HANDLER_H_
diff --git a/chromeos/ash/components/network/fake_stub_cellular_networks_provider.h b/chromeos/ash/components/network/fake_stub_cellular_networks_provider.h
index 27f1a685..ba18c1a 100644
--- a/chromeos/ash/components/network/fake_stub_cellular_networks_provider.h
+++ b/chromeos/ash/components/network/fake_stub_cellular_networks_provider.h
@@ -60,9 +60,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::FakeStubCellularNetworksProvider;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_FAKE_STUB_CELLULAR_NETWORKS_PROVIDER_H_
diff --git a/chromeos/ash/components/network/hermes_metrics_util.h b/chromeos/ash/components/network/hermes_metrics_util.h
index 24e9cb4..60b2ade 100644
--- a/chromeos/ash/components/network/hermes_metrics_util.h
+++ b/chromeos/ash/components/network/hermes_metrics_util.h
@@ -30,9 +30,4 @@
 
 }  // namespace ash::hermes_metrics
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-namespace hermes_metrics = ::ash::hermes_metrics;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_HERMES_METRICS_UTIL_H_
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler.h b/chromeos/ash/components/network/managed_cellular_pref_handler.h
index 35ff03b7..5f740a4 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler.h
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler.h
@@ -70,9 +70,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::ManagedCellularPrefHandler;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_MANAGED_CELLULAR_PREF_HANDLER_H_
diff --git a/chromeos/ash/components/network/managed_state.h b/chromeos/ash/components/network/managed_state.h
index 33e14c2..14a11ed 100644
--- a/chromeos/ash/components/network/managed_state.h
+++ b/chromeos/ash/components/network/managed_state.h
@@ -148,9 +148,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::ManagedState;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_MANAGED_STATE_H_
diff --git a/chromeos/ash/components/network/metrics/connection_results.h b/chromeos/ash/components/network/metrics/connection_results.h
index be2fb37f..4e68f9d 100644
--- a/chromeos/ash/components/network/metrics/connection_results.h
+++ b/chromeos/ash/components/network/metrics/connection_results.h
@@ -133,9 +133,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::ShillConnectResult;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_CONNECTION_RESULTS_H_
diff --git a/chromeos/ash/components/network/metrics/network_metrics_helper.h b/chromeos/ash/components/network/metrics/network_metrics_helper.h
index ad30be4..5150c8e8 100644
--- a/chromeos/ash/components/network/metrics/network_metrics_helper.h
+++ b/chromeos/ash/components/network/metrics/network_metrics_helper.h
@@ -61,9 +61,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::NetworkMetricsHelper;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_METRICS_NETWORK_METRICS_HELPER_H_
diff --git a/chromeos/ash/components/network/mock_network_state_handler.h b/chromeos/ash/components/network/mock_network_state_handler.h
index 33ab04c..eda27ad9 100644
--- a/chromeos/ash/components/network/mock_network_state_handler.h
+++ b/chromeos/ash/components/network/mock_network_state_handler.h
@@ -32,9 +32,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::MockNetworkStateHandler;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_MOCK_NETWORK_STATE_HANDLER_H_
diff --git a/chromeos/ash/components/network/network_name_util_unittest.cc b/chromeos/ash/components/network/network_name_util_unittest.cc
index 4a2ff88..355ef8eb 100644
--- a/chromeos/ash/components/network/network_name_util_unittest.cc
+++ b/chromeos/ash/components/network/network_name_util_unittest.cc
@@ -94,7 +94,7 @@
                  kTestServiceProviderName, hermes::profile::State::kActive,
                  kTestESimCellularServicePath);
 
-  const chromeos::NetworkState* network =
+  const NetworkState* network =
       network_state_test_helper_.network_state_handler()->GetNetworkState(
           kTestESimCellularServicePath);
 
@@ -107,7 +107,7 @@
 TEST_F(NetworkNameUtilTest, EsimNetworNetworkNamePriority) {
   AddESimProfile("", "", kTestServiceProviderName,
                  hermes::profile::State::kActive, kTestESimCellularServicePath);
-  const chromeos::NetworkState* network =
+  const NetworkState* network =
       network_state_test_helper_.network_state_handler()->GetNetworkState(
           kTestESimCellularServicePath);
 
@@ -119,7 +119,7 @@
 
 TEST_F(NetworkNameUtilTest, EthernetNetworkGetNetworkName) {
   AddEthernet();
-  const chromeos::NetworkState* network =
+  const NetworkState* network =
       network_state_test_helper_.network_state_handler()->GetNetworkState(
           kTestEthServicePath);
 
@@ -141,7 +141,7 @@
       base::Value(kTestNameFromShill));
   base::RunLoop().RunUntilIdle();
 
-  const chromeos::NetworkState* network =
+  const NetworkState* network =
       network_state_test_helper_.network_state_handler()->GetNetworkState(
           kTestESimCellularServicePath);
 
diff --git a/chromeos/ash/components/network/network_profile_observer.h b/chromeos/ash/components/network/network_profile_observer.h
index d9e27a9e..20c45e8 100644
--- a/chromeos/ash/components/network/network_profile_observer.h
+++ b/chromeos/ash/components/network/network_profile_observer.h
@@ -22,9 +22,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::NetworkProfileObserver;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_NETWORK_PROFILE_OBSERVER_H_
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index 8deef0f..9fd4cc5 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -199,7 +199,7 @@
     return true;
   } else if (key == shill::kUIDataProperty) {
     std::unique_ptr<NetworkUIData> ui_data =
-        chromeos::shill_property_util::GetUIDataFromValue(value);
+        shill_property_util::GetUIDataFromValue(value);
     if (!ui_data)
       return false;
     onc_source_ = ui_data->onc_source();
diff --git a/chromeos/ash/components/network/network_util.cc b/chromeos/ash/components/network/network_util.cc
index 84dadf5..19ec9acd 100644
--- a/chromeos/ash/components/network/network_util.cc
+++ b/chromeos/ash/components/network/network_util.cc
@@ -236,7 +236,7 @@
       ->managed_network_configuration_handler()
       ->FindPolicyByGUID(user_id_hash, network->guid(), &onc_source);
 
-  base::Value onc_dictionary = TranslateShillServiceToONCPart(
+  base::Value onc_dictionary = onc::TranslateShillServiceToONCPart(
       shill_dictionary, onc_source, &onc::kNetworkWithStateSignature, network);
 
   // Remove IPAddressConfigType/NameServersConfigType as these were
diff --git a/chromeos/ash/components/network/network_util.h b/chromeos/ash/components/network/network_util.h
index 3c1a2cee..17be181 100644
--- a/chromeos/ash/components/network/network_util.h
+++ b/chromeos/ash/components/network/network_util.h
@@ -157,12 +157,6 @@
 
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos {
-using ::ash::CellTower;
-using ::ash::CellTowerVector;
-using ::ash::CellularScanResult;
-using ::ash::CellularSIMSlotInfo;
-using ::ash::WifiAccessPoint;
-using ::ash::WifiAccessPointVector;
 namespace network_util = ::ash::network_util;
 }  // namespace chromeos
 
diff --git a/chromeos/ash/components/network/onc/network_onc_utils.h b/chromeos/ash/components/network/onc/network_onc_utils.h
index 448754d..04c455f8 100644
--- a/chromeos/ash/components/network/onc/network_onc_utils.h
+++ b/chromeos/ash/components/network/onc/network_onc_utils.h
@@ -99,8 +99,6 @@
 
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos::onc {
-using ::ash::onc::ConvertOncProxySettingsToProxyConfig;
-using ::ash::onc::GetPolicyForNetwork;
 using ::ash::onc::ImportNetworksForUser;
 using ::ash::onc::NetworkTypePatternFromOncType;
 }  // namespace chromeos::onc
diff --git a/chromeos/ash/components/network/onc/onc_certificate_pattern.h b/chromeos/ash/components/network/onc/onc_certificate_pattern.h
index dc6809d0..44d1ee1 100644
--- a/chromeos/ash/components/network/onc/onc_certificate_pattern.h
+++ b/chromeos/ash/components/network/onc/onc_certificate_pattern.h
@@ -75,9 +75,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::OncCertificatePattern;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_ONC_ONC_CERTIFICATE_PATTERN_H_
diff --git a/chromeos/ash/components/network/onc/onc_merger.h b/chromeos/ash/components/network/onc/onc_merger.h
index 9080565..0542040 100644
--- a/chromeos/ash/components/network/onc/onc_merger.h
+++ b/chromeos/ash/components/network/onc/onc_merger.h
@@ -49,10 +49,4 @@
 
 }  // namespace ash::onc
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos::onc {
-using ::ash::onc::MergeSettingsAndPoliciesToAugmented;
-using ::ash::onc::MergeSettingsAndPoliciesToEffective;
-}  // namespace chromeos::onc
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_ONC_ONC_MERGER_H_
diff --git a/chromeos/ash/components/network/onc/onc_normalizer.h b/chromeos/ash/components/network/onc/onc_normalizer.h
index c507811..0a247b7c 100644
--- a/chromeos/ash/components/network/onc/onc_normalizer.h
+++ b/chromeos/ash/components/network/onc/onc_normalizer.h
@@ -56,9 +56,4 @@
 
 }  // namespace ash::onc
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos::onc {
-using ::ash::onc::Normalizer;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_ONC_ONC_NORMALIZER_H_
diff --git a/chromeos/ash/components/network/onc/onc_translation_tables.h b/chromeos/ash/components/network/onc/onc_translation_tables.h
index abbca77..a1eff4eb 100644
--- a/chromeos/ash/components/network/onc/onc_translation_tables.h
+++ b/chromeos/ash/components/network/onc/onc_translation_tables.h
@@ -99,12 +99,9 @@
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos::onc {
 using ::ash::onc::kNetworkTechnologyTable;
-using ::ash::onc::kNetworkTypeTable;
 using ::ash::onc::kVPNTypeTable;
-using ::ash::onc::kWiFiSecurityTable;
 using ::ash::onc::StringTranslationEntry;
 using ::ash::onc::TranslateStringToONC;
-using ::ash::onc::TranslateStringToShill;
 }  // namespace chromeos::onc
 
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_ONC_ONC_TRANSLATION_TABLES_H_
diff --git a/chromeos/ash/components/network/onc/onc_translator.h b/chromeos/ash/components/network/onc/onc_translator.h
index 1aece04..d0cf719 100644
--- a/chromeos/ash/components/network/onc/onc_translator.h
+++ b/chromeos/ash/components/network/onc/onc_translator.h
@@ -53,10 +53,4 @@
 }  // namespace onc
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos::onc {
-using ::ash::onc::TranslateONCObjectToShill;
-using ::ash::onc::TranslateShillServiceToONCPart;
-}  // namespace chromeos::onc
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_ONC_ONC_TRANSLATOR_H_
diff --git a/chromeos/ash/components/network/portal_detector/network_portal_detector.h b/chromeos/ash/components/network/portal_detector/network_portal_detector.h
index a3067ad..65f340a 100644
--- a/chromeos/ash/components/network/portal_detector/network_portal_detector.h
+++ b/chromeos/ash/components/network/portal_detector/network_portal_detector.h
@@ -133,7 +133,10 @@
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos {
 using ::ash::NetworkPortalDetector;
-namespace network_portal_detector = ::ash::network_portal_detector;
+namespace network_portal_detector {
+using ::ash::network_portal_detector::GetInstance;
+using ::ash::network_portal_detector::InitializeForTesting;
+}  // namespace network_portal_detector
 }  // namespace chromeos
 
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_PORTAL_DETECTOR_NETWORK_PORTAL_DETECTOR_H_
diff --git a/chromeos/ash/components/network/shill_property_handler.h b/chromeos/ash/components/network/shill_property_handler.h
index 7398261..02b4849 100644
--- a/chromeos/ash/components/network/shill_property_handler.h
+++ b/chromeos/ash/components/network/shill_property_handler.h
@@ -275,9 +275,4 @@
 
 }  // namespace ash::internal
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos::internal {
-using ::ash::internal::ShillPropertyHandler;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_SHILL_PROPERTY_HANDLER_H_
diff --git a/chromeos/ash/components/network/shill_property_util.cc b/chromeos/ash/components/network/shill_property_util.cc
index 05d628d..a6709bc 100644
--- a/chromeos/ash/components/network/shill_property_util.cc
+++ b/chromeos/ash/components/network/shill_property_util.cc
@@ -229,7 +229,7 @@
   // If the feature flag is not enabled, we set each MAC Address Policy
   // to Hardware (non-randomized).
   if (!base::FeatureList::IsEnabled(
-          chromeos::features::kWifiConnectMacAddressRandomization)) {
+          features::kWifiConnectMacAddressRandomization)) {
     shill_dictionary->SetKey(shill::kWifiRandomMACPolicy,
                              base::Value(shill::kWifiRandomMacPolicyHardware));
     return;
diff --git a/chromeos/ash/components/network/shill_property_util.h b/chromeos/ash/components/network/shill_property_util.h
index bc028ac..26ae1adb 100644
--- a/chromeos/ash/components/network/shill_property_util.h
+++ b/chromeos/ash/components/network/shill_property_util.h
@@ -98,9 +98,4 @@
 }  // namespace shill_property_util
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-namespace shill_property_util = ::ash::shill_property_util;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_SHILL_PROPERTY_UTIL_H_
diff --git a/chromeos/ash/components/network/system_token_cert_db_storage_test_util.h b/chromeos/ash/components/network/system_token_cert_db_storage_test_util.h
index 75338e4..eb345a4 100644
--- a/chromeos/ash/components/network/system_token_cert_db_storage_test_util.h
+++ b/chromeos/ash/components/network/system_token_cert_db_storage_test_util.h
@@ -71,10 +71,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::FakeSystemTokenCertDbStorageObserver;
-using ::ash::GetSystemTokenCertDbCallbackWrapper;
-}  // namespace chromeos
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_SYSTEM_TOKEN_CERT_DB_STORAGE_TEST_UTIL_H_
diff --git a/chromeos/ash/components/network/test_cellular_esim_profile_handler.h b/chromeos/ash/components/network/test_cellular_esim_profile_handler.h
index 62ecc1e..e87065a 100644
--- a/chromeos/ash/components/network/test_cellular_esim_profile_handler.h
+++ b/chromeos/ash/components/network/test_cellular_esim_profile_handler.h
@@ -49,9 +49,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::TestCellularESimProfileHandler;
-}
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_TEST_CELLULAR_ESIM_PROFILE_HANDLER_H_
diff --git a/chromeos/ash/components/network/tether_constants.h b/chromeos/ash/components/network/tether_constants.h
index afab557..dd7fbed 100644
--- a/chromeos/ash/components/network/tether_constants.h
+++ b/chromeos/ash/components/network/tether_constants.h
@@ -32,15 +32,4 @@
 
 }  // namespace ash
 
-// TODO(https://crbug.com/1164001): remove when the migration is finished.
-namespace chromeos {
-using ::ash::kTetherBatteryPercentage;
-using ::ash::kTetherCarrier;
-using ::ash::kTetherDeviceName;
-using ::ash::kTetherDevicePath;
-using ::ash::kTetherHasConnectedToHost;
-using ::ash::kTetherSignalStrength;
-using ::ash::kTypeTether;
-}  // namespace chromeos
-
 #endif  // CHROMEOS_ASH_COMPONENTS_NETWORK_TETHER_CONSTANTS_H_
diff --git a/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.cc b/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.cc
index 961d9ce2..9b02cad 100644
--- a/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.cc
+++ b/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.cc
@@ -19,15 +19,20 @@
 
 }  // namespace
 
-TrashInfoParser::TrashInfoParser(
-    base::OnceCallback<void()> disconnect_callback) {
+TrashInfoParser::TrashInfoParser() {
   auto trash_pending_remote = LaunchTrashService();
   service_ = mojo::Remote<mojom::TrashService>(std::move(trash_pending_remote));
-  service_.set_disconnect_handler(std::move(disconnect_callback));
 }
 
 TrashInfoParser::~TrashInfoParser() = default;
 
+void TrashInfoParser::SetDisconnectHandler(
+    base::OnceCallback<void()> disconnect_callback) {
+  if (service_) {
+    service_.set_disconnect_handler(std::move(disconnect_callback));
+  }
+}
+
 void TrashInfoParser::ParseTrashInfoFile(const base::FilePath& path,
                                          ParseTrashInfoCallback callback) {
   if (!service_) {
diff --git a/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h b/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h
index fe740252..b31ed83 100644
--- a/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h
+++ b/chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h
@@ -17,7 +17,7 @@
 // A class that manages the lifetime and mojo connection of the Trash service.
 class TrashInfoParser {
  public:
-  explicit TrashInfoParser(base::OnceCallback<void()> disconnect_callback);
+  TrashInfoParser();
   ~TrashInfoParser();
 
   TrashInfoParser(const TrashInfoParser&) = delete;
@@ -26,6 +26,8 @@
   void ParseTrashInfoFile(const base::FilePath& path,
                           ParseTrashInfoCallback callback);
 
+  void SetDisconnectHandler(base::OnceCallback<void()> disconnect_callback);
+
  private:
   void OnGotFile(ParseTrashInfoCallback callback, base::File file);
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 1f658cf0..f40e4f5 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -540,6 +540,7 @@
     deps += [
       "//components/commerce/core:commerce_heuristics_data_unittests",
       "//components/commerce/core:feature_list_unittests",
+      "//components/commerce/core/subscriptions:subscriptions_unit_tests",
     ]
   }
 
diff --git a/components/browser_ui/media/android/BUILD.gn b/components/browser_ui/media/android/BUILD.gn
index dc2781c..3ea9e3d 100644
--- a/components/browser_ui/media/android/BUILD.gn
+++ b/components/browser_ui/media/android/BUILD.gn
@@ -40,34 +40,59 @@
   sources = [
     "java/res/drawable-hdpi/audio_playing.png",
     "java/res/drawable-hdpi/audio_playing_square.png",
+    "java/res/drawable-hdpi/ic_call_end_white_36dp.png",
     "java/res/drawable-hdpi/ic_fast_forward_white_36dp.png",
     "java/res/drawable-hdpi/ic_fast_rewind_white_36dp.png",
+    "java/res/drawable-hdpi/ic_mic_off_white_36dp.png",
+    "java/res/drawable-hdpi/ic_mic_white_36dp.png",
     "java/res/drawable-hdpi/ic_skip_next_white_36dp.png",
     "java/res/drawable-hdpi/ic_skip_previous_white_36dp.png",
+    "java/res/drawable-hdpi/ic_videocam_off_white_36dp.png",
+    "java/res/drawable-hdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-mdpi/audio_playing.png",
     "java/res/drawable-mdpi/audio_playing_square.png",
+    "java/res/drawable-mdpi/ic_call_end_white_36dp.png",
     "java/res/drawable-mdpi/ic_fast_forward_white_36dp.png",
     "java/res/drawable-mdpi/ic_fast_rewind_white_36dp.png",
+    "java/res/drawable-mdpi/ic_mic_off_white_36dp.png",
+    "java/res/drawable-mdpi/ic_mic_white_36dp.png",
     "java/res/drawable-mdpi/ic_skip_next_white_36dp.png",
     "java/res/drawable-mdpi/ic_skip_previous_white_36dp.png",
+    "java/res/drawable-mdpi/ic_videocam_off_white_36dp.png",
+    "java/res/drawable-mdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xhdpi/audio_playing.png",
     "java/res/drawable-xhdpi/audio_playing_square.png",
+    "java/res/drawable-xhdpi/ic_call_end_white_36dp.png",
     "java/res/drawable-xhdpi/ic_fast_forward_white_36dp.png",
     "java/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png",
+    "java/res/drawable-xhdpi/ic_mic_off_white_36dp.png",
+    "java/res/drawable-xhdpi/ic_mic_white_36dp.png",
     "java/res/drawable-xhdpi/ic_skip_next_white_36dp.png",
     "java/res/drawable-xhdpi/ic_skip_previous_white_36dp.png",
+    "java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png",
+    "java/res/drawable-xhdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xxhdpi/audio_playing.png",
     "java/res/drawable-xxhdpi/audio_playing_square.png",
+    "java/res/drawable-xxhdpi/ic_call_end_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png",
+    "java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png",
+    "java/res/drawable-xxhdpi/ic_mic_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_skip_next_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png",
+    "java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png",
+    "java/res/drawable-xxhdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xxxhdpi/audio_playing.png",
     "java/res/drawable-xxxhdpi/audio_playing_square.png",
+    "java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_fast_forward_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_fast_rewind_white_36dp.png",
+    "java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png",
+    "java/res/drawable-xxxhdpi/ic_mic_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png",
+    "java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png",
+    "java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png",
   ]
   deps = [
     "//components/browser_ui/strings/android:browser_ui_strings_grd",
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png
new file mode 100644
index 0000000..90773818
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png
new file mode 100644
index 0000000..c0e773b
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png
new file mode 100644
index 0000000..2b377a74
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png
new file mode 100644
index 0000000..fafc3a3
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png
new file mode 100644
index 0000000..5c99f19
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png
new file mode 100644
index 0000000..8fb6ffd
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png
new file mode 100644
index 0000000..153d979f
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png
new file mode 100644
index 0000000..d3d9dc2b
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png
new file mode 100644
index 0000000..b09d4dd3
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png
new file mode 100644
index 0000000..f4e905c
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png
new file mode 100644
index 0000000..ff84f1f
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png
new file mode 100644
index 0000000..89ec023
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png
new file mode 100644
index 0000000..d79f5bb
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png
new file mode 100644
index 0000000..b305b70
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png
new file mode 100644
index 0000000..646115b
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png
new file mode 100644
index 0000000..3213989
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png
new file mode 100644
index 0000000..03cb6a6
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png
new file mode 100644
index 0000000..fc3b9246
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png
new file mode 100644
index 0000000..54378c0
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png
new file mode 100644
index 0000000..60f37bc
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png
new file mode 100644
index 0000000..ad9f949
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png
new file mode 100644
index 0000000..533c60e
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png
new file mode 100644
index 0000000..5ec10394
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png
new file mode 100644
index 0000000..59bc5fe065
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png
new file mode 100644
index 0000000..3372697
--- /dev/null
+++ b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png
Binary files differ
diff --git a/components/browser_ui/strings/android/browser_ui_strings.grd b/components/browser_ui/strings/android/browser_ui_strings.grd
index 8936c88..69eacf0 100644
--- a/components/browser_ui/strings/android/browser_ui_strings.grd
+++ b/components/browser_ui/strings/android/browser_ui_strings.grd
@@ -712,6 +712,21 @@
       <message name="IDS_ACCESSIBILITY_SEEK_BACKWARD" desc="The seek backward button that seeks media to an earlier position.">
         Seek backward
       </message>
+      <message name="IDS_ACCESSIBILITY_HANG_UP" desc="The hang up button that hangs up the video call.">
+        Hang up
+      </message>
+      <message name="IDS_ACCESSIBILITY_MUTE_MICROPHONE" desc="The mute microphone button that mutes the microphone of the video call.">
+        Mute microphone
+      </message>
+      <message name="IDS_ACCESSIBILITY_UNMUTE_MICROPHONE" desc="The unmute microphone button that unmutes the microphone of the video call.">
+        Unmute microphone
+      </message>
+      <message name="IDS_ACCESSIBILITY_TURN_ON_CAMERA" desc="The turn on camera button that turns on the camera of the video call.">
+        Turn on camera
+      </message>
+      <message name="IDS_ACCESSIBILITY_TURN_OFF_CAMERA" desc="The turn off camera button that turns off the camera of the video call.">
+        Turn off camera
+      </message>
       <message name="IDS_MEDIA_NOTIFICATION_INCOGNITO" desc="Text used as a placeholder for a media notification about playing media, when notification is shown from Incognito tab.">
         A site is playing media
       </message>
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_HANG_UP.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_HANG_UP.png.sha1
new file mode 100644
index 0000000..7550f63
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_HANG_UP.png.sha1
@@ -0,0 +1 @@
+bc00ecb19f1574c5b10d6384f8a199cc363a0ce0
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_MUTE_MICROPHONE.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_MUTE_MICROPHONE.png.sha1
new file mode 100644
index 0000000..7550f63
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_MUTE_MICROPHONE.png.sha1
@@ -0,0 +1 @@
+bc00ecb19f1574c5b10d6384f8a199cc363a0ce0
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_OFF_CAMERA.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_OFF_CAMERA.png.sha1
new file mode 100644
index 0000000..7550f63
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_OFF_CAMERA.png.sha1
@@ -0,0 +1 @@
+bc00ecb19f1574c5b10d6384f8a199cc363a0ce0
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_ON_CAMERA.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_ON_CAMERA.png.sha1
new file mode 100644
index 0000000..721f26830
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_TURN_ON_CAMERA.png.sha1
@@ -0,0 +1 @@
+830c38bed54a5c2b1783c150806cd5b13320d381
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_UNMUTE_MICROPHONE.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_UNMUTE_MICROPHONE.png.sha1
new file mode 100644
index 0000000..721f26830
--- /dev/null
+++ b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_ACCESSIBILITY_UNMUTE_MICROPHONE.png.sha1
@@ -0,0 +1 @@
+830c38bed54a5c2b1783c150806cd5b13320d381
\ No newline at end of file
diff --git a/components/captive_portal/core/captive_portal_detector.cc b/components/captive_portal/core/captive_portal_detector.cc
index edf30b78..a931ee8d 100644
--- a/components/captive_portal/core/captive_portal_detector.cc
+++ b/components/captive_portal/core/captive_portal_detector.cc
@@ -27,11 +27,10 @@
 namespace {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 GURL GetProbeUrl(const GURL& default_url) {
-  DCHECK_EQ(chromeos::NetworkHandler::Get()->task_runner(),
+  DCHECK_EQ(ash::NetworkHandler::Get()->task_runner(),
             base::ThreadTaskRunnerHandle::Get().get());
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
-                                         ->network_state_handler()
-                                         ->DefaultNetwork();
+  const ash::NetworkState* network =
+      ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
   return network && !network->probe_url().is_empty() ? network->probe_url()
                                                      : default_url;
 }
@@ -69,9 +68,9 @@
   detection_callback_ = std::move(detection_callback);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (chromeos::NetworkHandler::IsInitialized()) {
+  if (ash::NetworkHandler::IsInitialized()) {
     base::PostTaskAndReplyWithResult(
-        chromeos::NetworkHandler::Get()->task_runner(), FROM_HERE,
+        ash::NetworkHandler::Get()->task_runner(), FROM_HERE,
         base::BindOnce(&GetProbeUrl, url),
         base::BindOnce(&CaptivePortalDetector::StartProbe,
                        weak_factory_.GetWeakPtr(), traffic_annotation));
diff --git a/components/commerce/core/subscriptions/BUILD.gn b/components/commerce/core/subscriptions/BUILD.gn
index 3253156..b22a320 100644
--- a/components/commerce/core/subscriptions/BUILD.gn
+++ b/components/commerce/core/subscriptions/BUILD.gn
@@ -24,3 +24,20 @@
     "//services/network/public/cpp:cpp",
   ]
 }
+
+source_set("subscriptions_unit_tests") {
+  testonly = true
+
+  sources = [ "subscriptions_manager_unittest.cc" ]
+
+  deps = [
+    ":subscriptions",
+    "//base/test:test_support",
+    "//components/commerce/core:feature_list",
+    "//components/signin/public/identity_manager:test_support",
+    "//services/network/public/cpp:cpp",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//url:url",
+  ]
+}
diff --git a/components/commerce/core/subscriptions/subscriptions_manager.cc b/components/commerce/core/subscriptions/subscriptions_manager.cc
index 1912ab0c..3080571 100644
--- a/components/commerce/core/subscriptions/subscriptions_manager.cc
+++ b/components/commerce/core/subscriptions/subscriptions_manager.cc
@@ -17,16 +17,27 @@
 SubscriptionsManager::SubscriptionsManager(
     signin::IdentityManager* identity_manager,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
-    : weak_ptr_factory_(this) {
-  server_proxy_ = std::make_unique<SubscriptionsServerProxy>(
-      identity_manager, std::move(url_loader_factory));
-  storage_ = std::make_unique<SubscriptionsStorage>();
+    : SubscriptionsManager(identity_manager,
+                           std::make_unique<SubscriptionsServerProxy>(
+                               identity_manager,
+                               std::move(url_loader_factory)),
+                           std::make_unique<SubscriptionsStorage>()) {}
+
+SubscriptionsManager::SubscriptionsManager(
+    signin::IdentityManager* identity_manager,
+    std::unique_ptr<SubscriptionsServerProxy> server_proxy,
+    std::unique_ptr<SubscriptionsStorage> storage)
+    : server_proxy_(std::move(server_proxy)),
+      storage_(std::move(storage)),
+      weak_ptr_factory_(this) {
 // Avoid duplicate server calls on android. Remove this after we integrate
 // android implementation to shopping service.
 #if !BUILDFLAG(IS_ANDROID)
   InitSubscriptions();
+  scoped_identity_manager_observation_.Observe(identity_manager);
 #endif  // !BUILDFLAG(IS_ANDROID)
 }
+
 SubscriptionsManager::~SubscriptionsManager() = default;
 
 SubscriptionsManager::Request::Request(SubscriptionType type,
@@ -84,6 +95,7 @@
 
 void SubscriptionsManager::InitSubscriptions() {
   init_succeeded_ = false;
+  storage_->DeleteAll();
   if (base::FeatureList::IsEnabled(commerce::kShoppingList)) {
     pending_requests_.push(Request(
         SubscriptionType::kPriceTrack, AsyncOperation::kInit,
@@ -195,4 +207,22 @@
   }
 }
 
+void SubscriptionsManager::OnPrimaryAccountChanged(
+    const signin::PrimaryAccountChangeEvent& event_details) {
+  InitSubscriptions();
+}
+
+bool SubscriptionsManager::GetInitSucceededForTesting() {
+  return init_succeeded_;
+}
+
+void SubscriptionsManager::SetHasRequestRunningForTesting(
+    bool has_request_running) {
+  has_request_running_ = has_request_running;
+}
+
+bool SubscriptionsManager::HasPendingRequestsForTesting() {
+  return !pending_requests_.empty();
+}
+
 }  // namespace commerce
diff --git a/components/commerce/core/subscriptions/subscriptions_manager.h b/components/commerce/core/subscriptions/subscriptions_manager.h
index 32388be..853a44f 100644
--- a/components/commerce/core/subscriptions/subscriptions_manager.h
+++ b/components/commerce/core/subscriptions/subscriptions_manager.h
@@ -12,15 +12,14 @@
 #include "base/callback.h"
 #include "base/check.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/scoped_observation.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/primary_account_change_event.h"
 
 namespace network {
 class SharedURLLoaderFactory;
 }  // namespace network
 
-namespace signin {
-class IdentityManager;
-}  // namespace signin
-
 namespace commerce {
 
 class SubscriptionsServerProxy;
@@ -28,14 +27,19 @@
 enum class SubscriptionType;
 struct CommerceSubscription;
 
-class SubscriptionsManager {
+class SubscriptionsManager : public signin::IdentityManager::Observer {
  public:
   SubscriptionsManager(
       signin::IdentityManager* identity_manager,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+  // Used for tests. The passed in objects are ordinarily created with
+  // parameters from the non-test constructor.
+  SubscriptionsManager(signin::IdentityManager* identity_manager,
+                       std::unique_ptr<SubscriptionsServerProxy> server_proxy,
+                       std::unique_ptr<SubscriptionsStorage> storage);
   SubscriptionsManager(const SubscriptionsManager&) = delete;
   SubscriptionsManager& operator=(const SubscriptionsManager&) = delete;
-  ~SubscriptionsManager();
+  ~SubscriptionsManager() override;
 
   void Subscribe(
       std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
@@ -45,6 +49,15 @@
       std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
       base::OnceCallback<void(bool)> callback);
 
+  // For tests only, return init_succeeded_.
+  bool GetInitSucceededForTesting();
+
+  // For tests only, set has_request_running_.
+  void SetHasRequestRunningForTesting(bool has_request_running);
+
+  // For tests only, return whether there are any pending requests.
+  bool HasPendingRequestsForTesting();
+
  private:
   enum class AsyncOperation {
     kInit = 0,
@@ -99,6 +112,9 @@
       base::OnceCallback<void(bool)> callback,
       bool succeeded);
 
+  void OnPrimaryAccountChanged(
+      const signin::PrimaryAccountChangeEvent& event_details) override;
+
   // Hold coming requests until previous ones have finished to avoid race
   // conditions.
   std::queue<Request> pending_requests_;
@@ -110,6 +126,10 @@
   // Whether there is any request running.
   bool has_request_running_ = false;
 
+  base::ScopedObservation<signin::IdentityManager,
+                          signin::IdentityManager::Observer>
+      scoped_identity_manager_observation_{this};
+
   std::unique_ptr<SubscriptionsServerProxy> server_proxy_;
 
   std::unique_ptr<SubscriptionsStorage> storage_;
diff --git a/components/commerce/core/subscriptions/subscriptions_manager_unittest.cc b/components/commerce/core/subscriptions/subscriptions_manager_unittest.cc
new file mode 100644
index 0000000..2a26603
--- /dev/null
+++ b/components/commerce/core/subscriptions/subscriptions_manager_unittest.cc
@@ -0,0 +1,510 @@
+// 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 <queue>
+#include <string>
+#include <unordered_map>
+
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/subscriptions/commerce_subscription.h"
+#include "components/commerce/core/subscriptions/subscriptions_manager.h"
+#include "components/commerce/core/subscriptions/subscriptions_server_proxy.h"
+#include "components/commerce/core/subscriptions/subscriptions_storage.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::InSequence;
+
+namespace {
+
+// Build a subscription list consisting of only one subscription.
+std::unique_ptr<std::vector<commerce::CommerceSubscription>> BuildSubscriptions(
+    std::string subscription_id) {
+  auto subscriptions =
+      std::make_unique<std::vector<commerce::CommerceSubscription>>();
+  subscriptions->push_back(commerce::CommerceSubscription(
+      commerce::SubscriptionType::kPriceTrack,
+      commerce::IdentifierType::kProductClusterId, subscription_id,
+      commerce::ManagementType::kUserManaged));
+  return subscriptions;
+}
+
+// Check whether the passing subscription list contains exactly one subscription
+// with |expected_id|.
+MATCHER_P(AreExpectedSubscriptions, expected_id, "") {
+  return (*arg).size() == 1 && (*arg)[0].id == expected_id;
+}
+
+}  // namespace
+
+namespace commerce {
+
+class MockSubscriptionsServerProxy : public SubscriptionsServerProxy {
+ public:
+  MockSubscriptionsServerProxy() : SubscriptionsServerProxy(nullptr, nullptr) {}
+  MockSubscriptionsServerProxy(const MockSubscriptionsServerProxy&) = delete;
+  MockSubscriptionsServerProxy operator=(const MockSubscriptionsServerProxy&) =
+      delete;
+  ~MockSubscriptionsServerProxy() override = default;
+
+  MOCK_METHOD(void,
+              Create,
+              (std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+               ManageSubscriptionsFetcherCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              Delete,
+              (std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+               ManageSubscriptionsFetcherCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              Get,
+              (SubscriptionType type, GetSubscriptionsFetcherCallback callback),
+              (override));
+
+  // Mock the server responses for Create and Delete requests.
+  void MockManageResponses(bool succeeded) {
+    ON_CALL(*this, Create)
+        .WillByDefault(
+            [succeeded](std::unique_ptr<std::vector<CommerceSubscription>>
+                            subscriptions,
+                        ManageSubscriptionsFetcherCallback callback) {
+              std::move(callback).Run(succeeded);
+            });
+    ON_CALL(*this, Delete)
+        .WillByDefault(
+            [succeeded](std::unique_ptr<std::vector<CommerceSubscription>>
+                            subscriptions,
+                        ManageSubscriptionsFetcherCallback callback) {
+              std::move(callback).Run(succeeded);
+            });
+  }
+
+  // Mock the server fetch responses for Get requests. |subscription_id| is used
+  // to generate a CommerceSubscription to be returned.
+  void MockGetResponses(std::string subscription_id) {
+    ON_CALL(*this, Get)
+        .WillByDefault(
+            [subscription_id](SubscriptionType type,
+                              GetSubscriptionsFetcherCallback callback) {
+              std::move(callback).Run(BuildSubscriptions(subscription_id));
+            });
+  }
+};
+
+class MockSubscriptionsStorage : public SubscriptionsStorage {
+ public:
+  MockSubscriptionsStorage() = default;
+  MockSubscriptionsStorage(const MockSubscriptionsStorage&) = delete;
+  MockSubscriptionsStorage operator=(const MockSubscriptionsStorage&) = delete;
+  ~MockSubscriptionsStorage() override = default;
+
+  MOCK_METHOD(void,
+              GetUniqueNonExistingSubscriptions,
+              (std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+               GetLocalSubscriptionsCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              GetUniqueExistingSubscriptions,
+              (std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+               GetLocalSubscriptionsCallback callback),
+              (override));
+  MOCK_METHOD(
+      void,
+      UpdateStorage,
+      (SubscriptionType type,
+       base::OnceCallback<void(bool)> callback,
+       std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions),
+      (override));
+  MOCK_METHOD(void, DeleteAll, (), (override));
+
+  // Mock the local fetch responses for Get* requests. |subscription_id| is used
+  // to generate a CommerceSubscription to be returned.
+  void MockGetResponses(std::string subscription_id) {
+    ON_CALL(*this, GetUniqueNonExistingSubscriptions)
+        .WillByDefault(
+            [subscription_id](std::unique_ptr<std::vector<CommerceSubscription>>
+                                  subscriptions,
+                              GetLocalSubscriptionsCallback callback) {
+              std::move(callback).Run(BuildSubscriptions(subscription_id));
+            });
+    ON_CALL(*this, GetUniqueExistingSubscriptions)
+        .WillByDefault(
+            [subscription_id](std::unique_ptr<std::vector<CommerceSubscription>>
+                                  subscriptions,
+                              GetLocalSubscriptionsCallback callback) {
+              std::move(callback).Run(BuildSubscriptions(subscription_id));
+            });
+  }
+
+  // Mock the responses for UpdateStorage requests.
+  void MockUpdateResponses(bool succeeded) {
+    ON_CALL(*this, UpdateStorage)
+        .WillByDefault(
+            [succeeded](SubscriptionType type,
+                        base::OnceCallback<void(bool)> callback,
+                        std::unique_ptr<std::vector<CommerceSubscription>>
+                            remote_subscriptions) {
+              std::move(callback).Run(succeeded);
+            });
+  }
+};
+
+class SubscriptionsManagerTest : public testing::Test {
+ public:
+  SubscriptionsManagerTest()
+      : mock_server_proxy_(std::make_unique<MockSubscriptionsServerProxy>()),
+        mock_storage_(std::make_unique<MockSubscriptionsStorage>()) {
+    test_features_.InitAndEnableFeature(commerce::kShoppingList);
+  }
+  ~SubscriptionsManagerTest() override = default;
+
+  void CreateManagerAndVerify(bool init_succeeded) {
+    subscriptions_manager_ = std::make_unique<SubscriptionsManager>(
+        identity_test_env_.identity_manager(), std::move(mock_server_proxy_),
+        std::move(mock_storage_));
+    ASSERT_EQ(init_succeeded,
+              subscriptions_manager_->GetInitSucceededForTesting());
+  }
+
+  void MockHasRequestRunning(bool has_request_running) {
+    subscriptions_manager_->SetHasRequestRunningForTesting(has_request_running);
+  }
+
+  void VerifyHasPendingRequests(bool has_pending_requests) {
+    ASSERT_EQ(has_pending_requests,
+              subscriptions_manager_->HasPendingRequestsForTesting());
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  signin::IdentityTestEnvironment identity_test_env_;
+  base::test::ScopedFeatureList test_features_;
+  std::unique_ptr<MockSubscriptionsServerProxy> mock_server_proxy_;
+  std::unique_ptr<MockSubscriptionsStorage> mock_storage_;
+  std::unique_ptr<SubscriptionsManager> subscriptions_manager_;
+};
+
+TEST_F(SubscriptionsManagerTest, TestInitSucceeded) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_storage_->MockUpdateResponses(true);
+  EXPECT_CALL(*mock_storage_, DeleteAll).Times(1);
+  EXPECT_CALL(*mock_server_proxy_, Get).Times(1);
+  EXPECT_CALL(*mock_storage_,
+              UpdateStorage(_, _, AreExpectedSubscriptions("111")))
+      .Times(1);
+
+  CreateManagerAndVerify(true);
+}
+
+TEST_F(SubscriptionsManagerTest, TestInitFailed) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_storage_->MockUpdateResponses(false);
+  EXPECT_CALL(*mock_storage_, DeleteAll).Times(1);
+  EXPECT_CALL(*mock_server_proxy_, Get).Times(1);
+  EXPECT_CALL(*mock_storage_,
+              UpdateStorage(_, _, AreExpectedSubscriptions("111")))
+      .Times(1);
+
+  CreateManagerAndVerify(false);
+}
+
+TEST_F(SubscriptionsManagerTest, TestSubscribe) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueNonExistingSubscriptions(
+                                    AreExpectedSubscriptions("333"), _));
+    EXPECT_CALL(*mock_server_proxy_,
+                Create(AreExpectedSubscriptions("222"), _));
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+  }
+
+  CreateManagerAndVerify(true);
+  bool callback_executed = false;
+  subscriptions_manager_->Subscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(true, succeeded);
+            *callback_executed = true;
+          },
+          &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestSubscribe_ServerManageFailed) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(false);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueNonExistingSubscriptions(
+                                    AreExpectedSubscriptions("333"), _));
+    EXPECT_CALL(*mock_server_proxy_,
+                Create(AreExpectedSubscriptions("222"), _));
+    EXPECT_CALL(*mock_server_proxy_, Get).Times(0);
+    EXPECT_CALL(*mock_storage_, UpdateStorage).Times(0);
+  }
+
+  CreateManagerAndVerify(true);
+  bool callback_executed = false;
+  subscriptions_manager_->Subscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(false, succeeded);
+            *callback_executed = true;
+          },
+          &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestSubscribe_InitFailed) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(false);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueNonExistingSubscriptions).Times(0);
+  }
+
+  CreateManagerAndVerify(false);
+  bool callback_executed = false;
+  subscriptions_manager_->Subscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(false, succeeded);
+            *callback_executed = true;
+          },
+          &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestSubscribe_HasRequestRunning) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueNonExistingSubscriptions).Times(0);
+  }
+
+  CreateManagerAndVerify(true);
+  MockHasRequestRunning(true);
+  bool callback_executed = false;
+  subscriptions_manager_->Subscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce([](bool* callback_executed,
+                        bool succeeded) { *callback_executed = true; },
+                     &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(false, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestSubscribe_HasPendingUnsubscribeRequest) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    // Init calls.
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    // Unsubscribe calls.
+    EXPECT_CALL(*mock_storage_, GetUniqueExistingSubscriptions(
+                                    AreExpectedSubscriptions("333"), _));
+    EXPECT_CALL(*mock_server_proxy_,
+                Delete(AreExpectedSubscriptions("222"), _));
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    // Subscribe calls.
+    EXPECT_CALL(*mock_storage_, GetUniqueNonExistingSubscriptions(
+                                    AreExpectedSubscriptions("444"), _));
+    EXPECT_CALL(*mock_server_proxy_,
+                Create(AreExpectedSubscriptions("222"), _));
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+  }
+
+  CreateManagerAndVerify(true);
+  VerifyHasPendingRequests(false);
+  // First, we set has_request_running_ as true to hold on coming Unsubscribe
+  // request.
+  MockHasRequestRunning(true);
+  bool unsubscribe_callback_executed = false;
+  subscriptions_manager_->Unsubscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(true, succeeded);
+            *callback_executed = true;
+          },
+          &unsubscribe_callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  // This request won't be processed and will be held in pending_requests_.
+  ASSERT_EQ(false, unsubscribe_callback_executed);
+  VerifyHasPendingRequests(true);
+
+  // Next, we set has_request_running_ as false and Subscribe. The previous
+  // Unsubscribe request should be processed first and once it finishes, this
+  // Subscribe request will be processed automatically.
+  MockHasRequestRunning(false);
+  bool subscribe_callback_executed = false;
+  subscriptions_manager_->Subscribe(
+      BuildSubscriptions("444"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(true, succeeded);
+            *callback_executed = true;
+          },
+          &subscribe_callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, unsubscribe_callback_executed);
+  ASSERT_EQ(true, subscribe_callback_executed);
+  VerifyHasPendingRequests(false);
+}
+
+TEST_F(SubscriptionsManagerTest, TestUnsubscribe) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueExistingSubscriptions(
+                                    AreExpectedSubscriptions("333"), _));
+    EXPECT_CALL(*mock_server_proxy_,
+                Delete(AreExpectedSubscriptions("222"), _));
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+  }
+
+  CreateManagerAndVerify(true);
+  bool callback_executed = false;
+  subscriptions_manager_->Unsubscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(true, succeeded);
+            *callback_executed = true;
+          },
+          &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestUnsubscribe_InitFailed) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_server_proxy_->MockManageResponses(true);
+  mock_storage_->MockGetResponses("222");
+  mock_storage_->MockUpdateResponses(false);
+
+  {
+    InSequence s;
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    EXPECT_CALL(*mock_storage_, GetUniqueExistingSubscriptions).Times(0);
+  }
+
+  CreateManagerAndVerify(false);
+  bool callback_executed = false;
+  subscriptions_manager_->Unsubscribe(
+      BuildSubscriptions("333"),
+      base::BindOnce(
+          [](bool* callback_executed, bool succeeded) {
+            ASSERT_EQ(false, succeeded);
+            *callback_executed = true;
+          },
+          &callback_executed));
+  // Use a RunLoop in case the callback is posted on a different thread.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(true, callback_executed);
+}
+
+TEST_F(SubscriptionsManagerTest, TestIdentityChange) {
+  mock_server_proxy_->MockGetResponses("111");
+  mock_storage_->MockUpdateResponses(true);
+
+  {
+    InSequence s;
+    // First init on manager instantiation.
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+    // Second init on primary account change.
+    EXPECT_CALL(*mock_storage_, DeleteAll);
+    EXPECT_CALL(*mock_server_proxy_, Get);
+    EXPECT_CALL(*mock_storage_,
+                UpdateStorage(_, _, AreExpectedSubscriptions("111")));
+  }
+
+  CreateManagerAndVerify(true);
+  identity_test_env_.MakePrimaryAccountAvailable("mock_email@gmail.com",
+                                                 signin::ConsentLevel::kSync);
+}
+
+}  // namespace commerce
diff --git a/components/commerce/core/subscriptions/subscriptions_server_proxy.h b/components/commerce/core/subscriptions/subscriptions_server_proxy.h
index 5563dec..1643037 100644
--- a/components/commerce/core/subscriptions/subscriptions_server_proxy.h
+++ b/components/commerce/core/subscriptions/subscriptions_server_proxy.h
@@ -42,18 +42,21 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
   SubscriptionsServerProxy(const SubscriptionsServerProxy&) = delete;
   SubscriptionsServerProxy& operator=(const SubscriptionsServerProxy&) = delete;
-  ~SubscriptionsServerProxy();
+  virtual ~SubscriptionsServerProxy();
 
   // Make an HTTPS call to backend to create the new |subscriptions|.
-  void Create(std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
-              ManageSubscriptionsFetcherCallback callback);
+  virtual void Create(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      ManageSubscriptionsFetcherCallback callback);
 
   // Make an HTTPS call to backend to delete the existing |subscriptions|.
-  void Delete(std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
-              ManageSubscriptionsFetcherCallback callback);
+  virtual void Delete(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      ManageSubscriptionsFetcherCallback callback);
 
   // Make an HTTP call to backend to get all subscriptions for specified type.
-  void Get(SubscriptionType type, GetSubscriptionsFetcherCallback callback);
+  virtual void Get(SubscriptionType type,
+                   GetSubscriptionsFetcherCallback callback);
 
  private:
   std::unique_ptr<EndpointFetcher> CreateEndpointFetcher(
diff --git a/components/commerce/core/subscriptions/subscriptions_storage.cc b/components/commerce/core/subscriptions/subscriptions_storage.cc
index 52d144c..494e9788 100644
--- a/components/commerce/core/subscriptions/subscriptions_storage.cc
+++ b/components/commerce/core/subscriptions/subscriptions_storage.cc
@@ -31,4 +31,6 @@
     base::OnceCallback<void(bool)> callback,
     std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions) {}
 
+void SubscriptionsStorage::DeleteAll() {}
+
 }  // namespace commerce
diff --git a/components/commerce/core/subscriptions/subscriptions_storage.h b/components/commerce/core/subscriptions/subscriptions_storage.h
index 22fafbc..8d997e1 100644
--- a/components/commerce/core/subscriptions/subscriptions_storage.h
+++ b/components/commerce/core/subscriptions/subscriptions_storage.h
@@ -25,28 +25,31 @@
   SubscriptionsStorage();
   SubscriptionsStorage(const SubscriptionsStorage&) = delete;
   SubscriptionsStorage& operator=(const SubscriptionsStorage&) = delete;
-  ~SubscriptionsStorage();
+  virtual ~SubscriptionsStorage();
 
   // Compare the provided subscriptions against local cache and return unique
   // subscriptions that are not in local cache. This is used for subscribe
   // operation.
-  void GetUniqueNonExistingSubscriptions(
+  virtual void GetUniqueNonExistingSubscriptions(
       std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
       GetLocalSubscriptionsCallback callback);
 
   // Compare the provided subscriptions against local cache and return unique
   // subscriptions that are already in local cache. This is used for unsubscribe
   // operation.
-  void GetUniqueExistingSubscriptions(
+  virtual void GetUniqueExistingSubscriptions(
       std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
       GetLocalSubscriptionsCallback callback);
 
   // Update local cache to keep consistency with |remote_subscriptions| and
   // notify |callback| if it completes successfully.
-  void UpdateStorage(
+  virtual void UpdateStorage(
       SubscriptionType type,
       base::OnceCallback<void(bool)> callback,
       std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions);
+
+  // Delete all local subscriptions.
+  virtual void DeleteAll();
 };
 
 }  // namespace commerce
diff --git a/components/contextual_search/core/browser/contextual_search_delegate.cc b/components/contextual_search/core/browser/contextual_search_delegate.cc
index 9ee41758..f59065ab 100644
--- a/components/contextual_search/core/browser/contextual_search_delegate.cc
+++ b/components/contextual_search/core/browser/contextual_search_delegate.cc
@@ -103,67 +103,65 @@
 // Handles tasks for the ContextualSearchManager in a separable, testable way.
 ContextualSearchDelegate::ContextualSearchDelegate(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    TemplateURLService* template_url_service,
-    ContextualSearchDelegate::SearchTermResolutionCallback search_term_callback,
-    ContextualSearchDelegate::SurroundingTextCallback surrounding_text_callback)
+    TemplateURLService* template_url_service)
     : url_loader_factory_(std::move(url_loader_factory)),
       template_url_service_(template_url_service),
-      field_trial_(std::make_unique<ContextualSearchFieldTrial>()),
-      search_term_callback_(std::move(search_term_callback)),
-      surrounding_text_callback_(std::move(surrounding_text_callback)) {}
+      field_trial_(std::make_unique<ContextualSearchFieldTrial>()) {}
 
 ContextualSearchDelegate::~ContextualSearchDelegate() = default;
 
 void ContextualSearchDelegate::GatherAndSaveSurroundingText(
-    base::WeakPtr<ContextualSearchContext> contextual_search_context,
-    content::WebContents* web_contents) {
+    base::WeakPtr<ContextualSearchContext> context,
+    content::WebContents* web_contents,
+    SurroundingTextCallback callback) {
   DCHECK(web_contents);
-  blink::mojom::LocalFrame::GetTextSurroundingSelectionCallback callback =
-      base::BindOnce(
+  blink::mojom::LocalFrame::GetTextSurroundingSelectionCallback
+      get_text_callback = base::BindOnce(
           &ContextualSearchDelegate::OnTextSurroundingSelectionAvailable,
-          AsWeakPtr());
-  context_ = contextual_search_context;
-  if (context_ == nullptr)
+          AsWeakPtr(), context, callback);
+  if (!context)
     return;
 
-  context_->SetBasePageEncoding(web_contents->GetEncoding());
-  int surroundingTextSize = context_->CanResolve()
+  context->SetBasePageEncoding(web_contents->GetEncoding());
+  int surroundingTextSize = context->CanResolve()
                                 ? field_trial_->GetResolveSurroundingSize()
                                 : field_trial_->GetSampleSurroundingSize();
   RenderFrameHost* focused_frame = web_contents->GetFocusedFrame();
   if (focused_frame) {
-    focused_frame->RequestTextSurroundingSelection(std::move(callback),
+    focused_frame->RequestTextSurroundingSelection(std::move(get_text_callback),
                                                    surroundingTextSize);
   } else {
-    std::move(callback).Run(std::u16string(), 0, 0);
+    std::move(get_text_callback).Run(std::u16string(), 0, 0);
   }
 }
 
 void ContextualSearchDelegate::StartSearchTermResolutionRequest(
-    base::WeakPtr<ContextualSearchContext> contextual_search_context,
-    content::WebContents* web_contents) {
+    base::WeakPtr<ContextualSearchContext> context,
+    content::WebContents* web_contents,
+    SearchTermResolutionCallback callback) {
   DCHECK(web_contents);
-  if (context_ == nullptr)
+  if (!context)
     return;
 
-  DCHECK(context_.get() == contextual_search_context.get());
-  DCHECK(context_->CanResolve());
+  DCHECK(context->CanResolve());
 
   // Immediately cancel any request that's in flight, since we're building a new
   // context (and the response disposes of any existing context).
   url_loader_.reset();
 
   // Decide if the URL should be sent with the context.
-  if (context_->CanSendBasePageUrl())
-    context_->SetBasePageUrl(web_contents->GetLastCommittedURL());
+  if (context->CanSendBasePageUrl())
+    context->SetBasePageUrl(web_contents->GetLastCommittedURL());
 
   // Issue the resolve request.
-  ResolveSearchTermFromContext();
+  ResolveSearchTermFromContext(context, std::move(callback));
 }
 
-void ContextualSearchDelegate::ResolveSearchTermFromContext() {
-  DCHECK(context_ != nullptr);
-  GURL request_url(BuildRequestUrl(context_.get()));
+void ContextualSearchDelegate::ResolveSearchTermFromContext(
+    base::WeakPtr<ContextualSearchContext> context,
+    SearchTermResolutionCallback callback) {
+  DCHECK(context);
+  GURL request_url(BuildRequestUrl(context.get()));
   DCHECK(request_url.is_valid());
 
   auto resource_request = std::make_unique<network::ResourceRequest>();
@@ -171,7 +169,7 @@
 
   // Populates the discourse context and adds it to the HTTP header of the
   // search term resolution request.
-  resource_request->headers.CopyFrom(GetDiscourseContext(*context_));
+  resource_request->headers.CopyFrom(GetDiscourseContext(*context));
 
   // Disable cookies for this request.
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
@@ -219,12 +217,14 @@
   url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       url_loader_factory_.get(),
       base::BindOnce(&ContextualSearchDelegate::OnUrlLoadComplete,
-                     base::Unretained(this)));
+                     base::Unretained(this), context, std::move(callback)));
 }
 
 void ContextualSearchDelegate::OnUrlLoadComplete(
+    base::WeakPtr<ContextualSearchContext> context,
+    SearchTermResolutionCallback callback,
     std::unique_ptr<std::string> response_body) {
-  if (!context_)
+  if (!context)
     return;
 
   int response_code = ResolvedSearchTerm::kResponseCodeUninitialized;
@@ -236,16 +236,16 @@
       std::make_unique<ResolvedSearchTerm>(response_code);
   if (response_body && response_code == net::HTTP_OK) {
     resolved_search_term =
-        GetResolvedSearchTermFromJson(response_code, *response_body);
+        GetResolvedSearchTermFromJson(*context, response_code, *response_body);
   }
-  search_term_callback_.Run(*resolved_search_term);
+  callback.Run(*resolved_search_term);
 }
 
 std::unique_ptr<ResolvedSearchTerm>
 ContextualSearchDelegate::GetResolvedSearchTermFromJson(
+    const ContextualSearchContext& context,
     int response_code,
     const std::string& json_string) {
-  DCHECK(context_ != nullptr);
   std::string search_term;
   std::string display_text;
   std::string alternate_term;
@@ -278,13 +278,13 @@
     // the new and old selection.
     if (mention_start >= mention_end ||
         (mention_end - mention_start) > kContextualSearchMaxSelection ||
-        mention_end <= context_->GetStartOffset() ||
-        mention_start >= context_->GetEndOffset()) {
+        mention_end <= context.GetStartOffset() ||
+        mention_start >= context.GetEndOffset()) {
       start_adjust = 0;
       end_adjust = 0;
     } else {
-      start_adjust = mention_start - context_->GetStartOffset();
-      end_adjust = mention_end - context_->GetEndOffset();
+      start_adjust = mention_start - context.GetStartOffset();
+      end_adjust = mention_end - context.GetEndOffset();
     }
   }
   bool is_invalid =
@@ -325,7 +325,7 @@
   }
 
   int mainFunctionVersion = kContextualSearchRequestVersion;
-  if (context_->GetRelatedSearches())
+  if (context->GetRelatedSearches())
     mainFunctionVersion = kRelatedSearchesVersion;
 
   TemplateURLRef::SearchTermsArgs::ContextualSearchParams params(
@@ -358,16 +358,18 @@
 }
 
 void ContextualSearchDelegate::OnTextSurroundingSelectionAvailable(
+    base::WeakPtr<ContextualSearchContext> context,
+    SurroundingTextCallback callback,
     const std::u16string& surrounding_text,
     uint32_t start_offset,
     uint32_t end_offset) {
-  if (context_ == nullptr)
+  if (!context)
     return;
 
   // Sometimes the surroundings are 0, 0, '', so run the callback with empty
   // data in that case. See https://crbug.com/393100.
   if (start_offset == 0 && end_offset == 0 && surrounding_text.length() == 0) {
-    surrounding_text_callback_.Run(std::string(), std::u16string(), 0, 0);
+    callback.Run(std::string(), std::u16string(), 0, 0);
     return;
   }
 
@@ -378,8 +380,7 @@
   start_offset = std::min(surrounding_length, start_offset);
   end_offset = std::min(surrounding_length, end_offset);
 
-  context_->SetSelectionSurroundings(start_offset, end_offset,
-                                     surrounding_text);
+  context->SetSelectionSurroundings(start_offset, end_offset, surrounding_text);
 
   // Call the Java surrounding callback with a shortened copy of the
   // surroundings to use as a sample of the surrounding text.
@@ -393,9 +394,8 @@
       SampleSurroundingText(surrounding_text, sample_padding_each_side,
                             &selection_start, &selection_end);
   DCHECK(selection_start <= selection_end);
-  surrounding_text_callback_.Run(context_->GetBasePageEncoding(),
-                                 sample_surrounding_text, selection_start,
-                                 selection_end);
+  callback.Run(context->GetBasePageEncoding(), sample_surrounding_text,
+               selection_start, selection_end);
 }
 
 // Decodes the given response from the search term resolution request and sets
@@ -531,7 +531,7 @@
 void ContextualSearchDelegate::ExtractMentionsStartEnd(
     const base::Value::List& mentions_list,
     int* start_result,
-    int* end_result) {
+    int* end_result) const {
   if (mentions_list.size() >= 1 && mentions_list[0].is_int())
     *start_result = std::max(0, mentions_list[0].GetInt());
   if (mentions_list.size() >= 2 && mentions_list[1].is_int())
@@ -542,7 +542,7 @@
     const std::u16string& surrounding_text,
     int padding_each_side,
     size_t* start,
-    size_t* end) {
+    size_t* end) const {
   std::u16string result_text = surrounding_text;
   size_t start_offset = *start;
   size_t end_offset = *end;
diff --git a/components/contextual_search/core/browser/contextual_search_delegate.h b/components/contextual_search/core/browser/contextual_search_delegate.h
index 6cbdfc7..a13b252 100644
--- a/components/contextual_search/core/browser/contextual_search_delegate.h
+++ b/components/contextual_search/core/browser/contextual_search_delegate.h
@@ -44,32 +44,32 @@
       void(const std::string&, const std::u16string&, size_t, size_t)>
       SurroundingTextCallback;
 
-  // Constructs a delegate that will always call back to the given callbacks
-  // when search term resolution or surrounding text responses are available.
+  // Constructs a delegate that uses the given url_loader_factory and
+  // template_url_service for all contextual search requests.
   ContextualSearchDelegate(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      TemplateURLService* template_url_service,
-      SearchTermResolutionCallback search_term_callback,
-      SurroundingTextCallback surrounding_callback);
+      TemplateURLService* template_url_service);
 
   ContextualSearchDelegate(const ContextualSearchDelegate&) = delete;
   ContextualSearchDelegate& operator=(const ContextualSearchDelegate&) = delete;
 
   virtual ~ContextualSearchDelegate();
 
-  // Gathers surrounding text and saves it locally in the given context.
+  // Gathers surrounding text and saves it in the given context. The given
+  // callback will be run when the surrounding text becomes available.
   void GatherAndSaveSurroundingText(
       base::WeakPtr<ContextualSearchContext> contextual_search_context,
-      content::WebContents* web_contents);
+      content::WebContents* web_contents,
+      SurroundingTextCallback callback);
 
   // Starts an asynchronous search term resolution request.
-  // The given context includes some content from a web page and must be able
+  // The given context may include some content from a web page and must be able
   // to resolve.
-  // When the response is available the callback specified in the constructor
-  // is run.
+  // When the response is available the given callback will be run.
   void StartSearchTermResolutionRequest(
       base::WeakPtr<ContextualSearchContext> contextual_search_context,
-      content::WebContents* web_contents);
+      content::WebContents* web_contents,
+      SearchTermResolutionCallback callback);
 
  private:
   // Friend our test which allows our private methods to be used in helper
@@ -95,23 +95,31 @@
   FRIEND_TEST_ALL_PREFIXES(ContextualSearchDelegateTest,
                            DecodeSearchTermFromJsonResponse);
 
-  void OnUrlLoadComplete(std::unique_ptr<std::string> response_body);
-
   // Resolves the search term specified by the current context.
-  // Only needed for tests.  TODO(donnd): make private and friend?
-  void ResolveSearchTermFromContext();
+  void ResolveSearchTermFromContext(
+      base::WeakPtr<ContextualSearchContext> context,
+      SearchTermResolutionCallback callback);
+
+  // Handles the contextual search response included in |response_body|. Calls
+  // |callback| with the resulting ResolvedSearchTerm.
+  void OnUrlLoadComplete(base::WeakPtr<ContextualSearchContext> context,
+                         SearchTermResolutionCallback callback,
+                         std::unique_ptr<std::string> response_body);
 
   // Builds and returns the search term resolution request URL.
   // |context| is used to help build the query.
   std::string BuildRequestUrl(ContextualSearchContext* context);
 
   void OnTextSurroundingSelectionAvailable(
+      base::WeakPtr<ContextualSearchContext> context,
+      SurroundingTextCallback callback,
       const std::u16string& surrounding_text,
       uint32_t start_offset,
       uint32_t end_offset);
 
   // Builds a Resolved Search Term by decoding the given JSON string.
   std::unique_ptr<ResolvedSearchTerm> GetResolvedSearchTermFromJson(
+      const ContextualSearchContext& context,
       int response_code,
       const std::string& json_string);
 
@@ -140,7 +148,7 @@
   // |mentions_list| must be a list.
   void ExtractMentionsStartEnd(const base::Value::List& mentions_list,
                                int* start_result,
-                               int* end_result);
+                               int* end_result) const;
 
   // Generates a subset of the given surrounding_text string, for usage from
   // Java.
@@ -157,13 +165,7 @@
   std::u16string SampleSurroundingText(const std::u16string& surrounding_text,
                                        int padding_each_side,
                                        size_t* start,
-                                       size_t* end);
-
-  // For testing.
-  void SetContextForTesting(
-      const base::WeakPtr<ContextualSearchContext>& context) {
-    context_ = context;
-  }
+                                       size_t* end) const;
 
   // The current request in progress, or NULL.
   std::unique_ptr<network::SimpleURLLoader> url_loader_;
@@ -176,16 +178,6 @@
 
   // The field trial helper instance, always set up by the constructor.
   std::unique_ptr<ContextualSearchFieldTrial> field_trial_;
-
-  // The callback for notifications of completed URL fetches.
-  SearchTermResolutionCallback search_term_callback_;
-
-  // The callback for notifications of surrounding text being available.
-  SurroundingTextCallback surrounding_text_callback_;
-
-  // Used to hold the context until an upcoming search term request is started.
-  // Owned by the Java ContextualSearchContext.
-  base::WeakPtr<ContextualSearchContext> context_;
 };
 
 #endif  // COMPONENTS_CONTEXTUAL_SEARCH_CORE_BROWSER_CONTEXTUAL_SEARCH_DELEGATE_H_
diff --git a/components/contextual_search/core/browser/contextual_search_delegate_unittest.cc b/components/contextual_search/core/browser/contextual_search_delegate_unittest.cc
index 914d00b..476fc86 100644
--- a/components/contextual_search/core/browser/contextual_search_delegate_unittest.cc
+++ b/components/contextual_search/core/browser/contextual_search_delegate_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/strings/escape.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
@@ -73,13 +74,9 @@
             &test_url_loader_factory_);
     template_url_service_.reset(CreateTemplateURLService());
     delegate_ = std::make_unique<ContextualSearchDelegate>(
-        test_shared_url_loader_factory_, template_url_service_.get(),
-        base::BindRepeating(
-            &ContextualSearchDelegateTest::recordSearchTermResolutionResponse,
-            base::Unretained(this)),
-        base::BindRepeating(
-            &ContextualSearchDelegateTest::recordSampleSelectionAvailable,
-            base::Unretained(this)));
+        test_shared_url_loader_factory_, template_url_service_.get());
+
+    received_search_term_resolution_response_ = false;
   }
 
   void TearDown() override {
@@ -117,12 +114,13 @@
       int end_offset) {
     test_context_ = std::make_unique<WeakContextualSearchContext>(
         std::string(), GURL(kSomeSpecificBasePage), "utf-8");
-    // ContextualSearchDelegate class takes ownership of the context.
-    delegate_->SetContextForTesting(test_context_->GetWeakPtr());
-
     test_context_->SetSelectionSurroundings(start_offset, end_offset,
                                             surrounding_text);
-    delegate_->ResolveSearchTermFromContext();
+    delegate_->ResolveSearchTermFromContext(
+        test_context_->GetWeakPtr(),
+        base::BindRepeating(
+            &ContextualSearchDelegateTest::recordSearchTermResolutionResponse,
+            base::Unretained(this)));
     ASSERT_TRUE(test_url_loader_factory_.GetPendingRequest(0));
   }
 
@@ -154,7 +152,6 @@
   void CreateTestContext() {
     test_context_ = std::make_unique<WeakContextualSearchContext>(
         std::string(), GURL(kSomeSpecificBasePage), "utf-8");
-    delegate_->SetContextForTesting(test_context_->GetWeakPtr());
   }
 
   void DestroyTestContext() { test_context_.reset(); }
@@ -162,12 +159,23 @@
   // Call the OnTextSurroundingSelectionAvailable.
   // Cannot be in an actual test because OnTextSurroundingSelectionAvailable
   // is private.
-  void CallOnTextSurroundingSelectionAvailable() {
-    delegate_->OnTextSurroundingSelectionAvailable(std::u16string(), 1, 2);
+  void CallOnTextSurroundingSelectionAvailable(
+      base::WeakPtr<ContextualSearchContext> context) {
+    delegate_->OnTextSurroundingSelectionAvailable(
+        context,
+        base::BindRepeating(
+            &ContextualSearchDelegateTest::recordSampleSelectionAvailable,
+            base::Unretained(this)),
+        std::u16string(), 1, 2);
   }
 
-  void CallResolveSearchTermFromContext() {
-    delegate_->ResolveSearchTermFromContext();
+  void CallResolveSearchTermFromContext(
+      base::WeakPtr<ContextualSearchContext> context) {
+    delegate_->ResolveSearchTermFromContext(
+        context,
+        base::BindRepeating(
+            &ContextualSearchDelegateTest::recordSearchTermResolutionResponse,
+            base::Unretained(this)));
   }
 
   void SetResponseStringAndSimulateResponse(const std::string& selected_text,
@@ -198,7 +206,6 @@
         std::string(), GURL(kSomeSpecificBasePage), "utf-8");
     test_context_->SetSelectionSurroundings(start_offset, end_offset,
                                             surrounding_text);
-    delegate_->SetContextForTesting(test_context_->GetWeakPtr());
   }
 
   // Gets the Client Discourse Context proto from the request header.
@@ -258,14 +265,21 @@
   int coca_card_tag() { return coca_card_tag_; }
   std::string related_searches_json() { return related_searches_json_; }
 
+  bool received_search_term_resolution_response() {
+    return received_search_term_resolution_response_;
+  }
+
   // The delegate under test.
   std::unique_ptr<ContextualSearchDelegate> delegate_;
+  std::unique_ptr<WeakContextualSearchContext> test_context_;
 
   network::TestURLLoaderFactory test_url_loader_factory_;
 
  private:
   void recordSearchTermResolutionResponse(
       const ResolvedSearchTerm& resolved_search_term) {
+    received_search_term_resolution_response_ = true;
+
     is_invalid_ = resolved_search_term.is_invalid;
     response_code_ = resolved_search_term.response_code;
     search_term_ = resolved_search_term.search_term;
@@ -313,6 +327,9 @@
   int coca_card_tag_;
   std::string related_searches_json_;
 
+  // Tracks whether a response was received.
+  bool received_search_term_resolution_response_;
+
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
@@ -321,8 +338,6 @@
   scoped_refptr<network::SharedURLLoaderFactory>
       test_shared_url_loader_factory_;
 
-  std::unique_ptr<WeakContextualSearchContext> test_context_;
-
   // Features to enable
   base::test::ScopedFeatureList feature_list_;
 };
@@ -674,23 +689,24 @@
 }
 
 // Test that we can destroy the context while resolving without a crash.
-// Test is flaky: https://crbug.com/890427
-TEST_F(ContextualSearchDelegateTest, DISABLED_DestroyContextDuringResolve) {
+TEST_F(ContextualSearchDelegateTest, DestroyContextDuringResolve) {
   CreateTestContext();
-  CallResolveSearchTermFromContext();
+  CallResolveSearchTermFromContext(test_context_->GetWeakPtr());
   DestroyTestContext();
 
   std::string response("Any response as it does not matter here.");
   SimulateResponseReturned(response);
 
-  EXPECT_TRUE(is_invalid());
+  EXPECT_FALSE(received_search_term_resolution_response());
 }
 
 // Test that we can destroy the context while gathering surrounding text.
 TEST_F(ContextualSearchDelegateTest, DestroyContextDuringGatherSurroundings) {
   CreateTestContext();
+  base::WeakPtr<ContextualSearchContext> weak_context =
+      test_context_->GetWeakPtr();
   DestroyTestContext();
-  CallOnTextSurroundingSelectionAvailable();
+  CallOnTextSurroundingSelectionAvailable(weak_context);
 }
 
 TEST_F(ContextualSearchDelegateTest, ResponseWithCocaCardTag) {
diff --git a/components/crash/core/app/crashpad.cc b/components/crash/core/app/crashpad.cc
index 0d0a0ed..8c0ae200 100644
--- a/components/crash/core/app/crashpad.cc
+++ b/components/crash/core/app/crashpad.cc
@@ -68,36 +68,6 @@
 
 crashpad::CrashReportDatabase* g_database;
 
-bool LogMessageHandler(int severity,
-                       const char* file,
-                       int line,
-                       size_t message_start,
-                       const std::string& string) {
-  // Only handle FATAL.
-  if (severity != logging::LOG_FATAL) {
-    return false;
-  }
-
-  // In case of an out-of-memory condition, this code could be reentered when
-  // constructing and storing the key. Using a static is not thread-safe, but if
-  // multiple threads are in the process of a fatal crash at the same time, this
-  // should work.
-  static bool guarded = false;
-  if (guarded) {
-    return false;
-  }
-  base::AutoReset<bool> guard(&guarded, true);
-
-  CHECK_LE(message_start, string.size());
-  static crashpad::StringAnnotation<512> crash_key("LOG_FATAL");
-  crash_key.Set(logging::LogMessage::BuildCrashString(
-      file, line, string.c_str() + message_start));
-
-  // Rather than including the code to force the crash here, allow the caller to
-  // do it.
-  return false;
-}
-
 void InitializeDatabasePath(const base::FilePath& database_path) {
   DCHECK(!g_database_path);
 
@@ -198,8 +168,6 @@
   platform.Set(base::SysInfo::HardwareModelName());
 #endif  // !BUILDFLAG(IS_IOS)
 
-  logging::SetLogMessageHandler(LogMessageHandler);
-
   // If clients called CRASHPAD_SIMULATE_CRASH() instead of
   // base::debug::DumpWithoutCrashing(), these dumps would appear as crashes in
   // the correct function, at the correct file and line. This would be
diff --git a/components/device_signals/core/browser/BUILD.gn b/components/device_signals/core/browser/BUILD.gn
index 01a1bd3a..abfd3a2d 100644
--- a/components/device_signals/core/browser/BUILD.gn
+++ b/components/device_signals/core/browser/BUILD.gn
@@ -38,12 +38,18 @@
   deps = [
     "//components/device_signals/core/common",
     "//components/policy/core/common",
+    "//components/prefs",
     "//components/signin/public/identity_manager",
   ]
 
   if (is_win) {
     public_deps += [ "//components/device_signals/core/common/win" ]
   }
+
+  if (is_win || is_linux || is_mac) {
+    public += [ "pref_names.h" ]
+    sources += [ "pref_names.cc" ]
+  }
 }
 
 static_library("test_support") {
diff --git a/components/device_signals/core/browser/DEPS b/components/device_signals/core/browser/DEPS
index 97d436ae..b5db532 100644
--- a/components/device_signals/core/browser/DEPS
+++ b/components/device_signals/core/browser/DEPS
@@ -2,4 +2,5 @@
   "+components/keyed_service/core",
   "+components/signin/public",
   "+components/policy/core",
+  "+components/prefs",
 ]
diff --git a/components/device_signals/core/browser/pref_names.cc b/components/device_signals/core/browser/pref_names.cc
new file mode 100644
index 0000000..d85093ee
--- /dev/null
+++ b/components/device_signals/core/browser/pref_names.cc
@@ -0,0 +1,23 @@
+// 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/device_signals/core/browser/pref_names.h"
+
+#include "components/prefs/pref_registry_simple.h"
+
+namespace device_signals {
+namespace prefs {
+
+// Whether or not admin requires managed users to share device signals when they
+// sign in on an unmanaged device
+const char kUnmanagedDeviceSignalsConsentFlowEnabled[] =
+    "device_signals.consent_collection_enabled";
+
+}  // namespace prefs
+
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(
+      prefs::kUnmanagedDeviceSignalsConsentFlowEnabled, false);
+}
+}  // namespace device_signals
diff --git a/components/device_signals/core/browser/pref_names.h b/components/device_signals/core/browser/pref_names.h
new file mode 100644
index 0000000..ad1ffb22
--- /dev/null
+++ b/components/device_signals/core/browser/pref_names.h
@@ -0,0 +1,19 @@
+// 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_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_
+#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_
+
+class PrefRegistrySimple;
+
+namespace device_signals {
+namespace prefs {
+extern const char kUnmanagedDeviceSignalsConsentFlowEnabled[];
+}  // namespace prefs
+
+// Registers user preferences related to Device Signal Sharing.
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+}  // namespace device_signals
+
+#endif  // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_PREF_NAMES_H_
diff --git a/components/device_signals/core/common/signals_features.cc b/components/device_signals/core/common/signals_features.cc
index 4da5464..cfb98c6 100644
--- a/components/device_signals/core/common/signals_features.cc
+++ b/components/device_signals/core/common/signals_features.cc
@@ -18,6 +18,17 @@
 const base::FeatureParam<bool> kDisableHotfix{&kNewEvSignalsEnabled,
                                               "DisableHotfix", false};
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+// Enables the consent promo for sharing device signal when a managed user
+// signs in on an unmanaged device. This occurs after the sign-in intercept
+// and before the sync promo (if enabled)
+// This feature also requires UnmanagedDeviceSignalsConsentFlowEnabled policy to
+// be enabled
+const base::Feature kDeviceSignalsPromoAfterSigninIntercept{
+    "DeviceSignalsPromoAfterSigninIntercept",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+
 bool IsNewFunctionEnabled(NewEvFunction new_ev_function) {
   if (!base::FeatureList::IsEnabled(kNewEvSignalsEnabled)) {
     return false;
diff --git a/components/device_signals/core/common/signals_features.h b/components/device_signals/core/common/signals_features.h
index 07bf89d4c5..a3dc50a 100644
--- a/components/device_signals/core/common/signals_features.h
+++ b/components/device_signals/core/common/signals_features.h
@@ -20,6 +20,10 @@
 extern const base::FeatureParam<bool> kDisableAntiVirus;
 extern const base::FeatureParam<bool> kDisableHotfix;
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+extern const base::Feature kDeviceSignalsPromoAfterSigninIntercept;
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+
 // Enum used to map a given function to its kill switch.
 enum class NewEvFunction { kFileSystemInfo, kSettings, kAntiVirus, kHotfix };
 
diff --git a/components/exo/wayland/clients/client_base.cc b/components/exo/wayland/clients/client_base.cc
index 37e04abe..ba2e3eb 100644
--- a/components/exo/wayland/clients/client_base.cc
+++ b/components/exo/wayland/clients/client_base.cc
@@ -559,7 +559,7 @@
     gl::GLDisplayEGL* display = static_cast<gl::GLDisplayEGL*>(
         gl::init::InitializeGLOneOff(/*system_device_id=*/0));
     DCHECK(display);
-    gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    gl_surface_ = gl::init::CreateOffscreenGLSurface(display, gfx::Size());
     gl_context_ =
         gl::init::CreateGLContext(nullptr,  // share_group
                                   gl_surface_.get(), gl::GLContextAttribs());
diff --git a/components/exo/wayland/wayland_positioner.cc b/components/exo/wayland/wayland_positioner.cc
index 8c28ce44..4aea1cc 100644
--- a/components/exo/wayland/wayland_positioner.cc
+++ b/components/exo/wayland/wayland_positioner.cc
@@ -263,6 +263,7 @@
                                      /*visibility=*/0},
                                     /*position=*/{0, 0},
                                     /*adjustment=*/ConstraintAdjustment{}};
+  bool found_solution = false;
   for (uint32_t adjustment_bit_field = 0; adjustment_bit_field < 8;
        ++adjustment_bit_field) {
     // When several options tie for visibility, we preference based on the
@@ -302,10 +303,25 @@
     }
 
     if (is_better) {
+      found_solution = true;
       best = IntermediateAdjustmentResult{
           {preferred, constrained, visibility}, position, adjustment};
     }
   }
+
+  // If no solution can be found, allow all transformations. Unfortunately the
+  // default setting is not valid, because it has a 0x0 dimension.
+  if (!found_solution) {
+    ConstraintAdjustment allow_all = {
+        .flip = true,
+        .slide = true,
+        .resize = true,
+    };
+    return DetermineBestConstraintAdjustment(work_area, anchor_range, size,
+                                             offset, anchor, gravity, allow_all,
+                                             avoid_occlusion);
+  }
+
   return {best.position, best.adjustment};
 }
 
diff --git a/components/exo/wayland/wayland_positioner_unittest.cc b/components/exo/wayland/wayland_positioner_unittest.cc
index 0787c96..41a3c21 100644
--- a/components/exo/wayland/wayland_positioner_unittest.cc
+++ b/components/exo/wayland/wayland_positioner_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/exo/wayland/wayland_positioner.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
 #include "xdg-shell-server-protocol.h"
 #include "xdg-shell-unstable-v6-server-protocol.h"
 
@@ -48,6 +49,11 @@
       return *this;
     }
 
+    TestCaseBuilder& SetWorkArea(const gfx::Rect& rect) {
+      work_area = rect;
+      return *this;
+    }
+
     TestCaseBuilder& SetSize(int w, int h) {
       positioner.SetSize({w, h});
       return *this;
@@ -232,6 +238,22 @@
       gfx::Rect(1, 1, 4, 4));
 }
 
+TEST_F(WaylandPositionerTest,
+       AllowsAdditionalAdjustmentsIfNoSolutionCanBeFoundUnstable) {
+  EXPECT_EQ(
+      TestCaseBuilder(WaylandPositioner::Version::UNSTABLE)
+          .SetWorkArea(gfx::Rect(5, 5))
+          .SetSize(10, 10)
+          .SetAnchorRect(0, 0, 0, 0)
+          .SetGravity(ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+                      ZXDG_POSITIONER_V6_GRAVITY_RIGHT)
+          // No solution should forcibly allow resize
+          .SetAdjustment(ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+                         ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
+          .SolveToRect(),
+      gfx::Rect(0, 0, 5, 5));
+}
+
 // Tests for the stable protocol.
 
 TEST_F(WaylandPositionerTest, UnconstrainedCases) {
@@ -401,6 +423,20 @@
             gfx::Rect(0, 2, 1, 3));
 }
 
+TEST_F(WaylandPositionerTest,
+       AllowsAdditionalAdjustmentsIfNoSolutionCanBeFound) {
+  EXPECT_EQ(TestCaseBuilder(WaylandPositioner::Version::STABLE)
+                .SetWorkArea(gfx::Rect(5, 5))
+                .SetSize(10, 10)
+                .SetAnchorRect(0, 0, 0, 0)
+                .SetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
+                // No solution should forcibly allow resize
+                .SetAdjustment(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+                               XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
+                .SolveToRect(),
+            gfx::Rect(0, 0, 5, 5));
+}
+
 }  // namespace
 }  // namespace wayland
 }  // namespace exo
diff --git a/components/global_media_controls/public/media_session_item_producer.cc b/components/global_media_controls/public/media_session_item_producer.cc
index 358ee9d..b4e984a 100644
--- a/components/global_media_controls/public/media_session_item_producer.cc
+++ b/components/global_media_controls/public/media_session_item_producer.cc
@@ -68,6 +68,13 @@
 
 void MediaSessionItemProducer::Session::MediaSessionInfoChanged(
     media_session::mojom::MediaSessionInfoPtr session_info) {
+  if (session_info && session_info->has_presentation) {
+    // The presentation gets its own item, so this item has become redundant.
+    // |this| gets deleted here.
+    owner_->RemoveItem(id_);
+    return;
+  }
+
   is_playing_ =
       session_info && session_info->playback_state ==
                           media_session::mojom::MediaPlaybackState::kPlaying;
diff --git a/components/global_media_controls/public/media_session_item_producer_unittest.cc b/components/global_media_controls/public/media_session_item_producer_unittest.cc
index f475c29c..4bde307 100644
--- a/components/global_media_controls/public/media_session_item_producer_unittest.cc
+++ b/components/global_media_controls/public/media_session_item_producer_unittest.cc
@@ -192,7 +192,17 @@
     session_info->playback_state =
         playing ? media_session::mojom::MediaPlaybackState::kPlaying
                 : media_session::mojom::MediaPlaybackState::kPaused;
+    SimulateMediaSessionInfoChanged(id, std::move(session_info));
+  }
 
+  void SimulateSessionHasPresentation(const base::UnguessableToken& id) {
+    MediaSessionInfoPtr session_info(MediaSessionInfo::New());
+    session_info->has_presentation = true;
+    SimulateMediaSessionInfoChanged(id, std::move(session_info));
+  }
+
+  void SimulateMediaSessionInfoChanged(const base::UnguessableToken& id,
+                                       MediaSessionInfoPtr session_info) {
     auto item_itr = sessions().find(id.ToString());
     EXPECT_NE(sessions().end(), item_itr);
     item_itr->second.MediaSessionInfoChanged(std::move(session_info));
@@ -684,4 +694,13 @@
   EXPECT_FALSE(HasActiveItems());
 }
 
+TEST_F(MediaSessionItemProducerTest, HidesSessionWithPresentation) {
+  const base::UnguessableToken id = SimulatePlayingControllableMedia();
+  EXPECT_TRUE(HasActiveItems());
+  SimulateSessionHasPresentation(id);
+  // The presentation gets its own item, so MediaSessionItemProducer's item has
+  // become redundant and gets hidden.
+  EXPECT_FALSE(HasActiveItems());
+}
+
 }  // namespace global_media_controls
diff --git a/components/history/core/browser/visit_annotations_database.cc b/components/history/core/browser/visit_annotations_database.cc
index aea2f78..ff77a11 100644
--- a/components/history/core/browser/visit_annotations_database.cc
+++ b/components/history/core/browser/visit_annotations_database.cc
@@ -64,16 +64,16 @@
   return base::JoinString(serialized_categories, ",");
 }
 
-// Converts the serialized related searches string into a vector of strings.
-std::vector<std::string> GetRelatedSearchesFromStringColumn(
+// Converts a serialized db string into a vector of strings.
+std::vector<std::string> DeserializeFromStringColumn(
     const std::string& column_value) {
   using std::string_literals::operator""s;
   return base::SplitString(column_value, "\0"s, base::TRIM_WHITESPACE,
                            base::SPLIT_WANT_NONEMPTY);
 }
 
-// Serializes related searches into a string that can be stored in the database.
-std::string ConvertRelatedSearchesToStringColumn(
+// Serializes a vector of strings into a string that can be stored in the db.
+std::string SerializeToStringColumn(
     const std::vector<std::string>& related_searches) {
   // Use the Null character as the separator to serialize the related searches.
   using std::string_literals::operator""s;
@@ -232,8 +232,8 @@
   statement.BindString(
       5, ConvertCategoriesToStringColumn(
              visit_content_annotations.model_annotations.entities));
-  statement.BindString(6, ConvertRelatedSearchesToStringColumn(
-                              visit_content_annotations.related_searches));
+  statement.BindString(
+      6, SerializeToStringColumn(visit_content_annotations.related_searches));
   statement.BindString(7,
                        visit_content_annotations.search_normalized_url.spec());
   statement.BindString16(8, visit_content_annotations.search_terms);
@@ -293,8 +293,8 @@
   statement.BindString(
       4, ConvertCategoriesToStringColumn(
              visit_content_annotations.model_annotations.entities));
-  statement.BindString(5, ConvertRelatedSearchesToStringColumn(
-                              visit_content_annotations.related_searches));
+  statement.BindString(
+      5, SerializeToStringColumn(visit_content_annotations.related_searches));
   statement.BindString(6,
                        visit_content_annotations.search_normalized_url.spec());
   statement.BindString16(7, visit_content_annotations.search_terms);
@@ -361,7 +361,7 @@
   out_content_annotations->model_annotations.entities =
       GetCategoriesFromStringColumn(statement.ColumnString(5));
   out_content_annotations->related_searches =
-      GetRelatedSearchesFromStringColumn(statement.ColumnString(6));
+      DeserializeFromStringColumn(statement.ColumnString(6));
   out_content_annotations->search_normalized_url =
       GURL(statement.ColumnString(7));
   out_content_annotations->search_terms = statement.ColumnString16(8);
@@ -389,15 +389,8 @@
   }
 
   auto cluster_id = GetClusterIdContainingVisit(visit_id);
-  if (cluster_id > 0 && GetVisitIdsInCluster(cluster_id).size() == 1) {
-    statement.Assign(GetDB().GetCachedStatement(
-        SQL_FROM_HERE, "DELETE FROM clusters WHERE cluster_id=?"));
-    statement.BindInt64(0, cluster_id);
-    if (!statement.Run()) {
-      DVLOG(0) << "Failed to execute clusters delete statement:  "
-               << "visit_id = " << visit_id << ", cluster_id = " << cluster_id;
-    }
-  }
+  if (cluster_id > 0 && GetVisitIdsInCluster(cluster_id).size() == 1)
+    DeleteClusters({cluster_id});
 
   statement.Assign(GetDB().GetCachedStatement(
       SQL_FROM_HERE, "DELETE FROM clusters_and_visits WHERE visit_id=?"));
@@ -420,14 +413,16 @@
       "VALUES(?,?,?)"));
   sql::Statement clusters_and_visits_statement(GetDB().GetCachedStatement(
       SQL_FROM_HERE,
-      "INSERT INTO "
-      "clusters_and_visits(cluster_id,visit_id,score,"
-      "engagement_score,url_for_deduping,normalized_url,url_for_display)"
+      "INSERT INTO clusters_and_visits"
+      "(cluster_id,visit_id,score,engagement_score,url_for_deduping,"
+      "normalized_url,url_for_display)"
       "VALUES(?,?,?,?,?,?,?)"));
 
   for (const auto& cluster : clusters) {
     if (cluster.visits.empty())
       continue;
+
+    // Insert the cluster into 'clusters'.
     clusters_statement.Reset(true);
     clusters_statement.BindBool(0,
                                 cluster.should_show_on_prominent_ui_surfaces);
@@ -439,6 +434,8 @@
     }
     const int64_t cluster_id = GetDB().GetLastInsertRowId();
     DCHECK(cluster_id);
+
+    // Insert each visit into 'clusters_and_visits'.
     base::ranges::for_each(cluster.visits, [&](const auto& cluster_visit) {
       const auto visit_id = cluster_visit.annotated_visit.visit_row.visit_id;
       clusters_and_visits_statement.Reset(true);
@@ -611,9 +608,9 @@
 
   // Not all version 43 history has the content_annotations table. So at this
   // point the content_annotations table may already have been initialized with
-  // the latest version with a annotation_flags column.
+  // the latest version with an annotation_flags column.
   if (!GetDB().DoesColumnExist("content_annotations", "annotation_flags")) {
-    // Add a annotation_flags column to the content_annotations table.
+    // Add an annotation_flags column to the content_annotations table.
     if (!GetDB().Execute("ALTER TABLE content_annotations ADD COLUMN "
                          "annotation_flags INTEGER DEFAULT 0 NOT NULL")) {
       return false;
diff --git a/components/lens/OWNERS b/components/lens/OWNERS
index a09d9e7..93aa6fa 100644
--- a/components/lens/OWNERS
+++ b/components/lens/OWNERS
@@ -1,3 +1,4 @@
 benwgold@google.com
 juanmojica@google.com
-yusuyoutube@google.com
\ No newline at end of file
+stanfield@google.com
+yusuyoutube@google.com
diff --git a/components/live_caption/live_caption_controller.cc b/components/live_caption/live_caption_controller.cc
index c2f1f27..f6be626 100644
--- a/components/live_caption/live_caption_controller.cc
+++ b/components/live_caption/live_caption_controller.cc
@@ -163,7 +163,9 @@
   CreateUI();
 }
 
-void LiveCaptionController::OnSodaError(speech::LanguageCode language_code) {
+void LiveCaptionController::OnSodaInstallError(
+    speech::LanguageCode language_code,
+    speech::SodaInstaller::ErrorCode error_code) {
   // Check that language code matches the selected language for Live Caption or
   // is LanguageCode::kNone (signifying the SODA binary failed).
   if (!prefs::IsLanguageCodeForLiveCaption(language_code, profile_prefs_) &&
diff --git a/components/live_caption/live_caption_controller.h b/components/live_caption/live_caption_controller.h
index 42ce5dfd..a596011 100644
--- a/components/live_caption/live_caption_controller.h
+++ b/components/live_caption/live_caption_controller.h
@@ -95,7 +95,8 @@
   void OnSodaInstalled(speech::LanguageCode language_code) override;
   void OnSodaProgress(speech::LanguageCode language_code,
                       int progress) override {}
-  void OnSodaError(speech::LanguageCode language_code) override;
+  void OnSodaInstallError(speech::LanguageCode language_code,
+                          speech::SodaInstaller::ErrorCode error_code) override;
 
   // ui::NativeThemeObserver:
   void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {}
diff --git a/components/media_router/common/mojom/media_router.mojom b/components/media_router/common/mojom/media_router.mojom
index 6eabc09..38fe5be 100644
--- a/components/media_router/common/mojom/media_router.mojom
+++ b/components/media_router/common/mojom/media_router.mojom
@@ -253,9 +253,9 @@
   // may be overridden by a provider implementation. The presentation ID will
   // be used by the presentation API to refer to the created route.
   //
-  // |origin| and |tab_id| may be passed in for enforcing same-origin and/or
-  // same-tab scopes. Use -1 as |tab_id| in cases where the request is not
-  // made on behalf of a tab.
+  // |origin| and |frame_tree_node_id| may be passed in for enforcing
+  // same-origin and/or same-tab scopes. Use -1 as |frame_tree_node_id| in
+  // cases where the request is not made on behalf of a tab.
   //
   // If |timeout| is positive, it will be used in place of the default timeout
   // defined by Media Route Provider Manager.
@@ -270,12 +270,13 @@
   //
   // |result_code| will be set to OK if successful, or an error code if an error
   // occurred.
-  // TODO(btolsch): Consolidate result params into struct.
+  // TODO(crbug.com/1346066): Consolidate parameters into a struct.
+  // TODO(crbug.com/1351184): Make |frame_tree_node_id| optional.
   CreateRoute(string media_source,
               string sink_id,
               string original_presentation_id,
               url.mojom.Origin origin,
-              int32 tab_id,
+              int32 frame_tree_node_id,
               mojo_base.mojom.TimeDelta timeout,
               bool off_the_record) =>
                   (MediaRoute? route,
@@ -286,8 +287,8 @@
   // Requests a connection to an established route for |media_source| given
   // by |presentation_id|.
   //
-  // |origin| and |tab_id| are used for validating same-origin/tab scopes;
-  // see CreateRoute for additional documentation.
+  // |origin| and |frame_tree_node_id| are used for validating same-origin/tab
+  // scopes; see CreateRoute for additional documentation.
   //
   // If |timeout| is positive, it will be used in place of the default timeout
   // defined by Media Route Provider Manager.
@@ -302,10 +303,12 @@
   //
   // |result_code| will be set to OK if successful, or an error code if an error
   // occurred.
+  // TODO(crbug.com/1346066): Consolidate parameters into a struct.
+  // TODO(crbug.com/1351184): Make |frame_tree_node_id| optional.
   JoinRoute(string media_source,
             string presentation_id,
             url.mojom.Origin origin,
-            int32 tab_id,
+            int32 frame_tree_node_id,
             mojo_base.mojom.TimeDelta timeout,
             bool off_the_record) =>
                 (MediaRoute? route,
@@ -447,10 +450,9 @@
   GetLogsAsString() => (string logs);
 
   // Called to get a mirroring.mojom.MirroringServiceHost.
-  GetMirroringServiceHostForTab(int32 target_tab_id,
+ GetMirroringServiceHostForTab(int32 frame_tree_node_id,
       pending_receiver<mirroring.mojom.MirroringServiceHost> receiver);
   GetMirroringServiceHostForDesktop(
-      int32 initiator_tab_id,  // The tab used to register the stream.
       string desktop_stream_id,
       pending_receiver<mirroring.mojom.MirroringServiceHost> receiver);
   GetMirroringServiceHostForOffscreenTab(
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index b74b76b..f6b9545c 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -51,8 +51,6 @@
     "file_metrics_provider.h",
     "form_factor_metrics_provider.cc",
     "form_factor_metrics_provider.h",
-    "histogram_encoder.cc",
-    "histogram_encoder.h",
     "log_decoder.cc",
     "log_decoder.h",
     "log_store.h",
@@ -122,6 +120,7 @@
   ]
 
   deps = [
+    ":library_support",
     "//base",
     "//base:base_static",
     "//build:branding_buildflags",
@@ -267,14 +266,19 @@
   }
 }
 
+# Dependency for histogram manager users: cronet and ios/webview
 source_set("library_support") {
-  sources = [
-    "library_support/histogram_manager.cc",
+  public = [
+    "histogram_encoder.h",
     "library_support/histogram_manager.h",
   ]
+  sources = [
+    "histogram_encoder.cc",
+    "library_support/histogram_manager.cc",
+  ]
+
   deps = [
     "//base",
-    "//components/metrics:metrics",
     "//third_party/metrics_proto",
   ]
 }
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 586ee57..83e94db 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -30476,6 +30476,39 @@
     ''',
     },
     {
+      'name': 'UnmanagedDeviceSignalsConsentFlowEnabled',
+      'owners': ['xzonghan@chromium.org', 'cbe-device-trust-eng@google.com'],
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'future_on': [
+        'chrome.*'
+      ],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable device signal consenting for managed users on unmanaged devices',
+        },
+        {
+          'value': False,
+          'caption': 'Disable device signal consenting for managed users on unmanaged devices',
+        },
+      ],
+      'default': False,
+      'example_value': True,
+      'id': 1001,
+      'caption': '''Ask for consent from managed users to share device signals on unmanaged devices to gain access''',
+      'tags': [],
+      'desc': '''
+      Setting the policy to Enabled (True) lets <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> asks for managed users' consent prior to sharing device signals on unmanaged devices in order to gain access.
+
+      Setting the policy to Disabled (False) or leaving it unset disallows <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> from collecting device signals
+      Examples of device signals include (but are not limited to) OS information, registry, file presesnce.''',
+    },
+    {
       'name': 'LockIconInAddressBarEnabled',
       'owners': ['meacer@chromium.org', 'trusty-transport@chromium.org'],
       'type': 'main',
@@ -33528,6 +33561,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 341, 412, 438, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669, 872],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 1000,
+  'highest_id_currently_used': 1001,
   'highest_atomic_group_id_currently_used': 43
 }
diff --git a/components/power_bookmarks/core/BUILD.gn b/components/power_bookmarks/core/BUILD.gn
index e8ff9fb8..57f07d7 100644
--- a/components/power_bookmarks/core/BUILD.gn
+++ b/components/power_bookmarks/core/BUILD.gn
@@ -10,6 +10,9 @@
 
 static_library("core") {
   sources = [
+    "power_bookmark_data_provider.h",
+    "power_bookmark_service.cc",
+    "power_bookmark_service.h",
     "power_bookmark_utils.cc",
     "power_bookmark_utils.h",
   ]
@@ -21,6 +24,7 @@
     "//base:i18n",
     "//components/bookmarks/browser",
     "//components/commerce/core:proto",
+    "//components/keyed_service/core:core",
     "//ui/base",
     "//url",
   ]
diff --git a/components/power_bookmarks/core/DEPS b/components/power_bookmarks/core/DEPS
index 61e1691d..228f1ea2 100644
--- a/components/power_bookmarks/core/DEPS
+++ b/components/power_bookmarks/core/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+components/bookmarks",
+  "+components/keyed_service",
   "+ui/base",
 ]
\ No newline at end of file
diff --git a/components/power_bookmarks/core/power_bookmark_data_provider.h b/components/power_bookmarks/core/power_bookmark_data_provider.h
new file mode 100644
index 0000000..473e783
--- /dev/null
+++ b/components/power_bookmarks/core/power_bookmark_data_provider.h
@@ -0,0 +1,27 @@
+// 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_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_DATA_PROVIDER_H_
+#define COMPONENTS_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_DATA_PROVIDER_H_
+
+namespace bookmarks {
+class BookmarkNode;
+}
+
+namespace power_bookmarks {
+
+class PowerBookmarkMeta;
+
+class PowerBookmarkDataProvider {
+ public:
+  // Allow features the opportunity to add metadata to `meta` when a bookmark
+  // `node` is created. The `meta` object is attached to and stored with the
+  // bookmark.
+  virtual void AttachMetadataForNewBookmark(const bookmarks::BookmarkNode* node,
+                                            PowerBookmarkMeta* meta) = 0;
+};
+
+}  // namespace power_bookmarks
+
+#endif  // COMPONENTS_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_DATA_PROVIDER_H_
\ No newline at end of file
diff --git a/components/power_bookmarks/core/power_bookmark_service.cc b/components/power_bookmarks/core/power_bookmark_service.cc
new file mode 100644
index 0000000..7b9b35a
--- /dev/null
+++ b/components/power_bookmarks/core/power_bookmark_service.cc
@@ -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.
+
+#include "components/power_bookmarks/core/power_bookmark_service.h"
+
+namespace power_bookmarks {
+
+PowerBookmarkService::PowerBookmarkService() = default;
+PowerBookmarkService::~PowerBookmarkService() = default;
+
+void PowerBookmarkService::AddDataProvider(
+    PowerBookmarkDataProvider* data_provider) {
+  data_providers_.emplace_back(data_provider);
+}
+
+void PowerBookmarkService::RemoveDataProvider(
+    PowerBookmarkDataProvider* data_provider) {
+  auto it =
+      std::find(data_providers_.begin(), data_providers_.end(), data_provider);
+  if (it != data_providers_.end())
+    data_providers_.erase(it);
+}
+
+}  // namespace power_bookmarks
\ No newline at end of file
diff --git a/components/power_bookmarks/core/power_bookmark_service.h b/components/power_bookmarks/core/power_bookmark_service.h
new file mode 100644
index 0000000..277e571
--- /dev/null
+++ b/components/power_bookmarks/core/power_bookmark_service.h
@@ -0,0 +1,32 @@
+// 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_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_SERVICE_H_
+#define COMPONENTS_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_SERVICE_H_
+
+#include <vector>
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/power_bookmarks/core/power_bookmark_data_provider.h"
+
+namespace power_bookmarks {
+
+class PowerBookmarkService : public KeyedService {
+ public:
+  PowerBookmarkService();
+  ~PowerBookmarkService() override;
+
+  // Allow features to receive notification when a bookmark node is created to
+  // add extra information. The `data_provider` can be removed with the remove
+  // method.
+  void AddDataProvider(PowerBookmarkDataProvider* data_provider);
+  void RemoveDataProvider(PowerBookmarkDataProvider* data_provider);
+
+ private:
+  std::vector<PowerBookmarkDataProvider*> data_providers_;
+};
+
+}  // namespace power_bookmarks
+
+#endif  // COMPONENTS_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_SERVICE_H_
\ No newline at end of file
diff --git a/components/reporting/README.md b/components/reporting/README.md
index d0a577f..2a34368 100644
--- a/components/reporting/README.md
+++ b/components/reporting/README.md
@@ -23,9 +23,22 @@
 1. Run `autoninja -C out/Default components_unittests` to build the components
    unit test executable.
 
-1. Then, run `out/Default/components_unittests --gtest_filter='<target tests>'` to
-   run relevant tests. Here, `<target tests>` is a wildcard pattern (refer to
+1. Then, run `out/Default/components_unittests --gtest_filter='<target tests>'`
+   to run relevant tests. Here, `<target tests>` is a wildcard pattern (refer to
    the document of gtest for more details). For example, to run all tests for
    `StorageQueue`, run
 
        $ out/Default/components_unittests --gtest_filter='*/StorageQueueTest.*'
+
+   For another example, to run all tests in this directory, run
+
+       $ tools/autotest.py -C out/Default --run_all components/reporting
+
+   You can also append a filter such as `--gtest_filter='*/StorageQueueTest.*'`
+   to the line above.
+
+   Another useful flag for dealing with flaky tests is `--gtest_repeat=`, which
+   repeats tests for multiple times.
+
+   For more gtest features, check out
+   [the gtest document](https://google.github.io/googletest/advanced.html).
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index d255a46..34d54f0 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -1823,11 +1823,15 @@
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
   WriteStringOrDie(kData[0]);
 
+  const auto original_total_memory = options_.memory_resource()->GetTotal();
+
   // Set uploader expectations.
   test::TestCallbackAutoWaiter waiter;
   EXPECT_CALL(set_mock_uploader_expectations_,
               Call(Eq(UploaderInterface::UploadReason::PERIODIC)))
       .WillOnce(Invoke([&waiter, this](UploaderInterface::UploadReason reason) {
+        // Update total memory to a low amount.
+        options_.memory_resource()->Test_SetTotal(100u);
         return TestUploader::SetUp(&waiter, this)
             .Complete(Status(error::RESOURCE_EXHAUSTED,
                              "Insufficient memory for upload"));
@@ -1835,21 +1839,19 @@
       .RetiresOnSaturation();
   EXPECT_CALL(set_mock_uploader_expectations_,
               Call(Eq(UploaderInterface::UploadReason::FAILURE_RETRY)))
-      .WillOnce(Invoke([&waiter, this](UploaderInterface::UploadReason reason) {
+      .WillOnce(Invoke([&waiter, &original_total_memory,
+                        this](UploaderInterface::UploadReason reason) {
+        // Reset after running upload so it does not affect other tests.
+        options_.memory_resource()->Test_SetTotal(original_total_memory);
         return TestUploader::SetUp(&waiter, this)
             .Required(0, kData[0])
             .Complete();
       }))
       .RetiresOnSaturation();
 
-  // Update total memory to a low amount.
-  const auto original_total_memory = options_.memory_resource()->GetTotal();
-  options_.memory_resource()->Test_SetTotal(100);
-  // Trigger upload.
+  // Trigger upload which will experience insufficient memory.
   task_environment_.FastForwardBy(base::Seconds(1));
-  // Reset after running upload so it does not affect other tests.
-  options_.memory_resource()->Test_SetTotal(original_total_memory);
-  // Trigger another upload.
+  // Trigger another upload resetting the memory resource.
   task_environment_.FastForwardBy(base::Seconds(1));
 }
 
diff --git a/components/services/app_service/public/cpp/intent_util.cc b/components/services/app_service/public/cpp/intent_util.cc
index 5f047360..9bed98f1 100644
--- a/components/services/app_service/public/cpp/intent_util.cc
+++ b/components/services/app_service/public/cpp/intent_util.cc
@@ -96,6 +96,7 @@
 const char kIntentActionPotentialFileHandler[] = "potential_file_handler";
 
 const char kUseBrowserForLink[] = "use_browser";
+const char kGuestOsActivityName[] = "open-with";
 
 apps::IntentPtr MakeShareIntent(const std::vector<GURL>& filesystem_urls,
                                 const std::vector<std::string>& mime_types) {
diff --git a/components/services/app_service/public/cpp/intent_util.h b/components/services/app_service/public/cpp/intent_util.h
index 1afa5bd..fc17fad 100644
--- a/components/services/app_service/public/cpp/intent_util.h
+++ b/components/services/app_service/public/cpp/intent_util.h
@@ -36,6 +36,10 @@
 // will open the link, and that we should not prompt the user about it.
 extern const char kUseBrowserForLink[];
 
+// Activity name for GuestOS intent filters. TODO(crbug/1349974): Remove when
+// default file handling preferences for Files App are migrated.
+extern const char kGuestOsActivityName[];
+
 struct SharedText {
   std::string text;
   GURL url;
diff --git a/components/services/app_service/public/cpp/publisher_base.h b/components/services/app_service/public/cpp/publisher_base.h
index d5e49519..a52540e8 100644
--- a/components/services/app_service/public/cpp/publisher_base.h
+++ b/components/services/app_service/public/cpp/publisher_base.h
@@ -16,7 +16,7 @@
 
 namespace apps {
 
-// An publisher parent class (in the App Service sense) for all app publishers.
+// A publisher parent class (in the App Service sense) for all app publishers.
 // This class has NOTIMPLEMENTED() implementations of mandatory methods from the
 // apps::mojom::Publisher class to simplify the process of adding a new
 // publisher.
diff --git a/components/services/patch/BUILD.gn b/components/services/patch/BUILD.gn
index d0bb33f..a7ade6d 100644
--- a/components/services/patch/BUILD.gn
+++ b/components/services/patch/BUILD.gn
@@ -1,6 +1,7 @@
 # Copyright 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+import("//components/update_client/buildflags.gni")
 
 source_set("lib") {
   sources = [
@@ -11,10 +12,16 @@
   deps = [
     "//base",
     "//components/services/filesystem/public/mojom",
+    "//components/update_client:buildflags",
     "//courgette:courgette_lib",
     "//mojo/public/cpp/bindings",
   ]
 
+  if (enable_puffin_patches) {
+    include_dirs = [ "//third_party/puffin/src/include" ]
+    deps += [ "//third_party/puffin:libpuffpatch" ]
+  }
+
   public_deps = [ "//components/services/patch/public/mojom" ]
 }
 
diff --git a/components/services/patch/DEPS b/components/services/patch/DEPS
index 5b3aea1..f2759258 100644
--- a/components/services/patch/DEPS
+++ b/components/services/patch/DEPS
@@ -2,4 +2,5 @@
   "+components/update_client",
   "+courgette",
   "+mojo/public",
+  "+third_party/puffin",
 ]
diff --git a/components/services/patch/file_patcher_impl.cc b/components/services/patch/file_patcher_impl.cc
index 1f0febc..6eca40e 100644
--- a/components/services/patch/file_patcher_impl.cc
+++ b/components/services/patch/file_patcher_impl.cc
@@ -4,9 +4,20 @@
 
 #include "components/services/patch/file_patcher_impl.h"
 
+#include <utility>
+
+#include "base/callback.h"
+#include "base/notreached.h"
+#include "components/update_client/buildflags.h"
 #include "courgette/courgette.h"
 #include "courgette/third_party/bsdiff/bsdiff.h"
 
+#if BUILDFLAG(ENABLE_PUFFIN_PATCHES)
+// TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+// we should remove this #if
+#include "third_party/puffin/puffin/src/include/puffin/puffpatch.h"
+#endif
+
 namespace patch {
 
 FilePatcherImpl::FilePatcherImpl() = default;
@@ -17,6 +28,8 @@
 
 FilePatcherImpl::~FilePatcherImpl() = default;
 
+// TODO(crbug.com/1349158): Remove this function once PatchFilePuffPatch is
+// implemented as this becomes obsolete.
 void FilePatcherImpl::PatchFileBsdiff(base::File input_file,
                                       base::File patch_file,
                                       base::File output_file,
@@ -30,6 +43,8 @@
   std::move(callback).Run(patch_result_status);
 }
 
+// TODO(crbug.com/1349158): Remove this function once PatchFilePuffPatch is
+// implemented as this becomes obsolete.
 void FilePatcherImpl::PatchFileCourgette(base::File input_file,
                                          base::File patch_file,
                                          base::File output_file,
@@ -43,4 +58,19 @@
   std::move(callback).Run(patch_result_status);
 }
 
+void FilePatcherImpl::PatchFilePuffPatch(base::File input_file,
+                                         base::File patch_file,
+                                         base::File output_file,
+                                         PatchFilePuffPatchCallback callback) {
+#if BUILDFLAG(ENABLE_PUFFIN_PATCHES)
+  // TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+  // we should remove this #if.
+  const int patch_result_status = puffin::ApplyPuffPatch(
+      std::move(input_file), std::move(patch_file), std::move(output_file));
+  std::move(callback).Run(patch_result_status);
+#else
+  NOTREACHED();
+#endif
+}
+
 }  // namespace patch
diff --git a/components/services/patch/file_patcher_impl.h b/components/services/patch/file_patcher_impl.h
index 3b55121c..e0e3ebf 100644
--- a/components/services/patch/file_patcher_impl.h
+++ b/components/services/patch/file_patcher_impl.h
@@ -10,6 +10,10 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 
+namespace base {
+class File;
+}  // namespace base
+
 namespace patch {
 
 class FilePatcherImpl : public mojom::FilePatcher {
@@ -36,6 +40,10 @@
                           base::File patch_file,
                           base::File output_file,
                           PatchFileCourgetteCallback callback) override;
+  void PatchFilePuffPatch(base::File input_file_path,
+                          base::File patch_file_path,
+                          base::File output_file_path,
+                          PatchFilePuffPatchCallback callback) override;
 
   mojo::Receiver<mojom::FilePatcher> receiver_{this};
 };
diff --git a/components/services/patch/public/cpp/BUILD.gn b/components/services/patch/public/cpp/BUILD.gn
index 5dd93bb..922eb8f2 100644
--- a/components/services/patch/public/cpp/BUILD.gn
+++ b/components/services/patch/public/cpp/BUILD.gn
@@ -12,4 +12,6 @@
     "//components/services/patch/public/mojom",
     "//mojo/public/cpp/bindings",
   ]
+
+  deps = [ "//components/update_client:buildflags" ]
 }
diff --git a/components/services/patch/public/cpp/patch.cc b/components/services/patch/public/cpp/patch.cc
index e40a288..7bd6bd1 100644
--- a/components/services/patch/public/cpp/patch.cc
+++ b/components/services/patch/public/cpp/patch.cc
@@ -11,10 +11,12 @@
 #include "base/callback.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "components/update_client/buildflags.h"
 #include "components/update_client/component_patcher_operation.h"  // nogncheck
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -57,6 +59,8 @@
 
 }  // namespace
 
+// TODO(crbug.com/1349158): Remove this function once PatchFilePuffPatch is
+// implemented as this becomes obsolete.
 void Patch(mojo::PendingRemote<mojom::FilePatcher> file_patcher,
            const std::string& operation,
            const base::FilePath& input_path,
@@ -82,8 +86,8 @@
 
   // In order to share |callback| between the connection error handler and the
   // FilePatcher calls, we have to use a context object.
-  scoped_refptr<PatchParams> patch_params =
-      new PatchParams(std::move(file_patcher), std::move(callback));
+  scoped_refptr<PatchParams> patch_params = base::MakeRefCounted<PatchParams>(
+      std::move(file_patcher), std::move(callback));
 
   patch_params->file_patcher().set_disconnect_handler(
       base::BindOnce(&PatchDone, patch_params, /*result=*/-1));
@@ -101,4 +105,26 @@
   }
 }
 
+void PuffPatch(mojo::PendingRemote<mojom::FilePatcher> file_patcher,
+               base::File input_file,
+               base::File patch_file,
+               base::File output_file,
+               PatchCallback callback) {
+#if BUILDFLAG(ENABLE_PUFFIN_PATCHES)
+  // TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+  // we should remove this #if.
+
+  // Use a context object to share callback.
+  scoped_refptr<PatchParams> patch_params = base::MakeRefCounted<PatchParams>(
+      std::move(file_patcher), std::move(callback));
+
+  patch_params->file_patcher().set_disconnect_handler(
+      base::BindOnce(&PatchDone, patch_params, /*result=*/-1));
+
+  patch_params->file_patcher()->PatchFilePuffPatch(
+      std::move(input_file), std::move(patch_file), std::move(output_file),
+      base::BindOnce(&PatchDone, patch_params));
+#endif
+}
+
 }  // namespace patch
diff --git a/components/services/patch/public/cpp/patch.h b/components/services/patch/public/cpp/patch.h
index 94c0b197..ba1b404 100644
--- a/components/services/patch/public/cpp/patch.h
+++ b/components/services/patch/public/cpp/patch.h
@@ -27,6 +27,14 @@
            const base::FilePath& output_abs_path,
            PatchCallback callback);
 
+// Patches |input_abs_path| with |patch_abs_path| using the |operation|
+// algorithm and place the output in |output_abs_path|.
+void PuffPatch(mojo::PendingRemote<mojom::FilePatcher> file_patcher,
+               base::File input_abs_path,
+               base::File patch_abs_path,
+               base::File output_abs_path,
+               PatchCallback callback);
+
 }  // namespace patch
 
 #endif  // COMPONENTS_SERVICES_PATCH_PUBLIC_CPP_PATCH_H_
diff --git a/components/services/patch/public/mojom/file_patcher.mojom b/components/services/patch/public/mojom/file_patcher.mojom
index 1bd048e..9850dfa 100644
--- a/components/services/patch/public/mojom/file_patcher.mojom
+++ b/components/services/patch/public/mojom/file_patcher.mojom
@@ -6,6 +6,7 @@
 
 import "mojo/public/mojom/base/file.mojom";
 import "mojo/public/mojom/base/read_only_file.mojom";
+import "mojo/public/mojom/base/file_path.mojom";
 import "sandbox/policy/mojom/sandbox.mojom";
 
 // Interface to the out-of-process file patcher. The file patcher runs
@@ -28,4 +29,12 @@
       mojo_base.mojom.ReadOnlyFile input_file,
       mojo_base.mojom.ReadOnlyFile patch_file,
       mojo_base.mojom.File output_file) => (int32 result);
+
+  // Patch |input_file_path| with |patch_file_path| using the Puffin PuffPatch
+  // algorithm and place the output in |output_file_path|.
+  // Returns |result| puffin::Status::P_OK on success.
+  PatchFilePuffPatch(
+      mojo_base.mojom.ReadOnlyFile input_file,
+      mojo_base.mojom.ReadOnlyFile patch_file,
+      mojo_base.mojom.File output_file) => (int32 result);
 };
diff --git a/components/soda/soda_installer.cc b/components/soda/soda_installer.cc
index e2d99ea..c078994 100644
--- a/components/soda/soda_installer.cc
+++ b/components/soda/soda_installer.cc
@@ -176,7 +176,8 @@
     NotifyOnSodaInstalled(language_code);
 }
 
-void SodaInstaller::NotifySodaErrorForTesting(LanguageCode language_code) {
+void SodaInstaller::NotifySodaErrorForTesting(LanguageCode language_code,
+                                              ErrorCode error_code) {
   // TODO: Call the actual functions in SodaInstallerImpl and
   // SodaInstallerImpleChromeOS that do this logic rather than faking it.
   if (language_code == LanguageCode::kNone) {
@@ -189,7 +190,7 @@
     if (base::Contains(language_pack_progress_, language_code))
       language_pack_progress_.erase(language_code);
   }
-  NotifyOnSodaError(language_code);
+  NotifyOnSodaInstallError(language_code, error_code);
 }
 
 void SodaInstaller::UninstallSodaForTesting() {
@@ -236,9 +237,10 @@
     observer.OnSodaInstalled(language_code);
 }
 
-void SodaInstaller::NotifyOnSodaError(LanguageCode language_code) {
+void SodaInstaller::NotifyOnSodaInstallError(LanguageCode language_code,
+                                             ErrorCode error_code) {
   for (Observer& observer : observers_)
-    observer.OnSodaError(language_code);
+    observer.OnSodaInstallError(language_code, error_code);
 }
 
 void SodaInstaller::NotifyOnSodaProgress(LanguageCode language_code,
diff --git a/components/soda/soda_installer.h b/components/soda/soda_installer.h
index d406e25..7dc52b2 100644
--- a/components/soda/soda_installer.h
+++ b/components/soda/soda_installer.h
@@ -25,6 +25,12 @@
 // trying to access the SodaInstaller instance.
 class COMPONENT_EXPORT(SODA_INSTALLER) SodaInstaller {
  public:
+  // Error codes passed to the observers.
+  enum class ErrorCode {
+    kUnspecifiedError,  // a default error.
+    kNeedsReboot,       // libsoda requires an OS reboot on ChromeOS.
+  };
+
   // Observer of the SODA (Speech On-Device API) installation.
   class Observer : public base::CheckedObserver {
    public:
@@ -35,7 +41,8 @@
     // Called if there is an error in the SODA installation. If the language
     // code is LanguageCode::kNone, the error is for the SODA binary; otherwise
     // it is for the language pack.
-    virtual void OnSodaError(LanguageCode language_code) = 0;
+    virtual void OnSodaInstallError(LanguageCode language_code,
+                                    ErrorCode error_code) = 0;
 
     // Called during the SODA installation. Progress is the weighted average of
     // the combined download percentage of the SODA binary and the language pack
@@ -113,7 +120,8 @@
   void NotifySodaInstalledForTesting(
       LanguageCode language_code = LanguageCode::kNone);
   void NotifySodaErrorForTesting(
-      LanguageCode language_code = LanguageCode::kNone);
+      LanguageCode language_code = LanguageCode::kNone,
+      ErrorCode error = ErrorCode::kUnspecifiedError);
   void UninstallSodaForTesting();
   void NotifySodaProgressForTesting(
       int progress,
@@ -139,7 +147,8 @@
   // Notifies the observers that there is an error in the SODA installation.
   // If the language code is LanguageCode::kNone, the error is for the SODA
   // binary; otherwise it is for the language pack.
-  void NotifyOnSodaError(LanguageCode language_code);
+  void NotifyOnSodaInstallError(LanguageCode language_code,
+                                ErrorCode error_code);
 
   // Notifies the observers of the combined progress as the SODA binary and
   // language pack are installed. Progress is the download percentage out of
diff --git a/components/soda/soda_installer_impl_chromeos.cc b/components/soda/soda_installer_impl_chromeos.cc
index 156f99d..37c3d510 100644
--- a/components/soda/soda_installer_impl_chromeos.cc
+++ b/components/soda/soda_installer_impl_chromeos.cc
@@ -4,6 +4,8 @@
 
 #include "components/soda/soda_installer_impl_chromeos.h"
 
+#include <string>
+
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
@@ -14,17 +16,23 @@
 #include "components/live_caption/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/soda/pref_names.h"
+#include "components/soda/soda_installer.h"
 #include "media/base/media_switches.h"
 #include "ui/base/l10n/l10n_util.h"
 
+namespace speech {
 namespace {
 
 constexpr char kSodaDlcName[] = "libsoda";
 constexpr char kSodaEnglishUsDlcName[] = "libsoda-model-en-us";
 
-}  // namespace
+SodaInstaller::ErrorCode DlcCodeToSodaErrorCode(const std::string& code) {
+  return (code == dlcservice::kErrorNeedReboot)
+             ? SodaInstaller::ErrorCode::kNeedsReboot
+             : SodaInstaller::ErrorCode::kUnspecifiedError;
+}
 
-namespace speech {
+}  // namespace
 
 SodaInstallerImplChromeOS::SodaInstallerImplChromeOS() = default;
 
@@ -142,7 +150,8 @@
   } else {
     soda_binary_installed_ = false;
     soda_progress_ = 0.0;
-    NotifyOnSodaError(LanguageCode::kNone);
+    NotifyOnSodaInstallError(LanguageCode::kNone,
+                             DlcCodeToSodaErrorCode(install_result.error));
     base::UmaHistogramTimes(kSodaBinaryInstallationFailureTimeTaken,
                             base::Time::Now() - start_time);
   }
@@ -169,7 +178,8 @@
   } else {
     // TODO: Notify the observer of the specific language pack that failed
     // to install. ChromeOS currently only supports the en-US language pack.
-    NotifyOnSodaError(language_code);
+    NotifyOnSodaInstallError(language_code,
+                             DlcCodeToSodaErrorCode(install_result.error));
 
     base::UmaHistogramTimes(
         GetInstallationFailureTimeMetricForLanguagePack(language_code),
diff --git a/components/soda/soda_installer_impl_chromeos_unittest.cc b/components/soda/soda_installer_impl_chromeos_unittest.cc
index d63efb72..8c00089 100644
--- a/components/soda/soda_installer_impl_chromeos_unittest.cc
+++ b/components/soda/soda_installer_impl_chromeos_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
 #include "components/live_caption/pref_names.h"
 #include "components/prefs/testing_pref_service.h"
@@ -48,7 +47,6 @@
     pref_service_->registry()->RegisterStringPref(
         prefs::kLiveCaptionLanguageCode, kUsEnglishLocale);
 
-    chromeos::DBusThreadManager::Initialize();
     chromeos::DlcserviceClient::InitializeFake();
     fake_dlcservice_client_ = static_cast<chromeos::FakeDlcserviceClient*>(
         chromeos::DlcserviceClient::Get());
@@ -57,7 +55,6 @@
   void TearDown() override {
     soda_installer_impl_.reset();
     pref_service_.reset();
-    chromeos::DBusThreadManager::Shutdown();
     chromeos::DlcserviceClient::Shutdown();
   }
 
diff --git a/components/test/data/update_client/puffin_patch_test/BUILD.gn b/components/test/data/update_client/puffin_patch_test/BUILD.gn
new file mode 100644
index 0000000..41c26bc
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/BUILD.gn
@@ -0,0 +1,71 @@
+# 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("//components/crx_file/crx3.gni")
+
+group("puffin_patch_test_files") {
+  testonly = true
+  data_deps = [
+    ":puff_patch_v1_to_v2",
+    ":puff_patch_v2_to_v1",
+    ":puffin_app_v1_crx",
+    ":puffin_app_v2_crx",
+  ]
+}
+
+executable("puffin_app_v1_binary") {
+  testonly = true
+  sources = [ "puffin_app_v1/main.cc" ]
+}
+
+executable("puffin_app_v2_binary") {
+  testonly = true
+  sources = [ "puffin_app_v2/main.cc" ]
+}
+
+crx3("puffin_app_v1") {
+  base_dir = "$root_build_dir"
+  key = "//chrome/updater/test/data/selfupdate_test_key.der"
+  output = "$root_build_dir/puffin_app_v1_crx.crx3"
+  testonly = true
+  deps = [ ":puffin_app_v1_binary" ]
+  if (is_win) {
+    inputs = [ "$root_build_dir/puffin_app_v1_binary.exe" ]
+  } else {
+    inputs = [ "$root_build_dir/puffin_app_v1_binary" ]
+  }
+}
+
+crx3("puffin_app_v2") {
+  base_dir = "$root_build_dir"
+  key = "//chrome/updater/test/data/selfupdate_test_key.der"
+  output = "$root_build_dir/puffin_app_v2_crx.crx3"
+  testonly = true
+  deps = [ ":puffin_app_v2_binary" ]
+  if (is_win) {
+    inputs = [ "$root_build_dir/puffin_app_v2_binary.exe" ]
+  } else {
+    inputs = [ "$root_build_dir/puffin_app_v2_binary" ]
+  }
+}
+
+copy("puffin_app_v1_crx") {
+  sources = [ "puffin_app_v1.crx3" ]
+  outputs = [ "$root_build_dir/puffin_app_v1.crx3" ]
+}
+
+copy("puffin_app_v2_crx") {
+  sources = [ "puffin_app_v2.crx3" ]
+  outputs = [ "$root_build_dir/puffin_app_v2.crx3" ]
+}
+
+copy("puff_patch_v1_to_v2") {
+  sources = [ "puffin_app_v1_to_v2.puff" ]
+  outputs = [ "$root_build_dir/puffin_app_v1_to_v2.puff" ]
+}
+
+copy("puff_patch_v2_to_v1") {
+  sources = [ "puffin_app_v2_to_v1.puff" ]
+  outputs = [ "$root_build_dir/puffin_app_v2_to_v1.puff" ]
+}
diff --git a/components/test/data/update_client/puffin_patch_test/README.md b/components/test/data/update_client/puffin_patch_test/README.md
new file mode 100644
index 0000000..cff9844
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/README.md
@@ -0,0 +1,40 @@
+## How to regenerate linux, mac and windows test puff files:
+
+If changes are made to puffin_app_v1/main.cc or puffin_app_v2/main.cc, the various puff files which represent a patch between the crx's produced by each of these sources. Thus, we'll need to regenerate these on each platform.
+
+This README assumes you are already in your Chromium repo's src directory, that your gn args were generated in "src/out/Default", and that you are able to build the third_party/puffin:puffin target. To make sure puffin is building, for the time being we have to add the following to our gn args:
+
+    enable_puffin_patches = true
+
+<!-- TODO(crbug.com/1349060) once the enable_puffin_patches build argument is removed, we should update this documentation. -->
+
+**Linux and Mac commands**
+
+    autoninja -C out/Default puffin puffin_app_v1 puffin_app_v2
+    rm components/test/data/update_client/puffin_patch_test/puffin_app_v1_to_v2.puff
+    rm components/test/data/update_client/puffin_patch_test/puffin_app_v2_to_v1.puff
+    out/Default/puffin -puffdiff out/Default/puffin_app_v1.crx3 out/Default/puffin_app_v2.crx3 components/test/data/update_client/puffin_patch_test/puffin_app_v1_to_v2.puff
+    out/Default/puffin -puffdiff out/Default/puffin_app_v2.crx3 out/Default/puffin_app_v1.crx3 components/test/data/update_client/puffin_patch_test/puffin_app_v2_to_v1.puff
+    cp out/Default/puffin_app_v1_crx.crx3 components/test/data/update_client/puffin_patch_test/puffin_app_v1.crx3
+    cp out/Default/puffin_app_v2_crx.crx3 components/test/data/update_client/puffin_patch_test/puffin_app_v2.crx3
+
+**Windows commands**
+
+    autoninja -C out\Default puffin puffin_app_v1_crx puffin_app_v2_crx
+    del /f  components\test\data\update_client\puffin_patch_test\puffin_app_v1_to_v2.puff
+    del /f  components\test\data\update_client\puffin_patch_test\puffin_app_v2_to_v1.puff
+    out\Default\puffin.exe -puffdiff out\Default\puffin_app_v1.crx3 out\Default\puffin_app_v2.crx3 chrome\test\data\updater\puffin_patch_test\puffin_app_v1_to_v2.puff
+    out\Default\puffin.exe -puffdiff out\Default\puffin_app_v2.crx3 out\Default\puffin_app_v1.crx3 chrome\test\data\updater\puffin_patch_test\puffin_app_v2_to_v1.puff
+    cp out\Default\puffin_app_v1_crx.crx3 components\test\data\update_client\puffin_patch_test\puffin_app_v1.crx3
+    cp out\Default\puffin_app_v2_crx.crx3 components\test\data\update_client\puffin_patch_test\puffin_app_v2.crx3
+
+## Testing the new patches
+You can test but running the following commands to verify if all tests pass, on each platform. Specifically the "PatchingTest.ApplyPuffPatchTest":
+
+**Mac and Linux:**
+    autoninja -C out/Default puffin_unittest
+    out/Default/puffin_unittest
+
+**Windows:**
+    autoninja -C out\Default puffin_unittest
+    out\Default\puffin_unittest.exe
diff --git a/components/test/data/update_client/puffin_patch_test/puffin_app_v1.crx3 b/components/test/data/update_client/puffin_patch_test/puffin_app_v1.crx3
new file mode 100644
index 0000000..d609bfdc
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v1.crx3
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/puffin_app_v1/main.cc b/components/test/data/update_client/puffin_patch_test/puffin_app_v1/main.cc
similarity index 88%
rename from chrome/test/data/updater/puffin_patch_test/puffin_app_v1/main.cc
rename to components/test/data/update_client/puffin_patch_test/puffin_app_v1/main.cc
index d85be81..263ac30 100644
--- a/chrome/test/data/updater/puffin_patch_test/puffin_app_v1/main.cc
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v1/main.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.
 
-// The puffin patch test app is a do-nothing application "installer" that is
+// The Puffin patch test app is a do-nothing application "installer" that is
 // embedded in an update CRX. This is version 1, version 2 is located at
 // ../puffin_app_v2. if you apply the puffpatch puffin_app_v1_to_v2.puff to
 // puffin_app_v1.crx3, it produces puffin_app_v2.crx3. See
diff --git a/components/test/data/update_client/puffin_patch_test/puffin_app_v1_to_v2.puff b/components/test/data/update_client/puffin_patch_test/puffin_app_v1_to_v2.puff
new file mode 100644
index 0000000..88171d1e
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v1_to_v2.puff
Binary files differ
diff --git a/components/test/data/update_client/puffin_patch_test/puffin_app_v2.crx3 b/components/test/data/update_client/puffin_patch_test/puffin_app_v2.crx3
new file mode 100644
index 0000000..6b15b7f
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v2.crx3
Binary files differ
diff --git a/chrome/test/data/updater/puffin_patch_test/puffin_app_v2/main.cc b/components/test/data/update_client/puffin_patch_test/puffin_app_v2/main.cc
similarity index 88%
rename from chrome/test/data/updater/puffin_patch_test/puffin_app_v2/main.cc
rename to components/test/data/update_client/puffin_patch_test/puffin_app_v2/main.cc
index f55c947..d682acc 100644
--- a/chrome/test/data/updater/puffin_patch_test/puffin_app_v2/main.cc
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v2/main.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.
 
-// The puffin patch test app is a do-nothing application "installer" that is
+// The Puffin patch test app is a do-nothing application "installer" that is
 // embedded in an update CRX. This is version 2, version 1 is located at
 // ../puffin_app_v1. If you apply the
 // puffpatch puffin_app_v1_to_v2.puff to puffin_app_v1.crx3, it produces
diff --git a/components/test/data/update_client/puffin_patch_test/puffin_app_v2_to_v1.puff b/components/test/data/update_client/puffin_patch_test/puffin_app_v2_to_v1.puff
new file mode 100644
index 0000000..8101ef9
--- /dev/null
+++ b/components/test/data/update_client/puffin_patch_test/puffin_app_v2_to_v1.puff
Binary files differ
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
index 6b4f654..e24cc2f0 100644
--- a/components/update_client/BUILD.gn
+++ b/components/update_client/BUILD.gn
@@ -2,9 +2,16 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/buildflag_header.gni")
+import("//components/update_client/buildflags.gni")
 import("//net/features.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
 
+buildflag_header("buildflags") {
+  header = "buildflags.h"
+  flags = [ "ENABLE_PUFFIN_PATCHES=$enable_puffin_patches" ]
+}
+
 source_set("network_impl") {
   sources = [
     "net/network_chromium.h",
@@ -39,6 +46,7 @@
     "patch/in_process_patcher.h",
   ]
   deps = [
+    ":buildflags",
     ":update_client",
     "//base",
     "//courgette:bsdiff",
@@ -63,6 +71,7 @@
     "patch/patch_impl.h",
   ]
   deps = [
+    ":buildflags",
     ":update_client",
     "//build:chromeos_buildflags",
     "//components/services/patch/public/cpp",
@@ -165,6 +174,18 @@
       "background_downloader_win.h",
     ]
   }
+
+  # TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+  # we should remove the enable_puffin_patches flag.
+  if (enable_puffin_patches) {
+    include_dirs = [ "//third_party/puffin/src/include" ]
+    sources += [
+      "puffin_component_unpacker.cc",
+      "puffin_component_unpacker.h",
+      "puffin_patcher.cc",
+      "puffin_patcher.h",
+    ]
+  }
 }
 
 static_library("test_support") {
@@ -276,6 +297,17 @@
     "//testing/gtest",
     "//third_party/re2",
   ]
+
+  # TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+  # we should remove the enable_puffin_patches flag.
+  if (enable_puffin_patches) {
+    sources += [
+      "puffin_component_unpacker_unittest.cc",
+      "puffin_patcher_unittest.cc",
+    ]
+    deps += [ "//third_party/puffin:libpuffpatch" ]
+    data_deps = [ "//components/test/data/update_client/puffin_patch_test:puffin_patch_test_files" ]
+  }
 }
 
 fuzzer_test("update_client_protocol_serializer_fuzzer") {
diff --git a/components/update_client/DEPS b/components/update_client/DEPS
index f226ca9..6878ba4 100644
--- a/components/update_client/DEPS
+++ b/components/update_client/DEPS
@@ -8,6 +8,7 @@
   "+courgette",
   "+crypto",
   "+mojo/public",
+  "+third_party/puffin",
   "+third_party/re2",
   "+third_party/zlib",
 ]
diff --git a/components/update_client/buildflags.gni b/components/update_client/buildflags.gni
new file mode 100644
index 0000000..fdbc7e1
--- /dev/null
+++ b/components/update_client/buildflags.gni
@@ -0,0 +1,9 @@
+# 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.
+
+declare_args() {
+  # Whether to enable the Puffin PuffPatch feature which is currently still
+  # in development.
+  enable_puffin_patches = false
+}
diff --git a/components/update_client/component_patcher.h b/components/update_client/component_patcher.h
index 8b01f7fa..41dc3f11 100644
--- a/components/update_client/component_patcher.h
+++ b/components/update_client/component_patcher.h
@@ -34,6 +34,9 @@
 #include "base/values.h"
 #include "components/update_client/component_unpacker.h"
 
+// TODO(crbug.com/1349158): Remove this class once Puffin patches are fully
+// implemented.
+
 namespace base {
 class FilePath;
 }
diff --git a/components/update_client/component_patcher_operation.h b/components/update_client/component_patcher_operation.h
index 66d4447a..78e4fa2 100644
--- a/components/update_client/component_patcher_operation.h
+++ b/components/update_client/component_patcher_operation.h
@@ -13,6 +13,9 @@
 #include "components/update_client/component_patcher.h"
 #include "components/update_client/component_unpacker.h"
 
+// TODO(crbug.com/1349158): Remove this file once Puffin patches are fully
+// implemented.
+
 namespace base {
 class DictionaryValue;
 }  // namespace base
diff --git a/components/update_client/component_patcher_unittest.h b/components/update_client/component_patcher_unittest.h
index 3299619..ba9b780 100644
--- a/components/update_client/component_patcher_unittest.h
+++ b/components/update_client/component_patcher_unittest.h
@@ -13,6 +13,9 @@
 #include "courgette/third_party/bsdiff/bsdiff.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+// TODO(crbug.com/1349158): Remove this file once Puffin patches are fully
+// implemented.
+
 namespace update_client {
 
 class ReadOnlyTestInstaller;
diff --git a/components/update_client/component_unpacker.h b/components/update_client/component_unpacker.h
index 1a735471..a68e3a9 100644
--- a/components/update_client/component_unpacker.h
+++ b/components/update_client/component_unpacker.h
@@ -16,6 +16,9 @@
 #include "base/memory/ref_counted.h"
 #include "components/update_client/update_client_errors.h"
 
+// TODO(crbug.com/1349158): Remove this class once Puffin patches are fully
+// implemented.
+
 namespace crx_file {
 enum class VerifierFormat;
 }
diff --git a/components/update_client/patch/in_process_patcher.cc b/components/update_client/patch/in_process_patcher.cc
index 17d6c99..c3afd2e 100644
--- a/components/update_client/patch/in_process_patcher.cc
+++ b/components/update_client/patch/in_process_patcher.cc
@@ -10,6 +10,8 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
+#include "components/update_client/buildflags.h"
 #include "courgette/courgette.h"
 #include "courgette/third_party/bsdiff/bsdiff.h"
 
@@ -21,6 +23,8 @@
  public:
   InProcessPatcher() = default;
 
+  // TODO(crbug.com/1349158): Remove this function once PatchPuffPatch is
+  // implemented as this becomes obsolete.
   void PatchBsdiff(const base::FilePath& input_path,
                    const base::FilePath& patch_path,
                    const base::FilePath& output_path,
@@ -41,6 +45,8 @@
         std::move(input_file), std::move(patch_file), std::move(output_file)));
   }
 
+  // TODO(crbug.com/1349158): Remove this function once PatchPuffPatch is
+  // implemented as this becomes obsolete.
   void PatchCourgette(const base::FilePath& input_path,
                       const base::FilePath& patch_path,
                       const base::FilePath& output_path,
@@ -61,6 +67,20 @@
         std::move(input_file), std::move(patch_file), std::move(output_file)));
   }
 
+  void PatchPuffPatch(base::File input_file,
+                      base::File patch_file,
+                      base::File output_file,
+                      PatchCompleteCallback callback) const override {
+#if BUILDFLAG(ENABLE_PUFFIN_PATCHES)
+    // TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+    // we should remove this #if.
+    std::move(callback).Run(puffin::ApplyPuffPatch(
+        std::move(input_file), std::move(patch_file), std::move(output_file)));
+#else
+    NOTREACHED();
+#endif
+  }
+
  protected:
   ~InProcessPatcher() override = default;
 };
diff --git a/components/update_client/patch/patch_impl.cc b/components/update_client/patch/patch_impl.cc
index 599101f..9c020a9 100644
--- a/components/update_client/patch/patch_impl.cc
+++ b/components/update_client/patch/patch_impl.cc
@@ -4,7 +4,11 @@
 
 #include "components/update_client/patch/patch_impl.h"
 
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/notreached.h"
 #include "components/services/patch/public/cpp/patch.h"
+#include "components/update_client/buildflags.h"
 #include "components/update_client/component_patcher_operation.h"
 
 namespace update_client {
@@ -32,6 +36,21 @@
                  patch_file, destination, std::move(callback));
   }
 
+  void PatchPuffPatch(base::File old_file,
+                      base::File patch_file,
+                      base::File destination_file,
+                      PatchCompleteCallback callback) const override {
+#if BUILDFLAG(ENABLE_PUFFIN_PATCHES)
+    // TODO(crbug.com/1349060) once Puffin patches are fully implemented,
+    // we should remove this #if.
+    patch::PuffPatch(callback_.Run(), std::move(old_file),
+                     std::move(patch_file), std::move(destination_file),
+                     std::move(callback));
+#else
+    NOTREACHED();
+#endif
+  }
+
  protected:
   ~PatcherImpl() override = default;
 
diff --git a/components/update_client/patcher.h b/components/update_client/patcher.h
index 604c2b2..55eea78 100644
--- a/components/update_client/patcher.h
+++ b/components/update_client/patcher.h
@@ -10,6 +10,7 @@
 
 namespace base {
 class FilePath;
+class File;
 }  // namespace base
 
 namespace update_client {
@@ -31,6 +32,11 @@
                               const base::FilePath& destination,
                               PatchCompleteCallback callback) const = 0;
 
+  virtual void PatchPuffPatch(base::File input_file_path,
+                              base::File patch_file_path,
+                              base::File output_file_path,
+                              PatchCompleteCallback callback) const = 0;
+
  protected:
   friend class base::RefCountedThreadSafe<Patcher>;
   Patcher() = default;
diff --git a/components/update_client/puffin_component_unpacker.cc b/components/update_client/puffin_component_unpacker.cc
new file mode 100644
index 0000000..973bd09
--- /dev/null
+++ b/components/update_client/puffin_component_unpacker.cc
@@ -0,0 +1,121 @@
+// 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/update_client/puffin_component_unpacker.h"
+
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+PuffinComponentUnpacker::Result::Result() = default;
+
+PuffinComponentUnpacker::PuffinComponentUnpacker(
+    const std::vector<uint8_t>& pk_hash,
+    const base::FilePath& path,
+    std::unique_ptr<Unzipper> unzipper,
+    crx_file::VerifierFormat crx_format,
+    base::OnceCallback<void(const Result& result)> callback)
+    : pk_hash_(pk_hash),
+      path_(path),
+      unzipper_(std::move(unzipper)),
+      crx_format_(crx_format),
+      callback_(std::move(callback)) {}
+
+PuffinComponentUnpacker::~PuffinComponentUnpacker() = default;
+
+void PuffinComponentUnpacker::Unpack(
+    const std::vector<uint8_t>& pk_hash,
+    const base::FilePath& path,
+    std::unique_ptr<Unzipper> unzipper,
+    crx_file::VerifierFormat crx_format,
+    base::OnceCallback<void(const Result& result)> callback) {
+  scoped_refptr<PuffinComponentUnpacker> unpacker =
+      base::WrapRefCounted(new PuffinComponentUnpacker(
+          pk_hash, path, std::move(unzipper), crx_format, std::move(callback)));
+  unpacker->Verify();
+}
+
+void PuffinComponentUnpacker::Verify() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  VLOG(1) << "Verifying component: " << path_.value();
+  if (path_.empty()) {
+    EndUnpacking(UnpackerError::kInvalidParams, 0);
+    return;
+  }
+  std::vector<std::vector<uint8_t>> required_keys;
+  if (!pk_hash_.empty())
+    required_keys.push_back(pk_hash_);
+  const crx_file::VerifierResult result = crx_file::Verify(
+      path_, crx_format_, required_keys, std::vector<uint8_t>(), &public_key_,
+      /*crx_id=*/nullptr, /*compressed_verified_contents=*/nullptr);
+  if (result != crx_file::VerifierResult::OK_FULL) {
+    EndUnpacking(UnpackerError::kInvalidFile, static_cast<int>(result));
+    return;
+  }
+  VLOG(2) << "Verification successful: " << path_.value();
+  BeginUnzipping();
+}
+
+void PuffinComponentUnpacker::BeginUnzipping() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::FilePath& destination = unpack_path_;
+  if (!base::CreateNewTempDirectory(base::FilePath::StringType(),
+                                    &destination)) {
+    VLOG(1) << "Unable to create temporary directory for unpacking.";
+    EndUnpacking(UnpackerError::kUnzipPathError, 0);
+    return;
+  }
+  VLOG(1) << "Unpacking in: " << destination.value();
+  unzipper_->Unzip(
+      path_, destination,
+      base::BindOnce(&PuffinComponentUnpacker::EndUnzipping, this));
+}
+
+void PuffinComponentUnpacker::EndUnzipping(bool result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!result) {
+    VLOG(1) << "Unzipping failed.";
+    EndUnpacking(UnpackerError::kUnzipFailed, 0);
+    return;
+  }
+  VLOG(2) << "Unzipped successfully";
+  EndUnpacking(UnpackerError::kNone, 0);
+}
+
+void PuffinComponentUnpacker::EndUnpacking(UnpackerError error,
+                                           int extended_error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (error != UnpackerError::kNone && !unpack_path_.empty())
+    base::DeletePathRecursively(unpack_path_);
+
+  Result result;
+  result.error = error;
+  result.extended_error = extended_error;
+  if (error == UnpackerError::kNone) {
+    VLOG(2) << "Unpacked successfully";
+    result.unpack_path = unpack_path_;
+    result.public_key = public_key_;
+  }
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), result));
+}
+
+}  // namespace update_client
diff --git a/components/update_client/puffin_component_unpacker.h b/components/update_client/puffin_component_unpacker.h
new file mode 100644
index 0000000..c70140b
--- /dev/null
+++ b/components/update_client/puffin_component_unpacker.h
@@ -0,0 +1,119 @@
+// 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_UPDATE_CLIENT_PUFFIN_COMPONENT_UNPACKER_H_
+#define COMPONENTS_UPDATE_CLIENT_PUFFIN_COMPONENT_UNPACKER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace crx_file {
+enum class VerifierFormat;
+}
+
+namespace update_client {
+
+class Unzipper;
+
+// Unpacks the component CRX package and verifies that it is
+// well formed and the cryptographic signature is correct.
+//
+// This class is only used by the component updater. It is inspired by
+// and overlaps with code in the extension's SandboxedUnpacker.
+// The main differences are:
+// - The public key hash is SHA256.
+// - Does not use a sandboxed unpacker. A valid component is fully trusted.
+// - The manifest can have different attributes and resources are not
+//   transcoded.
+//
+// This is an updated version of ComponentUnpacker that leverages the new
+// Puffin-based puffpatch CRX-diff format, rather than the legacy
+// courgette/bsdiff per-file CRXD format. Puffin patches the CRX before
+// unpacking, changing the order of operations such that patching needs to occur
+// before verifying and unpacking. Unlike the original implementation, by the
+// time we unpack, the patching has already occurred.
+class PuffinComponentUnpacker
+    : public base::RefCountedThreadSafe<PuffinComponentUnpacker> {
+ public:
+  // Contains the result of the unpacking.
+  struct Result {
+    Result();
+
+    // Unpack error: 0 indicates success.
+    UnpackerError error = UnpackerError::kNone;
+
+    // Additional error information, such as errno or last error.
+    int extended_error = 0;
+
+    // Path of the unpacked files if the unpacking was successful.
+    base::FilePath unpack_path;
+
+    // The extracted public key of the package if the unpacking was successful.
+    std::string public_key;
+  };
+
+  PuffinComponentUnpacker(const PuffinComponentUnpacker&) = delete;
+  PuffinComponentUnpacker& operator=(const PuffinComponentUnpacker&) = delete;
+
+  // Begins the actual unpacking of the files. Calls `callback` with the result.
+  static void Unpack(const std::vector<uint8_t>& pk_hash,
+                     const base::FilePath& path,
+                     std::unique_ptr<Unzipper> unzipper,
+                     crx_file::VerifierFormat crx_format,
+                     base::OnceCallback<void(const Result& result)> callback);
+
+ private:
+  friend class base::RefCountedThreadSafe<PuffinComponentUnpacker>;
+
+  // Constructs an unpacker for a specific component unpacking operation.
+  // `pk_hash` is the expected public developer key's SHA256 hash. If empty,
+  // the unpacker accepts any developer key. `path` is the current location
+  // of the CRX.
+  PuffinComponentUnpacker(
+      const std::vector<uint8_t>& pk_hash,
+      const base::FilePath& path,
+      std::unique_ptr<Unzipper> unzipper,
+      crx_file::VerifierFormat crx_format,
+      base::OnceCallback<void(const Result& result)> callback);
+
+  virtual ~PuffinComponentUnpacker();
+
+  // The first step of unpacking is to verify the file. Triggers
+  // `BeginUnzipping` if successful. Triggers `EndUnpacking` if an early error
+  // is encountered.
+  void Verify();
+
+  // The next step of unpacking is to unzip. Triggers `EndUnzipping` if
+  // successful. Triggers `EndUnpacking` if an early error is encountered.
+  void BeginUnzipping();
+  void EndUnzipping(bool error);
+
+  // The final step is to do clean-up for things that can't be tidied as we go.
+  // If there is an error at any step, the remaining steps are skipped and
+  // `EndUnpacking` is called. `EndUnpacking` is responsible for calling the
+  // callback provided in `Unpack`.
+  void EndUnpacking(UnpackerError error, int extended_error);
+
+  std::vector<uint8_t> pk_hash_;
+  base::FilePath path_;
+  std::unique_ptr<Unzipper> unzipper_;
+  crx_file::VerifierFormat crx_format_;
+  base::OnceCallback<void(const Result& result)> callback_;
+  base::FilePath unpack_path_;
+  std::string public_key_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PUFFIN_COMPONENT_UNPACKER_H_
diff --git a/components/update_client/puffin_component_unpacker_unittest.cc b/components/update_client/puffin_component_unpacker_unittest.cc
new file mode 100644
index 0000000..10b25bc
--- /dev/null
+++ b/components/update_client/puffin_component_unpacker_unittest.cc
@@ -0,0 +1,131 @@
+// 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 <iterator>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/puffin_component_unpacker.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/unzipper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+base::FilePath TestFile(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  return path.AppendASCII("components")
+      .AppendASCII("test")
+      .AppendASCII("data")
+      .AppendASCII("update_client")
+      .AppendASCII(file);
+}
+
+}  // namespace
+
+namespace update_client {
+
+class PuffinComponentUnpackerTest : public testing::Test {
+ public:
+  PuffinComponentUnpackerTest() = default;
+  ~PuffinComponentUnpackerTest() override = default;
+
+  void UnpackComplete(const PuffinComponentUnpacker::Result& result);
+
+ protected:
+  base::test::TaskEnvironment env_;
+};
+
+TEST_F(PuffinComponentUnpackerTest, UnpackFullCrx) {
+  auto config = base::MakeRefCounted<TestConfigurator>();
+  SEQUENCE_CHECKER(sequence_checker);
+  base::RunLoop loop;
+  PuffinComponentUnpacker::Unpack(
+      std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+      TestFile("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+      config->GetUnzipperFactory()->Create(), crx_file::VerifierFormat::CRX3,
+      base::BindLambdaForTesting(
+          [&loop,
+           &sequence_checker](const PuffinComponentUnpacker::Result& result) {
+            DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
+            EXPECT_EQ(result.error, UnpackerError::kNone);
+            EXPECT_EQ(result.extended_error, 0);
+
+            base::FilePath unpack_path = result.unpack_path;
+            EXPECT_TRUE(base::DirectoryExists(unpack_path));
+            EXPECT_EQ(result.public_key, jebg_public_key);
+
+            int64_t file_size = 0;
+            EXPECT_TRUE(base::GetFileSize(
+                unpack_path.AppendASCII("component1.dll"), &file_size));
+            EXPECT_EQ(file_size, 1024);
+            EXPECT_TRUE(base::GetFileSize(
+                unpack_path.AppendASCII("manifest.json"), &file_size));
+            EXPECT_EQ(file_size, 169);
+
+            EXPECT_TRUE(base::DeletePathRecursively(unpack_path));
+            loop.Quit();
+          }));
+  loop.Run();
+  DETACH_FROM_SEQUENCE(sequence_checker);
+}
+
+TEST_F(PuffinComponentUnpackerTest, UnpackFileNotFound) {
+  SEQUENCE_CHECKER(sequence_checker);
+  base::RunLoop loop;
+  PuffinComponentUnpacker::Unpack(
+      std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+      TestFile("file_not_found.crx"), nullptr, crx_file::VerifierFormat::CRX3,
+      base::BindLambdaForTesting(
+          [&loop,
+           &sequence_checker](const PuffinComponentUnpacker::Result& result) {
+            DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
+            EXPECT_EQ(result.error, UnpackerError::kInvalidFile);
+            EXPECT_EQ(result.extended_error,
+                      static_cast<int>(
+                          crx_file::VerifierResult::ERROR_FILE_NOT_READABLE));
+            EXPECT_TRUE(result.unpack_path.empty());
+            EXPECT_TRUE(result.public_key.empty());
+            loop.Quit();
+          }));
+  loop.Run();
+  DETACH_FROM_SEQUENCE(sequence_checker);
+}
+
+// Tests a mismatch between the public key hash and the id of the component.
+TEST_F(PuffinComponentUnpackerTest, UnpackFileHashMismatch) {
+  SEQUENCE_CHECKER(sequence_checker);
+  base::RunLoop loop;
+  PuffinComponentUnpacker::Unpack(
+      std::vector<uint8_t>(std::begin(abag_hash), std::end(abag_hash)),
+      TestFile("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr,
+      crx_file::VerifierFormat::CRX3,
+      base::BindLambdaForTesting(
+          [&loop,
+           &sequence_checker](const PuffinComponentUnpacker::Result& result) {
+            DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
+            EXPECT_EQ(result.error, UnpackerError::kInvalidFile);
+            EXPECT_EQ(
+                result.extended_error,
+                static_cast<int>(
+                    crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING));
+            EXPECT_TRUE(result.unpack_path.empty());
+            EXPECT_TRUE(result.public_key.empty());
+            loop.Quit();
+          }));
+  loop.Run();
+  DETACH_FROM_SEQUENCE(sequence_checker);
+}
+
+}  // namespace update_client
diff --git a/components/update_client/puffin_patcher.cc b/components/update_client/puffin_patcher.cc
new file mode 100644
index 0000000..490f85a
--- /dev/null
+++ b/components/update_client/puffin_patcher.cc
@@ -0,0 +1,90 @@
+// 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/update_client/puffin_patcher.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/update_client/component_patcher_operation.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "third_party/puffin/puffin/src/include/puffin/puffpatch.h"
+
+namespace update_client {
+
+PuffinPatcher::PuffinPatcher(
+    base::File old_crx_file,
+    base::File puff_patch_file,
+    base::File new_crx_output,
+    scoped_refptr<Patcher> patcher,
+    base::OnceCallback<void(UnpackerError, int)> callback)
+    : old_crx_file_(std::move(old_crx_file)),
+      puff_patch_file_(std::move(puff_patch_file)),
+      new_crx_output_file_(std::move(new_crx_output)),
+      patcher_(patcher),
+      callback_(std::move(callback)) {}
+
+PuffinPatcher::~PuffinPatcher() = default;
+
+void PuffinPatcher::Patch(
+    base::File old_crx_file,
+    base::File puff_patch_file,
+    base::File new_crx_output,
+    scoped_refptr<Patcher> patcher,
+    base::OnceCallback<void(UnpackerError, int)> callback) {
+  scoped_refptr<PuffinPatcher> puffin_patcher =
+      base::WrapRefCounted(new PuffinPatcher(
+          std::move(old_crx_file), std::move(puff_patch_file),
+          std::move(new_crx_output), patcher, std::move(callback)));
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&PuffinPatcher::StartPatching, puffin_patcher));
+}
+
+void PuffinPatcher::StartPatching() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!old_crx_file_.IsValid()) {
+    DonePatching(UnpackerError::kInvalidParams, 0);
+  } else if (!puff_patch_file_.IsValid()) {
+    DonePatching(UnpackerError::kInvalidParams, 0);
+  } else if (!new_crx_output_file_.IsValid()) {
+    DonePatching(UnpackerError::kInvalidParams, 0);
+  } else {
+    PatchCrx();
+  }
+}
+
+void PuffinPatcher::PatchCrx() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  patcher_->PatchPuffPatch(std::move(old_crx_file_),
+                           std::move(puff_patch_file_),
+                           std::move(new_crx_output_file_),
+                           base::BindOnce(&PuffinPatcher::DonePatch, this));
+}
+
+void PuffinPatcher::DonePatch(int extended_error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (extended_error != puffin::P_OK) {
+    DonePatching(UnpackerError::kDeltaOperationFailure, extended_error);
+  } else {
+    DonePatching(UnpackerError::kNone, 0);
+  }
+}
+
+void PuffinPatcher::DonePatching(UnpackerError error, int extended_error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+  std::move(callback_).Run(error, extended_error);
+}
+
+}  // namespace update_client
diff --git a/components/update_client/puffin_patcher.h b/components/update_client/puffin_patcher.h
new file mode 100644
index 0000000..4f43e791
--- /dev/null
+++ b/components/update_client/puffin_patcher.h
@@ -0,0 +1,87 @@
+// 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.
+
+// Component updates can be either differential updates or full updates.
+// Full updates come in CRX format; differential puffdiff updates come in *.puff
+// files, with a different magic number. They describe a bsdiff diff generated
+// by Puffin. The patcher takes the previous version of this binary, applies
+// puffpatch and generates the newest version of the CRX.
+//
+// The component updater attempts a differential update if it is available
+// and allowed to, and fall back to a full update if it fails.
+//
+// After installation (diff or full), the component updater records "fp", the
+// fingerprint of the installed files, to later identify the existing files to
+// the server so that a proper differential update can be provided next cycle.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_PUFFIN_PATCHER_H_
+#define COMPONENTS_UPDATE_CLIENT_PUFFIN_PATCHER_H_
+
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "base/values.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class File;
+}
+
+namespace update_client {
+
+class Patcher;
+
+// Encapsulates a task for applying a differential update to a component.
+class PuffinPatcher : public base::RefCountedThreadSafe<PuffinPatcher> {
+ public:
+  PuffinPatcher(const PuffinPatcher&) = delete;
+  PuffinPatcher& operator=(const PuffinPatcher&) = delete;
+
+  // Takes a full CRX `old_crx_file` and a Puffin puffpatch file
+  // `puff_patch_file`, and sets up the class to create a new full
+  // If `in_process` is true, patching is done completely within the
+  // existing process. Otherwise, some steps of patching may be done
+  // out-of-process. Then, it starts patching files.
+  //
+  // This static function returns immediately, after posting a task
+  // to do the patching. When patching has been completed,
+  // `callback` is called with the error codes if any error codes were
+  // encountered.
+  static void Patch(base::File old_crx_file,
+                    base::File puff_patch_file,
+                    base::File new_crx_output,
+                    scoped_refptr<Patcher> patcher,
+                    base::OnceCallback<void(UnpackerError, int)> callback);
+
+ private:
+  friend class base::RefCountedThreadSafe<PuffinPatcher>;
+
+  PuffinPatcher(base::File old_crx_file,
+                base::File puff_patch_file,
+                base::File new_crx_output,
+                scoped_refptr<Patcher> patcher,
+                base::OnceCallback<void(UnpackerError, int)> callback);
+
+  virtual ~PuffinPatcher();
+
+  void StartPatching();
+
+  void PatchCrx();
+
+  void DonePatch(int extended_error);
+
+  void DonePatching(UnpackerError error, int extended_error);
+
+  base::File old_crx_file_;
+  base::File puff_patch_file_;
+  base::File new_crx_output_file_;
+  scoped_refptr<Patcher> patcher_;
+  base::OnceCallback<void(UnpackerError, int)> callback_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PUFFIN_PATCHER_H_
diff --git a/components/update_client/puffin_patcher_unittest.cc b/components/update_client/puffin_patcher_unittest.cc
new file mode 100644
index 0000000..48bf253
--- /dev/null
+++ b/components/update_client/puffin_patcher_unittest.cc
@@ -0,0 +1,110 @@
+// 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/update_client/puffin_patcher.h"
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "components/services/patch/in_process_file_patcher.h"
+#include "components/update_client/patch/patch_impl.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/update_client_errors.h"
+#include "courgette/courgette.h"
+#include "courgette/third_party/bsdiff/bsdiff.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+namespace {
+
+base::FilePath OutTestFile(const char* file) {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_GEN_TEST_DATA_ROOT, &path);
+  return path.AppendASCII(file);
+}
+
+}  // namespace
+
+class PuffinPatcherTest : public testing::Test {
+ public:
+  PuffinPatcherTest() = default;
+  ~PuffinPatcherTest() override = default;
+
+ protected:
+  base::test::TaskEnvironment env_;
+};
+
+TEST_F(PuffinPatcherTest, CheckPuffPatch) {
+  // The operation needs a Patcher to access the PatchService.
+  scoped_refptr<Patcher> patcher =
+      base::MakeRefCounted<PatchChromiumFactory>(
+          base::BindRepeating(&patch::LaunchInProcessFilePatcher))
+          ->Create();
+
+  base::FilePath out_file = OutTestFile("puffin_app_v1_to_v2.crx3");
+  EXPECT_TRUE(base::DeleteFile(out_file));
+  SEQUENCE_CHECKER(sequence_checker);
+  {
+    base::File input_file(OutTestFile("puffin_app_v1.crx3"),
+                          base::File::FLAG_OPEN | base::File::FLAG_READ);
+    base::File patch_file(OutTestFile("puffin_app_v1_to_v2.puff"),
+                          base::File::FLAG_OPEN | base::File::FLAG_READ);
+    base::File output_file(out_file, base::File::FLAG_CREATE |
+                                         base::File::FLAG_WRITE |
+                                         base::File::FLAG_WIN_EXCLUSIVE_WRITE);
+    base::RunLoop loop;
+    PuffinPatcher::Patch(
+        std::move(input_file), std::move(patch_file), std::move(output_file),
+        patcher,
+        base::BindLambdaForTesting(
+            [&loop, &sequence_checker](UnpackerError error, int extra_code) {
+              DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
+              EXPECT_EQ(error, UnpackerError::kNone);
+              EXPECT_EQ(extra_code, 0);
+              loop.Quit();
+            }));
+    loop.Run();
+  }
+
+  EXPECT_TRUE(base::ContentsEqual(OutTestFile("puffin_app_v2.crx3"), out_file));
+
+  out_file = OutTestFile("puffin_app_v2_to_v1.crx3");
+  EXPECT_TRUE(base::DeleteFile(out_file));
+  {
+    base::File input_file(OutTestFile("puffin_app_v2.crx3"),
+                          base::File::FLAG_OPEN | base::File::FLAG_READ);
+    base::File patch_file(OutTestFile("puffin_app_v2_to_v1.puff"),
+                          base::File::FLAG_OPEN | base::File::FLAG_READ);
+    base::File output_file(out_file, base::File::FLAG_CREATE |
+                                         base::File::FLAG_WRITE |
+                                         base::File::FLAG_WIN_EXCLUSIVE_WRITE);
+    base::RunLoop loop;
+    PuffinPatcher::Patch(
+        std::move(input_file), std::move(patch_file), std::move(output_file),
+        patcher,
+        base::BindLambdaForTesting(
+            [&loop, &sequence_checker](UnpackerError error, int extra_code) {
+              DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
+              EXPECT_EQ(error, UnpackerError::kNone);
+              EXPECT_EQ(extra_code, 0);
+              loop.Quit();
+            }));
+    loop.Run();
+  }
+
+  DETACH_FROM_SEQUENCE(sequence_checker);
+  EXPECT_TRUE(base::ContentsEqual(OutTestFile("puffin_app_v1.crx3"), out_file));
+}
+
+}  // namespace update_client
diff --git a/components/user_notes/browser/user_note_service.cc b/components/user_notes/browser/user_note_service.cc
index 015ef7f2..6f08bf3 100644
--- a/components/user_notes/browser/user_note_service.cc
+++ b/components/user_notes/browser/user_note_service.cc
@@ -14,6 +14,7 @@
 #include "components/user_notes/interfaces/user_notes_ui.h"
 #include "components/user_notes/user_notes_features.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/weak_document_ptr.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace user_notes {
@@ -68,12 +69,12 @@
 
   DCHECK(UserNoteManager::GetForPage(rfh->GetPage()));
 
-  std::vector<content::RenderFrameHost*> frames = {rfh};
+  std::vector<content::WeakDocumentPtr> frames = {rfh->GetWeakDocumentPtr()};
   UserNoteStorage::UrlSet urls = {rfh->GetLastCommittedURL()};
   storage_->GetNoteMetadataForUrls(
       std::move(urls),
       base::BindOnce(&UserNoteService::OnNoteMetadataFetchedForNavigation,
-                     weak_ptr_factory_.GetWeakPtr(), frames, rfh));
+                     weak_ptr_factory_.GetWeakPtr(), frames));
 }
 
 void UserNoteService::OnNoteInstanceAddedToPage(
@@ -259,15 +260,18 @@
   std::vector<content::RenderFrameHost*> all_frames =
       delegate_->GetAllFramesForUserNotes();
   UserNoteStorage::UrlSet urls;
+  std::vector<content::WeakDocumentPtr> all_frames_weak;
+  all_frames_weak.reserve(all_frames.size());
 
   for (content::RenderFrameHost* frame : all_frames) {
     urls.emplace(frame->GetLastCommittedURL());
+    all_frames_weak.emplace_back(frame->GetWeakDocumentPtr());
   }
 
   storage_->GetNoteMetadataForUrls(
       std::move(urls),
       base::BindOnce(&UserNoteService::OnNoteMetadataFetched,
-                     weak_ptr_factory_.GetWeakPtr(), all_frames));
+                     weak_ptr_factory_.GetWeakPtr(), all_frames_weak));
 }
 
 void UserNoteService::InitializeNewNoteForCreation(
@@ -378,13 +382,19 @@
 }
 
 void UserNoteService::OnNoteMetadataFetchedForNavigation(
-    const std::vector<content::RenderFrameHost*>& all_frames,
-    const content::RenderFrameHost* navigated_frame,
+    const std::vector<content::WeakDocumentPtr>& all_frames,
     UserNoteMetadataSnapshot metadata_snapshot) {
   DCHECK(all_frames.size() == 1u);
 
-  if (delegate_->IsFrameInActiveTab(all_frames[0])) {
-    UserNotesUI* ui = delegate_->GetUICoordinatorForFrame(all_frames[0]);
+  content::RenderFrameHost* rfh = all_frames[0].AsRenderFrameHostIfValid();
+
+  if (!rfh) {
+    // The navigated frame is no longer valid.
+    return;
+  }
+
+  if (delegate_->IsFrameInActiveTab(rfh)) {
+    UserNotesUI* ui = delegate_->GetUICoordinatorForFrame(rfh);
     DCHECK(ui);
 
     // TODO(crbug.com/1313967): For now, always invalidate the UI if the tab is
@@ -415,7 +425,7 @@
 }
 
 void UserNoteService::OnNoteMetadataFetched(
-    const std::vector<content::RenderFrameHost*>& all_frames,
+    const std::vector<content::WeakDocumentPtr>& all_frames,
     UserNoteMetadataSnapshot metadata_snapshot) {
   std::vector<std::unique_ptr<FrameUserNoteChanges>> note_changes =
       CalculateNoteChanges(*this, all_frames, metadata_snapshot);
diff --git a/components/user_notes/browser/user_note_service.h b/components/user_notes/browser/user_note_service.h
index 03bd34c0..dbd76e1 100644
--- a/components/user_notes/browser/user_note_service.h
+++ b/components/user_notes/browser/user_note_service.h
@@ -145,11 +145,10 @@
   // Private helpers used when processing note storage changes. Marked virtual
   // for tests to override.
   virtual void OnNoteMetadataFetchedForNavigation(
-      const std::vector<content::RenderFrameHost*>& all_frames,
-      const content::RenderFrameHost* navigated_frame,
+      const std::vector<content::WeakDocumentPtr>& all_frames,
       UserNoteMetadataSnapshot metadata_snapshot);
   virtual void OnNoteMetadataFetched(
-      const std::vector<content::RenderFrameHost*>& all_frames,
+      const std::vector<content::WeakDocumentPtr>& all_frames,
       UserNoteMetadataSnapshot metadata_snapshot);
   virtual void OnNoteModelsFetched(
       const IdSet& new_notes,
diff --git a/components/user_notes/browser/user_note_service_unittest.cc b/components/user_notes/browser/user_note_service_unittest.cc
index 2ba12d5..d5904cb 100644
--- a/components/user_notes/browser/user_note_service_unittest.cc
+++ b/components/user_notes/browser/user_note_service_unittest.cc
@@ -140,13 +140,12 @@
 
   MOCK_METHOD(void,
               OnNoteMetadataFetchedForNavigation,
-              (const std::vector<content::RenderFrameHost*>& all_frames,
-               const content::RenderFrameHost* navigated_frame,
+              (const std::vector<content::WeakDocumentPtr>& all_frames,
                UserNoteMetadataSnapshot metadata_snapshot),
               (override));
   MOCK_METHOD(void,
               OnNoteMetadataFetched,
-              (const std::vector<content::RenderFrameHost*>& all_frames,
+              (const std::vector<content::WeakDocumentPtr>& all_frames,
                UserNoteMetadataSnapshot metadata_snapshot),
               (override));
   MOCK_METHOD(void,
@@ -172,15 +171,14 @@
   }
 
   void CallBaseClassOnNoteMetadataFetchedForNavigation(
-      const std::vector<content::RenderFrameHost*>& all_frames,
-      const content::RenderFrameHost* navigated_frame,
+      const std::vector<content::WeakDocumentPtr>& all_frames,
       UserNoteMetadataSnapshot metadata_snapshot) {
     UserNoteService::OnNoteMetadataFetchedForNavigation(
-        all_frames, navigated_frame, std::move(metadata_snapshot));
+        all_frames, std::move(metadata_snapshot));
   }
 
   void CallBaseClassOnNoteMetadataFetched(
-      const std::vector<content::RenderFrameHost*>& all_frames,
+      const std::vector<content::WeakDocumentPtr>& all_frames,
       UserNoteMetadataSnapshot metadata_snapshot) {
     UserNoteService::OnNoteMetadataFetched(all_frames,
                                            std::move(metadata_snapshot));
@@ -285,6 +283,14 @@
     return frames;
   }
 
+  std::vector<content::WeakDocumentPtr> GetAllFramesInUseAsWeakPtr() {
+    std::vector<content::WeakDocumentPtr> weak_documents;
+    for (content::RenderFrameHost* rfh : GetAllFramesInUse()) {
+      weak_documents.emplace_back(rfh->GetWeakDocumentPtr());
+    }
+    return weak_documents;
+  }
+
   raw_ptr<MockUserNoteService> mock_service_;
   raw_ptr<MockUserNoteServiceDelegate> service_delegate_;
   raw_ptr<MockUserNoteStorage> storage_;
@@ -531,15 +537,12 @@
   EXPECT_CALL(*storage_, GetNotesById).Times(0);
 
   // Configure service mock.
-  std::vector<content::RenderFrameHost*> all_frames_result;
-  const content::RenderFrameHost* navigated_frame_result;
+  std::vector<content::WeakDocumentPtr> all_frames_result;
   EXPECT_CALL(*mock_service_, OnNoteMetadataFetchedForNavigation)
       .Times(1)
-      .WillOnce([&](const std::vector<content::RenderFrameHost*>& all_frames,
-                    const content::RenderFrameHost* navigated_frame,
+      .WillOnce([&](const std::vector<content::WeakDocumentPtr>& all_frames,
                     UserNoteMetadataSnapshot metadata_snapshot) {
         all_frames_result.assign(all_frames.begin(), all_frames.end());
-        navigated_frame_result = navigated_frame;
       });
   EXPECT_CALL(*mock_service_, OnNoteMetadataFetched).Times(0);
   EXPECT_CALL(*mock_service_, OnNoteModelsFetched).Times(0);
@@ -553,8 +556,10 @@
   // Mocks ensure callbacks are invoked synchronously, so expectations can be
   // immediately verified.
   ASSERT_EQ(all_frames_result.size(), 1u);
-  EXPECT_EQ(all_frames_result[0], frame);
-  EXPECT_EQ(navigated_frame_result, frame);
+  content::RenderFrameHost* rfh =
+      all_frames_result[0].AsRenderFrameHostIfValid();
+  EXPECT_NE(rfh, nullptr);
+  EXPECT_EQ(rfh, frame);
 
   const UserNoteStorage::UrlSet& requested_urls =
       storage_->requested_metadata_urls();
@@ -621,7 +626,7 @@
 
   // Simulate the service receiving the metadata snapshot after a navigation.
   note_service_->OnNoteMetadataFetchedForNavigation(
-      GetAllFramesInUse(), GetAllFramesInUse()[0], std::move(snapshot));
+      GetAllFramesInUseAsWeakPtr(), std::move(snapshot));
 }
 
 // After a navigation to a document that has user notes, but isn't in the
@@ -672,7 +677,7 @@
 
   // Simulate the service receiving the metadata snapshot after a navigation.
   note_service_->OnNoteMetadataFetchedForNavigation(
-      GetAllFramesInUse(), GetAllFramesInUse()[0], std::move(snapshot));
+      GetAllFramesInUseAsWeakPtr(), std::move(snapshot));
 }
 
 // After a navigation to a document that doesn't have user notes but is in the
@@ -735,7 +740,7 @@
   // Simulate the service receiving the empty metadata snapshot after a
   // navigation.
   note_service_->OnNoteMetadataFetchedForNavigation(
-      GetAllFramesInUse(), GetAllFramesInUse()[0], UserNoteMetadataSnapshot());
+      GetAllFramesInUseAsWeakPtr(), UserNoteMetadataSnapshot());
 }
 
 // After a navigation to a document that doesn't have user notes, but isn't in
@@ -788,7 +793,7 @@
   // Simulate the service receiving the empty metadata snapshot after a
   // navigation.
   note_service_->OnNoteMetadataFetchedForNavigation(
-      GetAllFramesInUse(), GetAllFramesInUse()[0], UserNoteMetadataSnapshot());
+      GetAllFramesInUseAsWeakPtr(), UserNoteMetadataSnapshot());
 }
 
 // Tests that the service requests the right models from the storage after
@@ -868,7 +873,7 @@
 
   // Simulate the storage returning the metadata snapshot to the service
   // callback.
-  note_service_->OnNoteMetadataFetched(GetAllFramesInUse(),
+  note_service_->OnNoteMetadataFetched(GetAllFramesInUseAsWeakPtr(),
                                        std::move(snapshot));
 
   // Mocks ensure callbacks are invoked synchronously, so expectations can be
diff --git a/components/user_notes/browser/user_note_utils.cc b/components/user_notes/browser/user_note_utils.cc
index 2e6f029..f60da07e 100644
--- a/components/user_notes/browser/user_note_utils.cc
+++ b/components/user_notes/browser/user_note_utils.cc
@@ -11,13 +11,14 @@
 #include "components/user_notes/interfaces/user_note_metadata_snapshot.h"
 #include "components/user_notes/model/user_note_metadata.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/weak_document_ptr.h"
 #include "url/gurl.h"
 
 namespace user_notes {
 
 std::vector<std::unique_ptr<FrameUserNoteChanges>> CalculateNoteChanges(
     const UserNoteService& note_service,
-    const std::vector<content::RenderFrameHost*>& rfhs,
+    const std::vector<content::WeakDocumentPtr>& documents,
     const UserNoteMetadataSnapshot& metadata_snapshot) {
   std::vector<std::unique_ptr<FrameUserNoteChanges>> result;
 
@@ -25,7 +26,14 @@
   // where there's no entry in the metadata snapshot for a frame's URL.
   UserNoteMetadataSnapshot::IdToMetadataMap empty_map;
 
-  for (content::RenderFrameHost* rfh : rfhs) {
+  for (const content::WeakDocumentPtr& document : documents) {
+    content::RenderFrameHost* rfh = document.AsRenderFrameHostIfValid();
+
+    if (!rfh) {
+      // The frame is no longer valid.
+      continue;
+    }
+
     // Notes should only be processed for the primary page.
     DCHECK(rfh->GetMainFrame()->IsInPrimaryMainFrame());
 
diff --git a/components/user_notes/browser/user_note_utils.h b/components/user_notes/browser/user_note_utils.h
index 1b190d56..ecc95c8 100644
--- a/components/user_notes/browser/user_note_utils.h
+++ b/components/user_notes/browser/user_note_utils.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 namespace content {
-class RenderFrameHost;
+class WeakDocumentPtr;
 }  // namespace content
 
 namespace user_notes {
@@ -25,7 +25,7 @@
 // don't match the metadata.
 std::vector<std::unique_ptr<FrameUserNoteChanges>> CalculateNoteChanges(
     const UserNoteService& note_service,
-    const std::vector<content::RenderFrameHost*>& rfhs,
+    const std::vector<content::WeakDocumentPtr>& documents,
     const UserNoteMetadataSnapshot& metadata_snapshot);
 
 }  // namespace user_notes
diff --git a/components/user_notes/browser/user_note_utils_unittest.cc b/components/user_notes/browser/user_note_utils_unittest.cc
index c0ca8b3..7600daa 100644
--- a/components/user_notes/browser/user_note_utils_unittest.cc
+++ b/components/user_notes/browser/user_note_utils_unittest.cc
@@ -693,16 +693,16 @@
   }
 
   // Round up the test frames as if they were the user's open tabs.
-  std::vector<content::RenderFrameHost*> frame_hosts;
-  frame_hosts.reserve(frame_to_config_.size());
+  std::vector<content::WeakDocumentPtr> weak_documents;
+  weak_documents.reserve(frame_to_config_.size());
   for (const auto& config_it : frame_to_config_) {
-    frame_hosts.push_back(config_it.first);
+    weak_documents.emplace_back(config_it.first->GetWeakDocumentPtr());
   }
 
   // Calculate the diff between the notes in the frames and the notes in the
   // metadata.
   const std::vector<std::unique_ptr<FrameUserNoteChanges>>& actual_diffs =
-      CalculateNoteChanges(*note_service_, frame_hosts, metadata_snapshot);
+      CalculateNoteChanges(*note_service_, weak_documents, metadata_snapshot);
 
   std::unordered_set<content::RenderFrameHost*> frames_with_diff;
   for (const std::unique_ptr<FrameUserNoteChanges>& diff : actual_diffs) {
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index bc93203..03c100c 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -350,18 +350,14 @@
       local_state_(local_state),
       state_manager_(state_manager),
       policy_pref_service_(local_state),
-      initial_request_completed_(false),
-      delta_error_since_last_success_(false),
       resource_request_allowed_notifier_(std::move(notifier)),
-      request_count_(0),
       safe_seed_manager_(local_state),
       field_trial_creator_(client_.get(),
                            std::make_unique<VariationsSeedStore>(
                                local_state,
                                MaybeImportFirstRunSeed(local_state),
                                /*signature_verification_enabled=*/true),
-                           ui_string_overrider),
-      last_request_was_http_retry_(false) {
+                           ui_string_overrider) {
   DCHECK(client_);
   DCHECK(resource_request_allowed_notifier_);
 
@@ -764,6 +760,13 @@
   const bool is_first_request = !initial_request_completed_;
   initial_request_completed_ = true;
 
+  const base::TimeTicks now = base::TimeTicks::Now();
+  if (is_first_request &&
+      !local_state_->HasPrefPath(prefs::kVariationsSeedSignature)) {
+    base::UmaHistogramTimes("Variations.SeedFetchTimeOnFirstRun",
+                            now - last_request_started_time_);
+  }
+
   const network::mojom::URLResponseHead* response_info =
       pending_seed_request_->ResponseInfo();
   const scoped_refptr<net::HttpResponseHeaders> headers =
@@ -819,7 +822,6 @@
   if (headers->GetDateValue(&response_date)) {
     DCHECK(!response_date.is_null());
 
-    const base::TimeTicks now = base::TimeTicks::Now();
     const base::TimeDelta latency = now - last_request_started_time_;
     client_->GetNetworkTimeTracker()->UpdateNetworkTime(
         response_date, base::Seconds(kServerTimeResolutionInSeconds), latency,
diff --git a/components/variations/service/variations_service.h b/components/variations/service/variations_service.h
index 60a8332..22a625ce 100644
--- a/components/variations/service/variations_service.h
+++ b/components/variations/service/variations_service.h
@@ -384,11 +384,11 @@
   GURL insecure_variations_server_url_;
 
   // Tracks whether the initial request to the variations server had completed.
-  bool initial_request_completed_;
+  bool initial_request_completed_ = false;
 
   // Tracks whether any errors resolving delta compression were encountered
   // since the last time a seed was fetched successfully.
-  bool delta_error_since_last_success_;
+  bool delta_error_since_last_success_ = false;
 
   // Helper class used to tell this service if it's allowed to make network
   // resource requests.
@@ -400,7 +400,7 @@
   base::TimeTicks last_request_started_time_;
 
   // The number of requests to the variations server that have been performed.
-  int request_count_;
+  int request_count_ = 0;
 
   // List of observers of the VariationsService.
   base::ObserverList<Observer>::Unchecked observer_list_;
@@ -412,7 +412,7 @@
   VariationsFieldTrialCreator field_trial_creator_;
 
   // True if the last request was a retry over http.
-  bool last_request_was_http_retry_;
+  bool last_request_was_http_retry_ = false;
 
   // When not empty, contains an override for the os name in the variations
   // server url.
diff --git a/components/viz/service/display_embedder/compositor_gpu_thread.cc b/components/viz/service/display_embedder/compositor_gpu_thread.cc
index a93e1095..8ba3ef7 100644
--- a/components/viz/service/display_embedder/compositor_gpu_thread.cc
+++ b/components/viz/service/display_embedder/compositor_gpu_thread.cc
@@ -23,6 +23,7 @@
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(ENABLE_VULKAN)
@@ -108,7 +109,8 @@
   // Create a new share group. Note that this share group is different from the
   // share group which gpu main thread uses.
   auto share_group = base::MakeRefCounted<gl::GLShareGroup>();
-  auto surface = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  auto surface =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
 
   const auto& gpu_preferences = gpu_channel_manager_->gpu_preferences();
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_dependency.h b/components/viz/service/display_embedder/skia_output_surface_dependency.h
index 0683b7e..25deb86f 100644
--- a/components/viz/service/display_embedder/skia_output_surface_dependency.h
+++ b/components/viz/service/display_embedder/skia_output_surface_dependency.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_DEPENDENCY_H_
 
 #include <memory>
+#include <utility>
 
 #include "base/callback.h"
 #include "base/callback_helpers.h"
diff --git a/components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc b/components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc
index 2f76fe7..c372de46 100644
--- a/components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc
@@ -17,6 +17,7 @@
 #include "gpu/command_buffer/service/scheduler.h"
 #include "gpu/command_buffer/service/scheduler_sequence.h"
 #include "gpu/ipc/service/image_transport_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace viz {
@@ -107,10 +108,11 @@
     base::WeakPtr<gpu::ImageTransportSurfaceDelegate> stub,
     gl::GLSurfaceFormat format) {
   if (IsOffscreen()) {
-    return gl::init::CreateOffscreenGLSurfaceWithFormat(gfx::Size(), format);
+    return gl::init::CreateOffscreenGLSurfaceWithFormat(gl::GetDefaultDisplay(),
+                                                        gfx::Size(), format);
   } else {
     return gpu::ImageTransportSurface::CreateNativeSurface(
-        stub, surface_handle_, format);
+        gl::GetDefaultDisplay(), stub, surface_handle_, format);
   }
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index 784ba0b..117cdbd 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -5,13 +5,14 @@
 #ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_IMPL_ON_GPU_H_
 #define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_IMPL_ON_GPU_H_
 
-#include <deque>
-#include <map>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/containers/circular_deque.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/threading/thread_checker.h"
@@ -507,7 +508,7 @@
   // Overlayed render passes need to keep their write access open until after
   // submit. These will be set in FinishPaintRenderPass() if |is_overlay| is
   // true and destroyed in PostSubmit().
-  std::map<gpu::Mailbox, OverlayPassAccess> overlay_pass_accesses_;
+  base::flat_map<gpu::Mailbox, OverlayPassAccess> overlay_pass_accesses_;
 
   absl::optional<OverlayProcessorInterface::OutputSurfaceOverlayPlane>
       output_surface_plane_;
@@ -532,7 +533,7 @@
   // Pending release fence callbacks. These callbacks can be delayed if Vulkan
   // external semaphore type has copy transference, which means importing
   // semaphores has to be delayed until submission.
-  std::deque<std::pair<GrBackendSemaphore,
+  base::circular_deque<std::pair<GrBackendSemaphore,
                        base::OnceCallback<void(gfx::GpuFenceHandle)>>>
       pending_release_fence_cbs_;
 
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 8412124..0278b27 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -509,7 +509,7 @@
 
 void GpuServiceImpl::UpdateGPUInfoGL() {
   DCHECK(main_runner_->BelongsToCurrentThread());
-  gpu::CollectGraphicsInfoGL(&gpu_info_);
+  gpu::CollectGraphicsInfoGL(&gpu_info_, gl::GetDefaultDisplay());
   gpu_host_->DidUpdateGPUInfo(gpu_info_);
 }
 
diff --git a/components/viz/service/gl/gpu_service_impl_unittest.cc b/components/viz/service/gl/gpu_service_impl_unittest.cc
index 00b1846..290b0e4 100644
--- a/components/viz/service/gl/gpu_service_impl_unittest.cc
+++ b/components/viz/service/gl/gpu_service_impl_unittest.cc
@@ -23,6 +23,7 @@
 #include "services/viz/public/mojom/gpu.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace viz {
@@ -133,7 +134,7 @@
   std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
   gpu_service()->InitializeWithHost(
       std::move(gpu_host_proxy), gpu::GpuProcessActivityFlags(),
-      gl::init::CreateOffscreenGLSurface(gfx::Size()),
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
       /*sync_point_manager=*/nullptr, /*shared_image_manager=*/nullptr,
       /*shutdown_event=*/nullptr);
   gpu_service_remote.FlushForTesting();
@@ -167,7 +168,7 @@
   std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
   gpu_service()->InitializeWithHost(
       std::move(gpu_host_proxy), gpu::GpuProcessActivityFlags(),
-      gl::init::CreateOffscreenGLSurface(gfx::Size()),
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
       /*sync_point_manager=*/nullptr, /*shared_image_manager=*/nullptr,
       /*shutdown_event=*/nullptr);
   gpu_service_remote.FlushForTesting();
diff --git a/components/viz/test/test_gpu_service_holder.cc b/components/viz/test/test_gpu_service_holder.cc
index 6460c2b..4c9da0b 100644
--- a/components/viz/test/test_gpu_service_holder.cc
+++ b/components/viz/test/test_gpu_service_holder.cc
@@ -27,6 +27,7 @@
 #include "gpu/ipc/service/gpu_watchdog_thread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(ENABLE_VULKAN)
@@ -306,7 +307,7 @@
   std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
   gpu_service_->InitializeWithHost(
       std::move(gpu_host_proxy), gpu::GpuProcessActivityFlags(),
-      gl::init::CreateOffscreenGLSurface(gfx::Size()),
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
       /*sync_point_manager=*/nullptr, /*shared_image_manager=*/nullptr,
       /*shutdown_event=*/nullptr);
 
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index f6205a9..c5177c9 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -4,8 +4,11 @@
 
 #include "content/browser/interest_group/ad_auction_service_impl.h"
 
+#include <map>
 #include <set>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include "base/check.h"
 #include "base/containers/contains.h"
@@ -19,13 +22,17 @@
 #include "content/browser/interest_group/auction_runner.h"
 #include "content/browser/interest_group/auction_worklet_manager.h"
 #include "content/browser/interest_group/interest_group_manager_impl.h"
+#include "content/browser/private_aggregation/private_aggregation_manager.h"
 #include "content/browser/renderer_host/page_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/common/aggregatable_report.mojom.h"
+#include "content/common/private_aggregation_host.mojom.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_client.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/http/http_response_headers.h"
@@ -400,7 +407,9 @@
           &GetInterestGroupManager().auction_process_manager(),
           GetTopWindowOrigin(),
           origin(),
-          this) {}
+          this),
+      private_aggregation_manager_(PrivateAggregationManager::GetManager(
+          *render_frame_host.GetBrowserContext())) {}
 
 AdAuctionServiceImpl::~AdAuctionServiceImpl() {
   while (!auctions_.empty()) {
@@ -448,6 +457,34 @@
       origin);
 }
 
+void AdAuctionServiceImpl::SendPrivateAggregationRequests(
+    std::map<url::Origin,
+             std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
+        private_aggregation_requests) const {
+  if (!private_aggregation_manager_) {
+    return;
+  }
+
+  for (auto& [origin, requests] : private_aggregation_requests) {
+    mojo::Remote<mojom::PrivateAggregationHost> remote;
+    if (!private_aggregation_manager_->BindNewReceiver(
+            origin, PrivateAggregationBudgetKey::Api::kFledge,
+            remote.BindNewPipeAndPassReceiver())) {
+      continue;
+    }
+
+    for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
+         requests) {
+      DCHECK(request);
+      std::vector<mojom::AggregatableReportHistogramContributionPtr>
+          contributions;
+      contributions.push_back(std::move(request->contribution));
+      remote->SendHistogramReport(std::move(contributions),
+                                  request->aggregation_mode);
+    }
+  }
+}
+
 void AdAuctionServiceImpl::OnAuctionComplete(
     RunAdAuctionCallback callback,
     AuctionRunner* auction,
@@ -458,6 +495,9 @@
     std::vector<GURL> debug_loss_report_urls,
     std::vector<GURL> debug_win_report_urls,
     ReportingMetadata ad_beacon_map,
+    std::map<url::Origin,
+             std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
+        private_aggregation_requests,
     std::vector<std::string> errors) {
   // Delete the AuctionRunner. Since all arguments are passed by value, they're
   // all safe to used after this has been done.
@@ -472,6 +512,8 @@
         base::StrCat({"Worklet error: ", error}));
   }
 
+  SendPrivateAggregationRequests(std::move(private_aggregation_requests));
+
   auto* auction_result_metrics =
       AdAuctionResultMetrics::GetForPage(render_frame_host().GetPage());
 
diff --git a/content/browser/interest_group/ad_auction_service_impl.h b/content/browser/interest_group/ad_auction_service_impl.h
index 81e9d91..236de404 100644
--- a/content/browser/interest_group/ad_auction_service_impl.h
+++ b/content/browser/interest_group/ad_auction_service_impl.h
@@ -5,16 +5,20 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_AD_AUCTION_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_AD_AUCTION_SERVICE_IMPL_H_
 
+#include <map>
 #include <memory>
 #include <set>
+#include <vector>
 
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/memory/raw_ptr.h"
 #include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
 #include "content/browser/interest_group/auction_runner.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 "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
@@ -32,6 +36,7 @@
 class InterestGroupManagerImpl;
 class RenderFrameHost;
 class RenderFrameHostImpl;
+class PrivateAggregationManager;
 
 // Implements the AdAuctionService service called by Blink code.
 class CONTENT_EXPORT AdAuctionServiceImpl final
@@ -102,6 +107,15 @@
                                      interest_group_api_operation,
                                  const url::Origin& origin) const;
 
+  // Sends requests for the Private Aggregation API to its manager. Does nothing
+  // if the manager is unavailable. The map should be keyed by reporting origin
+  // of the corresponding requests.
+  void SendPrivateAggregationRequests(
+      std::map<
+          url::Origin,
+          std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
+          private_aggregation_requests) const;
+
   // Deletes `auction`.
   void OnAuctionComplete(
       RunAdAuctionCallback callback,
@@ -113,6 +127,10 @@
       std::vector<GURL> debug_loss_report_urls,
       std::vector<GURL> debug_win_report_urls,
       ReportingMetadata ad_beacon_map,
+      std::map<
+          url::Origin,
+          std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>>
+          private_aggregation_requests,
       std::vector<std::string> errors);
 
   InterestGroupManagerImpl& GetInterestGroupManager() const;
@@ -139,6 +157,10 @@
   AuctionWorkletManager auction_worklet_manager_;
 
   std::set<std::unique_ptr<AuctionRunner>, base::UniquePtrComparator> auctions_;
+
+  // Safe to keep as it will outlive the associated `RenderFrameHost` and
+  // therefore `this`, being tied to the lifetime of the `StoragePartition`.
+  const raw_ptr<PrivateAggregationManager> private_aggregation_manager_;
 };
 
 }  // namespace content
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index afea172a..347a6eb 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -19,15 +19,19 @@
 #include "base/synchronization/lock.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/thread_annotations.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/buildflag.h"
+#include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
 #include "content/browser/interest_group/auction_process_manager.h"
 #include "content/browser/interest_group/interest_group_manager_impl.h"
 #include "content/browser/interest_group/interest_group_storage.h"
+#include "content/browser/private_aggregation/private_aggregation_manager_impl.h"
+#include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
@@ -4967,6 +4971,82 @@
   EXPECT_EQ(99, GetPriority(kOriginA, kInterestGroupName));
 }
 
+TEST_F(AdAuctionServiceImplTest, PrivateAggregationReportForwarded) {
+  constexpr char kBiddingScript[] = R"(
+function generateBid(
+    interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals,
+    browserSignals) {
+  privateAggregation.sendHistogramReport({bucket: 1, value: 2});
+  return {'ad': 'example', 'bid': 1, 'render': 'https://example.com/render'};
+}
+)";
+
+  constexpr char kDecisionScript[] = R"(
+function scoreAd(
+    adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals) {
+  return bid;
+}
+)";
+
+  class TestPrivateAggregationManagerImpl
+      : public PrivateAggregationManagerImpl {
+   public:
+    TestPrivateAggregationManagerImpl(
+        std::unique_ptr<PrivateAggregationBudgeter> budgeter,
+        std::unique_ptr<PrivateAggregationHost> host)
+        : PrivateAggregationManagerImpl(std::move(budgeter),
+                                        std::move(host),
+                                        /*storage_partition=*/nullptr) {}
+  };
+
+  base::MockRepeatingCallback<void(AggregatableReportRequest,
+                                   PrivateAggregationBudgetKey)>
+      mock_callback;
+
+  static_cast<StoragePartitionImpl*>(
+      browser_context()->GetDefaultStoragePartition())
+      ->OverridePrivateAggregationManagerForTesting(
+          std::make_unique<TestPrivateAggregationManagerImpl>(
+              std::make_unique<MockPrivateAggregationBudgeter>(),
+              std::make_unique<PrivateAggregationHost>(
+                  /*on_report_request_received=*/mock_callback.Get())));
+
+  network_responder_->RegisterScriptResponse(kBiddingUrlPath, kBiddingScript);
+  network_responder_->RegisterScriptResponse(kDecisionUrlPath, kDecisionScript);
+
+  blink::InterestGroup interest_group = CreateInterestGroup();
+  interest_group.bidding_url = kUrlA.Resolve(kBiddingUrlPath);
+  interest_group.priority = 2;
+  interest_group.ads.emplace();
+  blink::InterestGroup::Ad ad(
+      /*render_url=*/GURL("https://example.com/render"),
+      /*metadata=*/absl::nullopt);
+  interest_group.ads->emplace_back(std::move(ad));
+  JoinInterestGroupAndFlush(interest_group);
+
+  blink::AuctionConfig auction_config;
+  auction_config.seller = kOriginA;
+  auction_config.decision_logic_url = kUrlA.Resolve(kDecisionUrlPath);
+  auction_config.non_shared_params.interest_group_buyers = {kOriginA};
+
+  EXPECT_CALL(mock_callback, Run)
+      .WillRepeatedly(
+          testing::Invoke([this](AggregatableReportRequest request,
+                                 PrivateAggregationBudgetKey budget_key) {
+            ASSERT_EQ(request.payload_contents().contributions.size(), 1u);
+            EXPECT_EQ(request.payload_contents().contributions[0].bucket, 1);
+            EXPECT_EQ(request.payload_contents().contributions[0].value, 2);
+            EXPECT_EQ(request.shared_info().reporting_origin, kOriginA);
+            EXPECT_EQ(budget_key.api(),
+                      PrivateAggregationBudgetKey::Api::kFledge);
+            EXPECT_EQ(budget_key.origin(), kOriginA);
+          }));
+
+  absl::optional<GURL> auction_result =
+      RunAdAuctionAndFlush(std::move(auction_config));
+  ASSERT_NE(auction_result, absl::nullopt);
+}
+
 class AdAuctionServiceImplNumAuctionLimitTest
     : public AdAuctionServiceImplTest {
  public:
diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc
index fa20501..2c205f4 100644
--- a/content/browser/interest_group/auction_runner.cc
+++ b/content/browser/interest_group/auction_runner.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/callback.h"
@@ -14,6 +15,7 @@
 #include "base/time/time.h"
 #include "content/browser/interest_group/interest_group_manager_impl.h"
 #include "content/public/browser/content_browser_client.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "services/network/public/mojom/client_security_state.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/interest_group/auction_config.h"
@@ -54,13 +56,14 @@
 
   UpdateInterestGroupsPostAuction();
 
-  std::move(callback_).Run(this, /*render_url=*/absl::nullopt,
-                           /*winning_group_key=*/absl::nullopt,
-                           /*ad_component_urls=*/{},
-                           /*report_urls=*/{},
-                           std::move(debug_loss_report_urls),
-                           std::move(debug_win_report_urls),
-                           /*ad_beacon_map=*/{}, auction_.TakeErrors());
+  std::move(callback_).Run(
+      this, /*render_url=*/absl::nullopt,
+      /*winning_group_key=*/absl::nullopt,
+      /*ad_component_urls=*/{},
+      /*report_urls=*/{}, std::move(debug_loss_report_urls),
+      std::move(debug_win_report_urls),
+      /*ad_beacon_map=*/{}, auction_.TakePrivateAggregationRequests(),
+      auction_.TakeErrors());
 }
 
 AuctionRunner::AuctionRunner(
@@ -156,7 +159,8 @@
       this, std::move(winning_group_key), auction_.top_bid()->bid->render_url,
       auction_.top_bid()->bid->ad_components, auction_.TakeReportUrls(),
       std::move(debug_loss_report_urls), std::move(debug_win_report_urls),
-      auction_.TakeAdBeaconMap(), auction_.TakeErrors());
+      auction_.TakeAdBeaconMap(), auction_.TakePrivateAggregationRequests(),
+      auction_.TakeErrors());
 }
 
 void AuctionRunner::UpdateInterestGroupsPostAuction() {
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index 04a872d8..8c2b9d9 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_AUCTION_RUNNER_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_AUCTION_RUNNER_H_
 
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
@@ -14,6 +15,7 @@
 #include "content/browser/interest_group/auction_worklet_manager.h"
 #include "content/browser/interest_group/interest_group_auction.h"
 #include "content/common/content_export.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
 #include "services/network/public/mojom/client_security_state.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/interest_group/interest_group.h"
@@ -38,6 +40,9 @@
 // the code to assign unique tracing IDs is not threadsafe.
 class CONTENT_EXPORT AuctionRunner {
  public:
+  using PrivateAggregationRequests =
+      std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
   // Invoked when a FLEDGE auction is complete.
   //
   // `winning_group_id` owner and name of the winning interest group (if any).
@@ -68,6 +73,8 @@
       std::vector<GURL> debug_loss_report_urls,
       std::vector<GURL> debug_win_report_urls,
       ReportingMetadata ad_beacon_map,
+      std::map<url::Origin, PrivateAggregationRequests>
+          private_aggregation_requests,
       std::vector<std::string> errors)>;
 
   // Returns true if `origin` is allowed to use the interest group API. Will be
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index 00e7062..94107522 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -34,12 +34,14 @@
 #include "content/browser/interest_group/interest_group_auction.h"
 #include "content/browser/interest_group/interest_group_manager_impl.h"
 #include "content/browser/interest_group/interest_group_storage.h"
+#include "content/common/aggregatable_report.mojom-shared.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/auction_worklet_service_impl.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
 #include "content/services/auction_worklet/worklet_test_util.h"
@@ -64,6 +66,7 @@
 using InterestGroupKey = blink::InterestGroupKey;
 using PostAuctionSignals = InterestGroupAuction::PostAuctionSignals;
 using blink::mojom::ReportingDestination;
+using PrivateAggregationRequests = AuctionRunner::PrivateAggregationRequests;
 
 const std::string kBidder1Name{"Ad Platform"};
 const char kBidder1DebugLossReportUrl[] =
@@ -99,6 +102,49 @@
     "topLevelWinningBid=${topLevelWinningBid},"
     "topLevelMadeWinningBid=${topLevelMadeWinningBid}";
 
+const auction_worklet::mojom::PrivateAggregationRequestPtr
+    kExpectedGenerateBidPrivateAggregationRequest =
+        auction_worklet::mojom::PrivateAggregationRequest::New(
+            content::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/1,
+                /*value=*/2),
+            content::mojom::AggregationServiceMode::kDefault);
+
+const auction_worklet::mojom::PrivateAggregationRequestPtr
+    kExpectedReportWinPrivateAggregationRequest =
+        auction_worklet::mojom::PrivateAggregationRequest::New(
+            content::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/3,
+                /*value=*/4),
+            content::mojom::AggregationServiceMode::kDefault);
+
+const auction_worklet::mojom::PrivateAggregationRequestPtr
+    kExpectedScoreAdPrivateAggregationRequest =
+        auction_worklet::mojom::PrivateAggregationRequest::New(
+            content::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/5,
+                /*value=*/6),
+            content::mojom::AggregationServiceMode::kDefault);
+
+const auction_worklet::mojom::PrivateAggregationRequestPtr
+    kExpectedReportResultPrivateAggregationRequest =
+        auction_worklet::mojom::PrivateAggregationRequest::New(
+            content::mojom::AggregatableReportHistogramContribution::New(
+                /*bucket=*/7,
+                /*value=*/8),
+            content::mojom::AggregationServiceMode::kDefault);
+
+// Helper to avoid excess boilerplate.
+template <typename... Ts>
+auto ElementsAreRequests(Ts&... requests) {
+  static_assert(
+      std::conjunction<std::is_same<
+          std::remove_const_t<Ts>,
+          auction_worklet::mojom::PrivateAggregationRequestPtr>...>::value);
+  // Need to use `std::ref` as `mojo::StructPtr`s are move-only.
+  return testing::UnorderedElementsAre(testing::Eq(std::ref(requests))...);
+}
+
 // 0 `num_component_urls` means no component URLs, as opposed to an empty list
 // (which isn't tested at this layer).
 std::string MakeBidScript(const url::Origin& seller,
@@ -236,6 +282,7 @@
       }
       if (browserSignals.dataVersion !== undefined)
         throw new Error(`wrong dataVersion (${browserSignals.dataVersion})`);
+      privateAggregation.sendHistogramReport({bucket: 1, value: 2});
       return result;
     }
 
@@ -321,6 +368,7 @@
       registerAdBeacon({
         "click": "https://buyer-reporting.example.com/" + 2*bid,
       });
+      privateAggregation.sendHistogramReport({bucket: 3, value: 4});
     }
   )";
   return base::StringPrintf(
@@ -460,6 +508,7 @@
         forDebuggingOnly.reportAdAuctionWin(
             buildDebugReportUrl(debugWinReportUrl) + bid);
       }
+      privateAggregation.sendHistogramReport({bucket: 5, value: 6});
 
       adMetadata.fromComponentAuction = true;
 
@@ -544,6 +593,8 @@
         }
         sendReportTo(sendReportUrl + browserSignals.bid);
       }
+      privateAggregation.sendHistogramReport({bucket: 7, value: 8});
+
       return browserSignals;
     }
 
@@ -600,6 +651,7 @@
 
 const char kAuctionScriptRejects2[] = R"(
   function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+    privateAggregation.sendHistogramReport({bucket: 5, value: 6});
     if (bid === 2)
       return -1;
     return bid + 1;
@@ -608,6 +660,7 @@
 
 const char kBasicReportResult[] = R"(
   function reportResult(auctionConfig, browserSignals) {
+    privateAggregation.sendHistogramReport({bucket: 7, value: 8});
     sendReportTo("https://reporting.example.com/" + browserSignals.bid);
     registerAdBeacon({
       "click": "https://reporting.example.com/" + 2*browserSignals.bid,
@@ -905,7 +958,8 @@
       const absl::optional<uint32_t>& bidding_signals_data_version =
           absl::nullopt,
       const absl::optional<GURL>& debug_loss_report_url = absl::nullopt,
-      const absl::optional<GURL>& debug_win_report_url = absl::nullopt) {
+      const absl::optional<GURL>& debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests pa_requests = {}) {
     WaitForGenerateBid();
 
     if (!bid.has_value()) {
@@ -917,6 +971,7 @@
                /*debug_win_report_url=*/absl::nullopt,
                /*set_priority=*/0,
                /*has_set_priority=*/false,
+               /*pa_requests=*/std::move(pa_requests),
                /*errors=*/std::vector<std::string>());
       return;
     }
@@ -929,6 +984,7 @@
              debug_win_report_url,
              /*set_priority=*/0,
              /*has_set_priority=*/false,
+             /*pa_requests=*/std::move(pa_requests),
              /*errors=*/std::vector<std::string>());
   }
 
@@ -945,10 +1001,11 @@
 
   void InvokeReportWinCallback(
       absl::optional<GURL> report_url = absl::nullopt,
-      base::flat_map<std::string, GURL> ad_beacon_map = {}) {
+      base::flat_map<std::string, GURL> ad_beacon_map = {},
+      PrivateAggregationRequests pa_requests = {}) {
     DCHECK(report_win_callback_);
     std::move(report_win_callback_)
-        .Run(report_url, ad_beacon_map,
+        .Run(report_url, ad_beacon_map, std::move(pa_requests),
              /*errors=*/std::vector<std::string>());
   }
 
@@ -1109,11 +1166,12 @@
   void InvokeReportResultCallback(
       absl::optional<GURL> report_url = absl::nullopt,
       base::flat_map<std::string, GURL> ad_beacon_map = {},
+      PrivateAggregationRequests pa_requests = {},
       std::vector<std::string> errors = {}) {
     DCHECK(report_result_callback_);
     std::move(report_result_callback_)
         .Run(/*signals_for_winner=*/absl::nullopt, std::move(report_url),
-             ad_beacon_map, errors);
+             ad_beacon_map, std::move(pa_requests), errors);
   }
 
   void Flush() { receiver_.FlushForTesting(); }
@@ -1421,6 +1479,8 @@
     std::vector<GURL> debug_loss_report_urls;
     std::vector<GURL> debug_win_report_urls;
     ReportingMetadata ad_beacon_map;
+    std::map<url::Origin, PrivateAggregationRequests>
+        private_aggregation_requests;
 
     std::vector<std::string> errors;
 
@@ -1601,6 +1661,8 @@
                          std::vector<GURL> debug_loss_report_urls,
                          std::vector<GURL> debug_win_report_urls,
                          ReportingMetadata ad_beacon_map,
+                         std::map<url::Origin, PrivateAggregationRequests>
+                             private_aggregation_requests,
                          std::vector<std::string> errors) {
     DCHECK(auction_run_loop_);
     DCHECK(!auction_complete_);
@@ -1622,6 +1684,8 @@
     result_.debug_loss_report_urls = std::move(debug_loss_report_urls);
     result_.debug_win_report_urls = std::move(debug_win_report_urls);
     result_.ad_beacon_map = std::move(ad_beacon_map);
+    result_.private_aggregation_requests =
+        std::move(private_aggregation_requests);
 
     // Retrieve bid count and previous wins for kBidder1 (and subsequently
     // kBidder2).
@@ -2422,6 +2486,23 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+
+  EXPECT_THAT(
+      res.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
+
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -2595,6 +2676,21 @@
                 ReportingDestination::kBuyer,
                 testing::ElementsAre(testing::Pair(
                     "click", GURL("https://buyer-reporting.example.com/4"))))));
+    EXPECT_THAT(
+        result_.private_aggregation_requests,
+        testing::UnorderedElementsAre(
+            testing::Pair(kBidder1,
+                          ElementsAreRequests(
+                              kExpectedGenerateBidPrivateAggregationRequest)),
+            testing::Pair(kBidder2,
+                          ElementsAreRequests(
+                              kExpectedGenerateBidPrivateAggregationRequest,
+                              kExpectedReportWinPrivateAggregationRequest)),
+            testing::Pair(
+                kSeller, ElementsAreRequests(
+                             kExpectedScoreAdPrivateAggregationRequest,
+                             kExpectedScoreAdPrivateAggregationRequest,
+                             kExpectedReportResultPrivateAggregationRequest))));
   }
 }
 
@@ -2663,6 +2759,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
 }
 
 TEST_F(AuctionRunnerTest, PauseSeller) {
@@ -2726,6 +2837,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
 }
 
 // A component auction with two successful bids from different components.
@@ -2761,6 +2887,28 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(
+              kComponentSeller1,
+              ElementsAreRequests(kExpectedScoreAdPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller2,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -2801,6 +2949,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -2873,6 +3036,24 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(
+              kComponentSeller1,
+              ElementsAreRequests(kExpectedScoreAdPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -2918,6 +3099,25 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -2947,6 +3147,7 @@
   const char kBidScript[] = R"(
       function generateBid(interestGroup, auctionSignals, perBuyerSignals,
                            trustedBiddingSignals, browserSignals) {
+        privateAggregation.sendHistogramReport({bucket: 1, value: 2});
         if (browserSignals.seller == "https://component.seller1.test") {
           return {ad: [], bid: 1, render: "https://component-bid.test/",
                   allowComponentAuction: true};
@@ -2965,6 +3166,7 @@
       registerAdBeacon({
         "click": "https://buyer-reporting.example.com/" + 2*browserSignals.bid,
       });
+      privateAggregation.sendHistogramReport({bucket: 3, value: 4});
     }
   )";
 
@@ -2972,6 +3174,7 @@
   // on bid and seller, to make sure correct values are plumbed through.
   const std::string kSellerScript = R"(
     function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+      privateAggregation.sendHistogramReport({bucket: 5, value: 6});
       if (auctionConfig.seller == "https://adstuff.publisher1.com")
         return {desirability: 20 + bid, allowComponentAuction: true};
       return {desirability: 10 + bid, allowComponentAuction: true};
@@ -2983,6 +3186,7 @@
       registerAdBeacon({
         "click": auctionConfig.seller + "/" + 2*browserSignals.desirability,
       });
+      privateAggregation.sendHistogramReport({bucket: 7, value: 8});
     }
   )";
 
@@ -3032,6 +3236,22 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(
+              kComponentSeller1,
+              ElementsAreRequests(kExpectedScoreAdPrivateAggregationRequest))));
   // Bid count should only be incremented by 1.
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
@@ -3089,6 +3309,28 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/6"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(
+              kComponentSeller1,
+              ElementsAreRequests(kExpectedScoreAdPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller2,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   // Bid count should only be incremented by 1.
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
@@ -3197,6 +3439,25 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3216,6 +3477,7 @@
   const char kBidScript[] = R"(
       function generateBid(interestGroup, auctionSignals, perBuyerSignals,
                            trustedBiddingSignals, browserSignals) {
+        privateAggregation.sendHistogramReport({bucket: 1, value: 2});
         return {ad: [], bid: 2, render: interestGroup.ads[0].renderUrl,
                 allowComponentAuction: true};
       }
@@ -3226,6 +3488,7 @@
       registerAdBeacon({
         "click": "https://buyer-reporting.example.com/" + 2*browserSignals.bid,
       });
+      privateAggregation.sendHistogramReport({bucket: 3, value: 4});
     }
   )";
 
@@ -3233,6 +3496,7 @@
   // top-level seller signals are null.
   const std::string kComponentSellerScript = R"(
     function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+      privateAggregation.sendHistogramReport({bucket: 5, value: 6});
       return {desirability: 10, allowComponentAuction: true};
     }
 
@@ -3243,6 +3507,7 @@
         "click": auctionConfig.seller + "/" +
                    (browserSignals.topLevelSellerSignals === null),
       });
+      privateAggregation.sendHistogramReport({bucket: 7, value: 8});
     }
   )";
 
@@ -3250,6 +3515,7 @@
   // value.
   const std::string kTopLevelSellerScript = R"(
     function scoreAd(adMetadata, bid, auctionConfig, browserSignals) {
+      privateAggregation.sendHistogramReport({bucket: 5, value: 6});
       return {desirability: 10, allowComponentAuction: true};
     }
 
@@ -3258,6 +3524,7 @@
       registerAdBeacon({
         "click": auctionConfig.seller + "/" + 2 * browserSignals.bid,
       });
+      privateAggregation.sendHistogramReport({bucket: 7, value: 8});
       // Note that there's no return value here.
     }
   )";
@@ -3303,6 +3570,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   CheckHistograms(InterestGroupAuction::AuctionResult::kSuccess,
                   /*expected_interest_groups=*/1, /*expected_owners=*/1,
                   /*expected_sellers=*/2);
@@ -3402,6 +3684,7 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   CheckHistograms(InterestGroupAuction::AuctionResult::kSuccess,
                   /*expected_interest_groups=*/1, /*expected_owners=*/1,
                   /*expected_sellers=*/2);
@@ -3422,6 +3705,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -3455,6 +3739,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -3509,6 +3794,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3536,6 +3836,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -3589,6 +3890,17 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3625,6 +3937,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -3674,6 +3987,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3764,6 +4092,17 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3826,6 +4165,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller1,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   // The bid send to the failing component seller worklet isn't counted,
   // regardless of whether the bid completed before the worklet failed to load.
@@ -3879,6 +4233,17 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -3916,6 +4281,7 @@
   EXPECT_TRUE(res.ad_component_urls.empty());
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(res.private_aggregation_requests.empty());
   EXPECT_EQ(5, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(5, res.bidder2_bid_count);
@@ -3957,6 +4323,7 @@
   EXPECT_TRUE(res.ad_component_urls.empty());
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(res.private_aggregation_requests.empty());
   EXPECT_EQ(5, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(5, res.bidder2_bid_count);
@@ -4006,6 +4373,15 @@
   EXPECT_TRUE(res.ad_component_urls.empty());
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4064,6 +4440,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -4087,6 +4478,7 @@
   EXPECT_TRUE(res.ad_component_urls.empty());
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(res.private_aggregation_requests.empty());
 
   EXPECT_EQ(0, url_loader_factory_.NumPending());
   EXPECT_EQ(5, res.bidder1_bid_count);
@@ -4144,6 +4536,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4196,6 +4603,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4254,6 +4676,21 @@
           ReportingDestination::kBuyer,
           testing::ElementsAre(testing::Pair(
               "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4305,6 +4742,21 @@
                   ReportingDestination::kSeller,
                   testing::ElementsAre(testing::Pair(
                       "click", GURL("https://reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            // ReportWin script override doesn't send a report
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4351,6 +4803,21 @@
             res.ad_component_urls);
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            // ReportWin script override doesn't send a report
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4405,6 +4872,16 @@
   EXPECT_THAT(res.report_urls, testing::UnorderedElementsAre(GURL(
                                    "https://seller.signals.were.null.test/")));
   EXPECT_TRUE(res.ad_beacon_map.metadata.empty());
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            // ReportWin script override doesn't send a report
+                            kExpectedGenerateBidPrivateAggregationRequest))));
   EXPECT_EQ(6, res.bidder1_bid_count);
   EXPECT_EQ(3u, res.bidder1_prev_wins.size());
   EXPECT_EQ(6, res.bidder2_bid_count);
@@ -4533,6 +5010,17 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/2"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      // Overridden script functions don't send reports
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder1,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(4u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -4691,6 +5179,22 @@
                   testing::ElementsAre(testing::Pair(
                       "click",
                       GURL("https://buyer-reporting.example.com/4"))))));
+      EXPECT_THAT(
+          result_.private_aggregation_requests,
+          testing::UnorderedElementsAre(
+              testing::Pair(kBidder1,
+                            ElementsAreRequests(
+                                kExpectedGenerateBidPrivateAggregationRequest)),
+              testing::Pair(kBidder2,
+                            ElementsAreRequests(
+                                kExpectedGenerateBidPrivateAggregationRequest,
+                                kExpectedReportWinPrivateAggregationRequest)),
+              testing::Pair(
+                  kSeller,
+                  ElementsAreRequests(
+                      kExpectedScoreAdPrivateAggregationRequest,
+                      kExpectedScoreAdPrivateAggregationRequest,
+                      kExpectedReportResultPrivateAggregationRequest))));
       EXPECT_EQ(6, result_.bidder1_bid_count);
       EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
       EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -4867,6 +5371,29 @@
                   testing::ElementsAre(testing::Pair(
                       "click",
                       GURL("https://buyer-reporting.example.com/4"))))));
+      EXPECT_THAT(
+          result_.private_aggregation_requests,
+          testing::UnorderedElementsAre(
+              testing::Pair(kBidder1,
+                            ElementsAreRequests(
+                                kExpectedGenerateBidPrivateAggregationRequest)),
+              testing::Pair(kBidder2,
+                            ElementsAreRequests(
+                                kExpectedGenerateBidPrivateAggregationRequest,
+                                kExpectedReportWinPrivateAggregationRequest)),
+              testing::Pair(
+                  kSeller, ElementsAreRequests(
+                               kExpectedScoreAdPrivateAggregationRequest,
+                               kExpectedScoreAdPrivateAggregationRequest,
+                               kExpectedReportResultPrivateAggregationRequest)),
+              testing::Pair(kComponentSeller1,
+                            ElementsAreRequests(
+                                kExpectedScoreAdPrivateAggregationRequest)),
+              testing::Pair(
+                  kComponentSeller2,
+                  ElementsAreRequests(
+                      kExpectedScoreAdPrivateAggregationRequest,
+                      kExpectedReportResultPrivateAggregationRequest))));
       CheckHistograms(InterestGroupAuction::AuctionResult::kSuccess,
                       /*expected_interest_groups=*/2, /*expected_owners=*/2,
                       /*expected_sellers=*/3);
@@ -5020,6 +5547,21 @@
               ReportingDestination::kBuyer,
               testing::ElementsAre(testing::Pair(
                   "click", GURL("https://buyer-reporting.example.com/4"))))));
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(
+              kBidder2,
+              ElementsAreRequests(kExpectedGenerateBidPrivateAggregationRequest,
+                                  kExpectedReportWinPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest)),
+          testing::Pair(kComponentSeller2,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   CheckHistograms(InterestGroupAuction::AuctionResult::kSuccess,
                   /*expected_interest_groups=*/2, /*expected_owners=*/2,
                   /*expected_sellers=*/3);
@@ -5106,6 +5648,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
 }
 
 TEST_F(AuctionRunnerTest, AllBiddersCrashBeforeBidding) {
@@ -5163,6 +5706,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -5239,7 +5783,8 @@
              /*data_version=*/0,
              /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+             /*errors=*/{});
 
     // Finish the auction.
     seller_worklet->WaitForReportResult();
@@ -5264,6 +5809,7 @@
     EXPECT_TRUE(result_.ad_component_urls.empty());
     EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
     EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+    EXPECT_TRUE(result_.private_aggregation_requests.empty());
     EXPECT_EQ(5, result_.bidder1_bid_count);
     EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
     EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -5290,13 +5836,32 @@
       mock_auction_process_manager_->TakeBidderWorklet(kBidder2Url);
   ASSERT_TRUE(bidder2_worklet);
 
-  bidder1_worklet->InvokeGenerateBidCallback(/*bid=*/7,
-                                             GURL("https://ad1.com/"));
+  PrivateAggregationRequests bidder_1_pa_requests;
+  bidder_1_pa_requests.push_back(
+      kExpectedGenerateBidPrivateAggregationRequest.Clone());
+  bidder1_worklet->InvokeGenerateBidCallback(
+      /*bid=*/7, GURL("https://ad1.com/"),
+      /*ad_component_urls=*/absl::nullopt,
+      /*duration=*/base::TimeDelta(),
+      /*bidding_signals_data_version=*/
+      absl::nullopt,
+      /*debug_loss_report_url=*/absl::nullopt,
+      /*debug_win_report_url=*/absl::nullopt, std::move(bidder_1_pa_requests));
   // The bidder pipe should be closed after it bids.
   EXPECT_TRUE(bidder1_worklet->PipeIsClosed());
   bidder1_worklet.reset();
-  bidder2_worklet->InvokeGenerateBidCallback(/*bid=*/5,
-                                             GURL("https://ad2.com/"));
+
+  PrivateAggregationRequests bidder_2_pa_requests;
+  bidder_2_pa_requests.push_back(
+      kExpectedGenerateBidPrivateAggregationRequest.Clone());
+  bidder2_worklet->InvokeGenerateBidCallback(
+      /*bid=*/5, GURL("https://ad2.com/"),
+      /*ad_component_urls=*/absl::nullopt,
+      /*duration=*/base::TimeDelta(),
+      /*bidding_signals_data_version=*/
+      absl::nullopt,
+      /*debug_loss_report_url=*/absl::nullopt,
+      /*debug_win_report_url=*/absl::nullopt, std::move(bidder_2_pa_requests));
   // The bidder pipe should be closed after it bids.
   EXPECT_TRUE(bidder2_worklet->PipeIsClosed());
   bidder2_worklet.reset();
@@ -5305,27 +5870,43 @@
   auto score_ad_params = seller_worklet->WaitForScoreAd();
   EXPECT_EQ(kBidder1, score_ad_params.interest_group_owner);
   EXPECT_EQ(7, score_ad_params.bid);
+  PrivateAggregationRequests score_ad_1_pa_requests;
+  score_ad_1_pa_requests.push_back(
+      kExpectedScoreAdPrivateAggregationRequest.Clone());
   std::move(score_ad_params.callback)
       .Run(/*score=*/11,
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt,
+           std::move(score_ad_1_pa_requests),
+           /*errors=*/{});
 
   // Score Bidder2's bid.
   score_ad_params = seller_worklet->WaitForScoreAd();
   EXPECT_EQ(kBidder2, score_ad_params.interest_group_owner);
   EXPECT_EQ(5, score_ad_params.bid);
+  PrivateAggregationRequests score_ad_2_pa_requests;
+  score_ad_2_pa_requests.push_back(
+      kExpectedScoreAdPrivateAggregationRequest.Clone());
   std::move(score_ad_params.callback)
       .Run(/*score=*/10,
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt,
+           std::move(score_ad_2_pa_requests),
+           /*errors=*/{});
+
+  PrivateAggregationRequests report_result_pa_requests;
+  report_result_pa_requests.push_back(
+      kExpectedReportResultPrivateAggregationRequest.Clone());
 
   // Bidder1 crashes while running ReportWin.
   seller_worklet->WaitForReportResult();
-  seller_worklet->InvokeReportResultCallback();
+  seller_worklet->InvokeReportResultCallback(
+      /*report_url=*/absl::nullopt,
+      /*ad_beacon_map=*/{}, std::move(report_result_pa_requests));
   mock_auction_process_manager_->WaitForWinningBidderReload();
   bidder1_worklet =
       mock_auction_process_manager_->TakeBidderWorklet(kBidder1Url);
@@ -5342,6 +5923,20 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_THAT(
+      result_.private_aggregation_requests,
+      testing::UnorderedElementsAre(
+          testing::Pair(kBidder1,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kBidder2,
+                        ElementsAreRequests(
+                            kExpectedGenerateBidPrivateAggregationRequest)),
+          testing::Pair(kSeller,
+                        ElementsAreRequests(
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedScoreAdPrivateAggregationRequest,
+                            kExpectedReportResultPrivateAggregationRequest))));
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -5443,7 +6038,8 @@
                auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
                /*data_version=*/0, /*has_data_version=*/false,
                /*debug_loss_report_url=*/absl::nullopt,
-               /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+               /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+               /*errors=*/{});
 
       // Score Bidder2's bid.
       EXPECT_EQ(kBidder2, score_ad_params2.interest_group_owner);
@@ -5453,7 +6049,8 @@
                auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
                /*data_version=*/0, /*has_data_version=*/false,
                /*debug_loss_report_url=*/absl::nullopt,
-               /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+               /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+               /*errors=*/{});
 
       seller_worklet->WaitForReportResult();
       DCHECK_EQ(CrashPhase::kReportResult, crash_phase);
@@ -5472,6 +6069,7 @@
     EXPECT_TRUE(result_.ad_component_urls.empty());
     EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
     EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+    EXPECT_TRUE(result_.private_aggregation_requests.empty());
     if (crash_phase != CrashPhase::kReportResult) {
       EXPECT_EQ(5, result_.bidder1_bid_count);
       EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
@@ -5565,7 +6163,7 @@
                /*has_bid=*/false),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt,
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
            /*errors=*/{});
 
   // Top-level seller worklet scores the bid.
@@ -5580,7 +6178,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Top-level seller worklet returns a report url.
   top_level_seller_worklet->WaitForReportResult();
@@ -5677,7 +6276,8 @@
                  /*has_bid=*/false),
              /*data_version=*/0, /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+             /*errors=*/{});
 
     // Top-level seller worklet scores the bid.
     auto top_level_seller_worklet =
@@ -5691,7 +6291,8 @@
              auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
              /*data_version=*/0, /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+             /*errors=*/{});
 
     // Top-level seller worklet returns a report url.
     top_level_seller_worklet->WaitForReportResult();
@@ -5755,7 +6356,8 @@
       const char kScriptError[] = "Script error";
 
       component_seller_worklet->InvokeReportResultCallback(
-          /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{}, {kScriptError});
+          /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{},
+          /*pa_requests=*/{}, {kScriptError});
 
       // Winning bidder worklet should be reloaded and ReportWin() invoked.
       mock_auction_process_manager_->WaitForWinningBidderReload();
@@ -5893,7 +6495,7 @@
         .Run(/*score=*/3, test_case.params.Clone(),
              /*data_version=*/0, /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt,
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
              /*errors=*/{});
 
     // The auction fails, because of the bad ComponentAuctionModifiedBidParams.
@@ -5951,7 +6553,7 @@
                /*has_bid=*/false),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt,
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
            /*errors=*/{});
 
   auction_run_loop_->Run();
@@ -6015,7 +6617,8 @@
                auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
                /*data_version=*/0, /*has_data_version=*/false,
                /*debug_loss_report_url=*/absl::nullopt,
-               /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+               /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+               /*errors=*/{});
 
       // Finish the auction.
       seller_worklet->WaitForReportResult();
@@ -6035,6 +6638,7 @@
       EXPECT_TRUE(result_.ad_component_urls.empty());
       EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
       EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+      EXPECT_TRUE(result_.private_aggregation_requests.empty());
       EXPECT_EQ(6, result_.bidder1_bid_count);
       ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
       EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -6056,6 +6660,7 @@
       EXPECT_TRUE(result_.ad_component_urls.empty());
       EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
       EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+      EXPECT_TRUE(result_.private_aggregation_requests.empty());
       EXPECT_EQ(5, result_.bidder1_bid_count);
       EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
       CheckHistograms(InterestGroupAuction::AuctionResult::kNoBids,
@@ -6106,7 +6711,8 @@
                auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
                /*data_version=*/0, /*has_data_version=*/false,
                /*debug_loss_report_url=*/absl::nullopt,
-               /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+               /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+               /*errors=*/{});
 
       // Finish the auction.
       seller_worklet->WaitForReportResult();
@@ -6126,6 +6732,7 @@
       EXPECT_EQ(ad_component_urls, result_.ad_component_urls);
       EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
       EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+      EXPECT_TRUE(result_.private_aggregation_requests.empty());
       EXPECT_EQ(6, result_.bidder1_bid_count);
       ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
       EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
@@ -6147,6 +6754,7 @@
       EXPECT_TRUE(result_.ad_component_urls.empty());
       EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
       EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+      EXPECT_TRUE(result_.private_aggregation_requests.empty());
       EXPECT_EQ(5, result_.bidder1_bid_count);
       EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
       CheckHistograms(InterestGroupAuction::AuctionResult::kNoBids,
@@ -6298,6 +6906,7 @@
     EXPECT_TRUE(result_.ad_component_urls.empty());
     EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
     EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+    EXPECT_TRUE(result_.private_aggregation_requests.empty());
     EXPECT_EQ(5, result_.bidder1_bid_count);
     EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
     EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6336,7 +6945,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Bidder1 never gets to report anything, since the seller providing a bad
   // report URL aborts the auction.
@@ -6353,6 +6963,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6390,7 +7001,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Bidder1 never gets to report anything, since the seller providing a bad
   // report URL aborts the auction.
@@ -6409,6 +7021,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6457,7 +7070,8 @@
                /*has_bid=*/false),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Top-level seller scores the bid.
   score_ad_params = seller_worklet->WaitForScoreAd();
@@ -6468,7 +7082,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Top-level seller worklet returns a valid HTTPS report URL.
   seller_worklet->WaitForReportResult();
@@ -6495,6 +7110,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6532,7 +7148,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   seller_worklet->WaitForReportResult();
   seller_worklet->InvokeReportResultCallback(
@@ -6553,6 +7170,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6590,7 +7208,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   seller_worklet->WaitForReportResult();
   seller_worklet->InvokeReportResultCallback(
@@ -6613,6 +7232,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(6, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(5, result_.bidder2_bid_count);
@@ -6653,7 +7273,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            /*debug_loss_report_url=*/absl::nullopt,
-           /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+           /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+           /*errors=*/{});
 
   // Finish the auction.
   seller_worklet->WaitForReportResult();
@@ -6672,6 +7293,7 @@
   EXPECT_TRUE(result_.ad_component_urls.empty());
   EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
   EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+  EXPECT_TRUE(result_.private_aggregation_requests.empty());
   EXPECT_EQ(5, result_.bidder1_bid_count);
   EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
   EXPECT_EQ(6, result_.bidder2_bid_count);
@@ -6713,7 +7335,8 @@
              auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
              /*data_version=*/0, /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+             /*errors=*/{});
 
     // Bidder2 returns a bid, which is then scored.
     bidder2_worklet->InvokeGenerateBidCallback(/*bid=*/5,
@@ -6726,7 +7349,8 @@
              auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
              /*data_version=*/0, /*has_data_version=*/false,
              /*debug_loss_report_url=*/absl::nullopt,
-             /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+             /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+             /*errors=*/{});
     // Need to flush the service pipe to make sure the AuctionRunner has
     // received the score.
     seller_worklet->Flush();
@@ -6779,6 +7403,7 @@
 
     EXPECT_THAT(result_.report_urls, testing::UnorderedElementsAre());
     EXPECT_TRUE(result_.ad_beacon_map.metadata.empty());
+    EXPECT_TRUE(result_.private_aggregation_requests.empty());
     EXPECT_EQ(6, result_.bidder1_bid_count);
     EXPECT_EQ(6, result_.bidder2_bid_count);
     CheckHistograms(InterestGroupAuction::AuctionResult::kSuccess,
@@ -6856,7 +7481,8 @@
                          ComponentAuctionModifiedBidParamsPtr(),
                      /*data_version=*/0, /*has_data_version=*/false,
                      /*debug_loss_report_url=*/absl::nullopt,
-                     /*debug_win_report_url=*/absl::nullopt, /*errors=*/{});
+                     /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
+                     /*errors=*/{});
             // Wait for the AuctionRunner to receive the score.
             task_environment_.RunUntilIdle();
             break;
@@ -6868,7 +7494,7 @@
                      /*data_version=*/0,
                      /*has_data_version=*/false,
                      /*debug_loss_report_url=*/absl::nullopt,
-                     /*debug_win_report_url=*/absl::nullopt,
+                     /*debug_win_report_url=*/absl::nullopt, /*pa_requests=*/{},
                      /*errors=*/{});
             // Wait for the AuctionRunner to receive the score.
             task_environment_.RunUntilIdle();
@@ -8170,7 +8796,8 @@
              auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
              /*data_version=*/0, /*has_data_version=*/false,
              test_case.seller_debug_loss_report_url,
-             test_case.seller_debug_win_report_url, /*errors=*/{});
+             test_case.seller_debug_win_report_url, /*pa_requests=*/{},
+             /*errors=*/{});
     auction_run_loop_->Run();
     EXPECT_EQ(test_case.expected_error_message, TakeBadMessage());
 
@@ -8229,7 +8856,8 @@
            auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr(),
            /*data_version=*/0, /*has_data_version=*/false,
            GURL("https://seller-debug-loss-reporting.com/1"),
-           GURL("https://seller-debug-win-reporting.com/1"), /*errors=*/{});
+           GURL("https://seller-debug-win-reporting.com/1"), /*pa_requests=*/{},
+           /*errors=*/{});
 
   seller_worklet->WaitForReportResult();
   seller_worklet->InvokeReportResultCallback();
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index be448413..1f88eab 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -7,16 +7,20 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <iterator>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/callback.h"
+#include "base/check.h"
 #include "base/containers/cxx20_erase_vector.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
+#include "base/ranges/algorithm.h"
 #include "base/strings/escape.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
@@ -32,6 +36,7 @@
 #include "content/browser/interest_group/storage_interest_group.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "services/network/public/mojom/client_security_state.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
@@ -357,19 +362,21 @@
           /*debug_win_report_url=*/absl::nullopt,
           /*set_priority=*/0,
           /*has_set_priority=*/false,
+          /*pa_requests=*/{},
           {base::StrCat({bid_state->bidder.interest_group.bidding_url->spec(),
                          " crashed while trying to run generateBid()."})});
       return;
     }
 
     // Otherwise, use error message from the worklet.
-    OnGenerateBidComplete(
-        bid_state, auction_worklet::mojom::BidderWorkletBidPtr(),
-        /*bidding_signals_data_version=*/0,
-        /*has_bidding_signals_data_version=*/false,
-        /*debug_loss_report_url=*/absl::nullopt,
-        /*debug_win_report_url=*/absl::nullopt,
-        /*set_priority=*/0, /*has_set_priority=*/false, errors);
+    OnGenerateBidComplete(bid_state,
+                          auction_worklet::mojom::BidderWorkletBidPtr(),
+                          /*bidding_signals_data_version=*/0,
+                          /*has_bidding_signals_data_version=*/false,
+                          /*debug_loss_report_url=*/absl::nullopt,
+                          /*debug_win_report_url=*/absl::nullopt,
+                          /*set_priority=*/0, /*has_set_priority=*/false,
+                          /*pa_requests=*/{}, errors);
   }
 
   // Invoked whenever the AuctionWorkletManager has provided a BidderWorket
@@ -426,6 +433,7 @@
       const absl::optional<GURL>& debug_win_report_url,
       double set_priority,
       bool has_set_priority,
+      PrivateAggregationRequests pa_requests,
       const std::vector<std::string>& errors) {
     DCHECK(!state->made_bid);
     DCHECK_GT(num_outstanding_bids_, 0);
@@ -444,6 +452,19 @@
           set_priority);
     }
 
+    DCHECK(base::ranges::none_of(
+        pa_requests,
+        [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
+               request_ptr) { return request_ptr.is_null(); }));
+    if (!pa_requests.empty()) {
+      PrivateAggregationRequests& pa_requests_for_bidder =
+          auction_->private_aggregation_requests_[state->bidder.interest_group
+                                                      .owner];
+      pa_requests_for_bidder.insert(pa_requests_for_bidder.end(),
+                                    std::move_iterator(pa_requests.begin()),
+                                    std::move_iterator(pa_requests.end()));
+    }
+
     auction_->errors_.insert(auction_->errors_.end(), errors.begin(),
                              errors.end());
 
@@ -959,6 +980,23 @@
   return std::move(report_urls_);
 }
 
+std::map<url::Origin, InterestGroupAuction::PrivateAggregationRequests>
+InterestGroupAuction::TakePrivateAggregationRequests() {
+  for (auto& component_auction : component_auctions_) {
+    std::map<url::Origin, PrivateAggregationRequests> requests_map =
+        component_auction->TakePrivateAggregationRequests();
+    for (auto& [origin, requests] : requests_map) {
+      DCHECK(!requests.empty());
+      PrivateAggregationRequests& destination_vector =
+          private_aggregation_requests_[origin];
+      destination_vector.insert(destination_vector.end(),
+                                std::move_iterator(requests.begin()),
+                                std::move_iterator(requests.end()));
+    }
+  }
+  return std::move(private_aggregation_requests_);
+}
+
 std::vector<std::string> InterestGroupAuction::TakeErrors() {
   for (auto& component_auction : component_auctions_) {
     std::vector<std::string> errors = component_auction->TakeErrors();
@@ -1259,6 +1297,7 @@
     bool has_data_version,
     const absl::optional<GURL>& debug_loss_report_url,
     const absl::optional<GURL>& debug_win_report_url,
+    PrivateAggregationRequests pa_requests,
     const std::vector<std::string>& errors) {
   DCHECK_GT(bids_being_scored_, 0);
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "seller_worklet_score_ad",
@@ -1267,6 +1306,19 @@
 
   --bids_being_scored_;
 
+  DCHECK(base::ranges::none_of(
+      pa_requests,
+      [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
+             request_ptr) { return request_ptr.is_null(); }));
+  if (!pa_requests.empty()) {
+    DCHECK(config_);
+    PrivateAggregationRequests& pa_requests_for_seller =
+        private_aggregation_requests_[config_->seller];
+    pa_requests_for_seller.insert(pa_requests_for_seller.end(),
+                                  std::move_iterator(pa_requests.begin()),
+                                  std::move_iterator(pa_requests.end()));
+  }
+
   // If `debug_loss_report_url` or `debug_win_report_url` is not a valid HTTPS
   // URL, the auction should fail because the worklet is compromised.
   if (debug_loss_report_url.has_value() &&
@@ -1539,6 +1591,7 @@
     const absl::optional<std::string>& signals_for_winner,
     const absl::optional<GURL>& seller_report_url,
     const base::flat_map<std::string, GURL>& seller_ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     const std::vector<std::string>& errors) {
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "seller_worklet_report_result",
                                   trace_id_);
@@ -1552,6 +1605,19 @@
   // an error if it crashes at this point, failing the auction unnecessarily.
   seller_worklet_handle_.reset();
 
+  DCHECK(base::ranges::none_of(
+      pa_requests,
+      [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
+             request_ptr) { return request_ptr.is_null(); }));
+  if (!pa_requests.empty()) {
+    DCHECK(config_);
+    PrivateAggregationRequests& pa_requests_for_seller =
+        private_aggregation_requests_[config_->seller];
+    pa_requests_for_seller.insert(pa_requests_for_seller.end(),
+                                  std::move_iterator(pa_requests.begin()),
+                                  std::move_iterator(pa_requests.end()));
+  }
+
   if (!seller_ad_beacon_map.empty()) {
     for (const auto& element : seller_ad_beacon_map) {
       if (!IsUrlValid(element.second)) {
@@ -1640,6 +1706,7 @@
 void InterestGroupAuction::OnReportBidWinComplete(
     const absl::optional<GURL>& bidder_report_url,
     const base::flat_map<std::string, GURL>& bidder_ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     const std::vector<std::string>& errors) {
   // There should be at most one other report URL at this point.
   DCHECK_LE(report_urls_.size(), 1u);
@@ -1651,6 +1718,19 @@
   // fatal error notification.
   top_bid_->bid->bid_state->worklet_handle.reset();
 
+  DCHECK(base::ranges::none_of(
+      pa_requests,
+      [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
+             request_ptr) { return request_ptr.is_null(); }));
+  if (!pa_requests.empty()) {
+    DCHECK(config_);
+    PrivateAggregationRequests& pa_requests_for_bidder =
+        private_aggregation_requests_[top_bid_->bid->interest_group->owner];
+    pa_requests_for_bidder.insert(pa_requests_for_bidder.end(),
+                                  std::move_iterator(pa_requests.begin()),
+                                  std::move_iterator(pa_requests.end()));
+  }
+
   if (!bidder_ad_beacon_map.empty()) {
     for (const auto& element : bidder_ad_beacon_map) {
       if (!IsUrlValid(element.second)) {
@@ -1695,7 +1775,8 @@
     // currently fail the auction.
     OnReportSellerResultComplete(/*signals_for_winner=*/absl::nullopt,
                                  /*seller_report_url=*/absl::nullopt,
-                                 /*seller_ad_beacon_map=*/{}, errors);
+                                 /*seller_ad_beacon_map=*/{},
+                                 /*pa_requests=*/{}, errors);
   }
 }
 
@@ -1715,7 +1796,8 @@
     // An error while reloading the worklet to call ReportWin() does not
     // currently fail the auction.
     OnReportBidWinComplete(/*bidder_report_url=*/absl::nullopt,
-                           /*bidder_ad_beacon_map=*/{}, errors);
+                           /*bidder_ad_beacon_map=*/{},
+                           /*pa_requests=*/{}, errors);
   }
 }
 
diff --git a/content/browser/interest_group/interest_group_auction.h b/content/browser/interest_group/interest_group_auction.h
index 3844153..474947c 100644
--- a/content/browser/interest_group/interest_group_auction.h
+++ b/content/browser/interest_group/interest_group_auction.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
@@ -23,6 +24,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "services/network/public/mojom/client_security_state.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -292,6 +294,9 @@
   // Always invoked asynchronously.
   using AuctionPhaseCompletionCallback = base::OnceCallback<void(bool success)>;
 
+  using PrivateAggregationRequests =
+      std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
   // All passed in raw pointers must remain valid until the InterestGroupAuction
   // is destroyed. `config` is typically owned by the AuctionRunner's
   // `owned_auction_config_` field. `parent` should be the parent
@@ -386,6 +391,14 @@
   // URLs.
   std::vector<GURL> TakeReportUrls();
 
+  // Retrieves all requests to the Private Aggregation API returned by
+  // GenerateBid(), ScoreAd(), ReportWin() and ReportResult(). The return value
+  // is keyed by reporting origin of the associated requests. May only be called
+  // after an auction has completed (successfully or not). May only be called
+  // once, since it takes ownership of stored reporting URLs.
+  std::map<url::Origin, PrivateAggregationRequests>
+  TakePrivateAggregationRequests();
+
   // Retrieves any errors from the auction. May only be called once, since it
   // takes ownership of stored errors.
   std::vector<std::string> TakeErrors();
@@ -497,6 +510,7 @@
                    bool has_scoring_signals_data_version,
                    const absl::optional<GURL>& debug_loss_report_url,
                    const absl::optional<GURL>& debug_win_report_url,
+                   PrivateAggregationRequests pa_requests,
                    const std::vector<std::string>& errors);
 
   // Invoked when the bid becomes the new highest scoring other bid, to handle
@@ -531,12 +545,14 @@
       const absl::optional<std::string>& signals_for_winner,
       const absl::optional<GURL>& seller_report_url,
       const base::flat_map<std::string, GURL>& seller_ad_beacon_map,
+      PrivateAggregationRequests pa_requests,
       const std::vector<std::string>& error_msgs);
   void LoadBidderWorkletToReportBidWin(const std::string& signals_for_winner);
   void ReportBidWin(const std::string& signals_for_winner);
   void OnReportBidWinComplete(
       const absl::optional<GURL>& bidder_report_url,
       const base::flat_map<std::string, GURL>& bidder_ad_beacon_map,
+      PrivateAggregationRequests pa_requests,
       const std::vector<std::string>& error_msgs);
 
   // Called when the component SellerWorklet with the bidder that won an
@@ -719,6 +735,12 @@
   // auction itself can be deleted at the end of the auction.
   std::vector<GURL> report_urls_;
 
+  // Stores all pending Private Aggregation API report requests until they have
+  // been flushed. Keyed by the origin of the script that issued the request
+  // (i.e. the reporting origin).
+  std::map<url::Origin, PrivateAggregationRequests>
+      private_aggregation_requests_;
+
   // All errors reported by worklets thus far.
   std::vector<std::string> errors_;
 
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 71836df..2096290 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -304,6 +304,9 @@
   one_shot_players_.clear();
 
   AbandonSystemAudioFocusIfNeeded();
+
+  content::GetContentClient()->browser()->RemovePresentationObserver(
+      this, web_contents());
 }
 
 void MediaSessionImpl::RenderFrameDeleted(RenderFrameHost* rfh) {
@@ -999,6 +1002,14 @@
   DCHECK(web_contents());
   DidUpdateFaviconURL(web_contents()->GetPrimaryMainFrame(),
                       web_contents()->GetFaviconURLs());
+
+  content::GetContentClient()->browser()->AddPresentationObserver(
+      this, web_contents());
+}
+
+void MediaSessionImpl::OnPresentationsChanged(bool has_presentation) {
+  has_presentation_ = has_presentation;
+  RebuildAndNotifyMediaSessionInfoChanged();
 }
 
 AudioFocusDelegate::AudioFocusResult MediaSessionImpl::RequestSystemAudioFocus(
@@ -1107,6 +1118,7 @@
   }
 
   info->muted = is_muted_;
+  info->has_presentation = has_presentation_;
 
   return info;
 }
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h
index 583375d5..cfb53aa 100644
--- a/content/browser/media/session/media_session_impl.h
+++ b/content/browser/media/session/media_session_impl.h
@@ -22,6 +22,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/page_user_data.h"
+#include "content/public/browser/presentation_observer.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -71,7 +72,8 @@
 // work with it.
 class MediaSessionImpl : public MediaSession,
                          public WebContentsObserver,
-                         public WebContentsUserData<MediaSessionImpl> {
+                         public WebContentsUserData<MediaSessionImpl>,
+                         public PresentationObserver {
  public:
   enum class State { ACTIVE, SUSPENDED, INACTIVE };
 
@@ -287,6 +289,9 @@
   // Mute or unmute the media player.
   void SetMute(bool mute) override;
 
+  // PresentationObserver:
+  void OnPresentationsChanged(bool has_presentation) override;
+
   // Downloads the bitmap version of a MediaImage at least |minimum_size_px|
   // and closest to |desired_size_px|. If the download failed, was too small or
   // the image did not come from the media session then returns a null image.
@@ -596,6 +601,9 @@
 
   bool should_throttle_duration_update_ = false;
 
+  // Whether the associated WebContents is connected to a presentation.
+  bool has_presentation_ = false;
+
   absl::optional<PlayerIdentifier> guarding_player_id_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
diff --git a/content/browser/media/session/media_session_impl_unittest.cc b/content/browser/media/session/media_session_impl_unittest.cc
index dc3b70e..2a3497b6 100644
--- a/content/browser/media/session/media_session_impl_unittest.cc
+++ b/content/browser/media/session/media_session_impl_unittest.cc
@@ -734,6 +734,19 @@
   EXPECT_FALSE(info->audio_sink_id.has_value());
 }
 
+TEST_F(MediaSessionImplTest, SessionInfoPresentation) {
+  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
+                   ->has_presentation);
+
+  GetMediaSession()->OnPresentationsChanged(true);
+  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
+                  ->has_presentation);
+
+  GetMediaSession()->OnPresentationsChanged(false);
+  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
+                   ->has_presentation);
+}
+
 TEST_F(MediaSessionImplTest, RaiseActivatesWebContents) {
   MockWebContentsDelegate delegate;
   web_contents()->SetDelegate(&delegate);
diff --git a/content/browser/private_aggregation/private_aggregation_manager.cc b/content/browser/private_aggregation/private_aggregation_manager.cc
index 1597b0d..da93ddf 100644
--- a/content/browser/private_aggregation/private_aggregation_manager.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager.cc
@@ -11,9 +11,9 @@
 namespace content {
 
 PrivateAggregationManager* PrivateAggregationManager::GetManager(
-    BrowserContext* browser_context) {
+    BrowserContext& browser_context) {
   return static_cast<StoragePartitionImpl*>(
-             browser_context->GetDefaultStoragePartition())
+             browser_context.GetDefaultStoragePartition())
       ->GetPrivateAggregationManager();
 }
 
diff --git a/content/browser/private_aggregation/private_aggregation_manager.h b/content/browser/private_aggregation/private_aggregation_manager.h
index c50766c..d57a75f06 100644
--- a/content/browser/private_aggregation/private_aggregation_manager.h
+++ b/content/browser/private_aggregation/private_aggregation_manager.h
@@ -23,7 +23,7 @@
  public:
   virtual ~PrivateAggregationManager() = default;
 
-  static PrivateAggregationManager* GetManager(BrowserContext* browser_context);
+  static PrivateAggregationManager* GetManager(BrowserContext& browser_context);
 
   // Binds a new pending receiver for a worklet, allowing messages to be sent
   // and processed. However, the receiver is not bound if the `worklet_origin`
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc b/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
index d72d74afa..2650f1e5 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
@@ -46,30 +46,6 @@
 
 constexpr char kExampleOriginUrl[] = "https://origin.example";
 
-class MockPrivateAggregationBudgeter : public PrivateAggregationBudgeter {
- public:
-  MOCK_METHOD(void,
-              ConsumeBudget,
-              (int,
-               const PrivateAggregationBudgetKey&,
-               base::OnceCallback<void(bool)>),
-              (override));
-};
-
-class MockPrivateAggregationHost : public PrivateAggregationHost {
- public:
-  MockPrivateAggregationHost()
-      : PrivateAggregationHost(
-            /*on_report_request_received=*/base::DoNothing()) {}
-
-  MOCK_METHOD(bool,
-              BindNewReceiver,
-              (url::Origin,
-               PrivateAggregationBudgetKey::Api,
-               mojo::PendingReceiver<mojom::PrivateAggregationHost>),
-              (override));
-};
-
 class PrivateAggregationManagerImplUnderTest
     : public PrivateAggregationManagerImpl {
  public:
diff --git a/content/browser/private_aggregation/private_aggregation_test_utils.cc b/content/browser/private_aggregation/private_aggregation_test_utils.cc
index 2c2d650..a079968 100644
--- a/content/browser/private_aggregation/private_aggregation_test_utils.cc
+++ b/content/browser/private_aggregation/private_aggregation_test_utils.cc
@@ -6,10 +6,20 @@
 
 #include <tuple>
 
+#include "base/callback_helpers.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 
 namespace content {
 
+MockPrivateAggregationBudgeter::MockPrivateAggregationBudgeter() = default;
+MockPrivateAggregationBudgeter::~MockPrivateAggregationBudgeter() = default;
+
+MockPrivateAggregationHost::MockPrivateAggregationHost()
+    : PrivateAggregationHost(
+          /*on_report_request_received=*/base::DoNothing()) {}
+
+MockPrivateAggregationHost::~MockPrivateAggregationHost() = default;
+
 bool operator==(const PrivateAggregationBudgetKey::TimeWindow& a,
                 const PrivateAggregationBudgetKey::TimeWindow& b) {
   return a.start_time() == b.start_time();
diff --git a/content/browser/private_aggregation/private_aggregation_test_utils.h b/content/browser/private_aggregation/private_aggregation_test_utils.h
index a4b3aae..08ae5f1 100644
--- a/content/browser/private_aggregation/private_aggregation_test_utils.h
+++ b/content/browser/private_aggregation/private_aggregation_test_utils.h
@@ -5,10 +5,55 @@
 #ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_TEST_UTILS_H_
 #define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_TEST_UTILS_H_
 
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
+#include "content/browser/private_aggregation/private_aggregation_budgeter.h"
+#include "content/browser/private_aggregation/private_aggregation_host.h"
+#include "content/common/aggregatable_report.mojom-forward.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace url {
+class Origin;
+}
 
 namespace content {
 
+class MockPrivateAggregationBudgeter : public PrivateAggregationBudgeter {
+ public:
+  MockPrivateAggregationBudgeter();
+  ~MockPrivateAggregationBudgeter() override;
+
+  MOCK_METHOD(void,
+              ConsumeBudget,
+              (int,
+               const PrivateAggregationBudgetKey&,
+               base::OnceCallback<void(bool)>),
+              (override));
+};
+
+class MockPrivateAggregationHost : public PrivateAggregationHost {
+ public:
+  MockPrivateAggregationHost();
+  ~MockPrivateAggregationHost() override;
+
+  MOCK_METHOD(bool,
+              BindNewReceiver,
+              (url::Origin,
+               PrivateAggregationBudgetKey::Api,
+               mojo::PendingReceiver<mojom::PrivateAggregationHost>),
+              (override));
+
+  MOCK_METHOD(void,
+              SendHistogramReport,
+              (std::vector<mojom::AggregatableReportHistogramContributionPtr>,
+               mojom::AggregationServiceMode aggregation_mode),
+              (override));
+};
+
 bool operator==(const PrivateAggregationBudgetKey::TimeWindow&,
                 const PrivateAggregationBudgetKey::TimeWindow&);
 
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index bd8d4be3..564a3ac 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -112,6 +112,7 @@
 #include "content/public/test/test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "content/shell/browser/shell.h"
+#include "content/shell/common/main_frame_counter_test_impl.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/did_commit_navigation_interceptor.h"
@@ -195,6 +196,20 @@
 
 namespace {
 
+void VerifyChildProcessHasMainFrame(
+    mojo::Remote<mojom::MainFrameCounterTest>& main_frame_counter,
+    bool expected_state) {
+  main_frame_counter.FlushForTesting();
+  base::RunLoop run_loop;
+  main_frame_counter->HasMainFrame(base::BindOnce(
+      [](base::RunLoop* loop, bool expected_state, bool has_main_frame) {
+        EXPECT_EQ(expected_state, has_main_frame);
+        loop->Quit();
+      },
+      &run_loop, expected_state));
+  run_loop.Run();
+}
+
 using CrashVisibility = CrossProcessFrameConnector::CrashVisibility;
 
 // Helper function to send a postMessage and wait for a reply message.  The
@@ -586,6 +601,17 @@
         web_contents()->GetRenderWidgetHostViewsInWebContentsTree();
     EXPECT_EQ(2U, views_set.size());
   }
+  mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter;
+  shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver(
+      main_frame_counter.BindNewPipeAndPassReceiver());
+
+  VerifyChildProcessHasMainFrame(main_frame_counter, true);
+
+  mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter_child;
+  rph->BindReceiver(main_frame_counter_child.BindNewPipeAndPassReceiver());
+
+  VerifyChildProcessHasMainFrame(main_frame_counter_child, false);
+
   RenderFrameProxyHost* proxy_to_parent =
       child->render_manager()->GetProxyToParent();
   EXPECT_TRUE(proxy_to_parent);
@@ -632,6 +658,7 @@
   EXPECT_NE(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
             child->current_frame_host()->GetProcess());
   EXPECT_NE(rph, child->current_frame_host()->GetProcess());
+  VerifyChildProcessHasMainFrame(main_frame_counter, true);
   {
     std::set<RenderWidgetHostViewBase*> views_set =
         web_contents()->GetRenderWidgetHostViewsInWebContentsTree();
@@ -656,6 +683,61 @@
       DepictFrameTree(root));
 }
 
+// Ensure that processes for iframes correctly track whether or not they have a
+// local main frame.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
+                       CrossSiteIframeMainFrameCount) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a,a,a(a,a))"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+
+  TestNavigationObserver observer(shell()->web_contents());
+
+  EXPECT_EQ(
+      " Site A\n"
+      "   |--Site A\n"
+      "   |--Site A\n"
+      "   +--Site A\n"
+      "        |--Site A\n"
+      "        +--Site A\n"
+      "Where A = http://a.com/",
+      DepictFrameTree(root));
+
+  mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter;
+  shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver(
+      main_frame_counter.BindNewPipeAndPassReceiver());
+  VerifyChildProcessHasMainFrame(main_frame_counter, true);
+
+  GURL url = embedded_test_server()->GetURL(
+      "b.com", "/cross_site_iframe_factory.html?b(a,a)");
+  {
+    RenderFrameDeletedObserver deleted_observer(
+        root->child_at(2)->current_frame_host());
+    EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(2), url));
+    deleted_observer.WaitUntilDeleted();
+  }
+
+  EXPECT_EQ(
+      " Site A ------------ proxies for B\n"
+      "   |--Site A ------- proxies for B\n"
+      "   |--Site A ------- proxies for B\n"
+      "   +--Site B ------- proxies for A\n"
+      "        |--Site A -- proxies for B\n"
+      "        +--Site A -- proxies for B\n"
+      "Where A = http://a.com/\n"
+      "      B = http://b.com/",
+      DepictFrameTree(root));
+
+  VerifyChildProcessHasMainFrame(main_frame_counter, true);
+
+  mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter_child;
+  root->child_at(2)->current_frame_host()->GetProcess()->BindReceiver(
+      main_frame_counter_child.BindNewPipeAndPassReceiver());
+  VerifyChildProcessHasMainFrame(main_frame_counter_child, false);
+}
+
 // Ensure that title updates affect the correct NavigationEntry after a new
 // subframe navigation with an out-of-process iframe.  https://crbug.com/616609.
 IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TitleAfterCrossSiteIframe) {
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 2af1689..9606e4c 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -2823,6 +2823,13 @@
   attribution_manager_ = std::move(attribution_manager);
 }
 
+void StoragePartitionImpl::OverridePrivateAggregationManagerForTesting(
+    std::unique_ptr<PrivateAggregationManagerImpl>
+        private_aggregation_manager) {
+  DCHECK(initialized_);
+  private_aggregation_manager_ = std::move(private_aggregation_manager);
+}
+
 void StoragePartitionImpl::GetQuotaSettings(
     storage::OptionalQuotaSettingsCallback callback) {
   if (g_test_quota_settings) {
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 7d74987..4890af4d 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -144,6 +144,9 @@
       std::unique_ptr<AggregationService> aggregation_service);
   void OverrideAttributionManagerForTesting(
       std::unique_ptr<AttributionManager> attribution_manager);
+  void OverridePrivateAggregationManagerForTesting(
+      std::unique_ptr<PrivateAggregationManagerImpl>
+          private_aggregation_manager);
 
   // Returns the StoragePartitionConfig that represents this StoragePartition.
   const StoragePartitionConfig& GetConfig();
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index a3a809b..6352cae 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -50,8 +50,9 @@
   kManifestListTooBig,
   kDisabledEmbargo,
   kUserInterfaceTimedOut,  // obsolete
+  kRpPageNotVisible,
 
-  kMaxValue = kUserInterfaceTimedOut
+  kMaxValue = kRpPageNotVisible
 };
 
 // This enum describes whether user sign-in states between IDP and browser
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 0c10cd2..0ff5566 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -695,6 +695,8 @@
       // Does not show the dialog if the user has left the page. e.g. they may
       // open a new tab before browser is ready to show the dialog.
       if (!is_visible) {
+        fedcm_metrics_->RecordRequestTokenStatus(
+            TokenStatus::kRpPageNotVisible);
         CompleteRequest(FederatedAuthRequestResult::kError, "",
                         /*should_delay_callback=*/true);
         return;
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 02ba386f..4c19dbd0 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -399,8 +399,6 @@
                kThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes},
           {"TopicsAPI", features::kPrivacySandboxAdsAPIsOverride,
            kSetOnlyIfOverridden},
-          {"TouchActionEffectiveAtPointerDown",
-           features::kVirtualKeyboardMultitouch},
           {"UserAgentClientHint", blink::features::kUserAgentClientHint},
           {"ViewportHeightClientHintHeader",
            blink::features::kViewportHeightClientHintHeader},
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index c5a9654..aca90ff 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -410,6 +410,17 @@
   }
 }
 
+# This is used by content/shell as well, which we don't want to depend on the
+# entire content/common.
+component("main_frame_counter") {
+  defines = [ "IS_MAIN_FRAME_COUNTER_IMPL" ]
+  sources = [
+    "main_frame_counter.cc",
+    "main_frame_counter.h",
+  ]
+  deps = [ "//base:base" ]
+}
+
 if (is_linux || is_chromeos) {
   source_set("set_process_title_linux") {
     public = [ "set_process_title_linux.h" ]
diff --git a/content/common/main_frame_counter.cc b/content/common/main_frame_counter.cc
new file mode 100644
index 0000000..08a19b9
--- /dev/null
+++ b/content/common/main_frame_counter.cc
@@ -0,0 +1,30 @@
+// 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 "content/common/main_frame_counter.h"
+#include "base/check_op.h"
+
+namespace content {
+
+// static
+size_t MainFrameCounter::main_frame_count_ = 0;
+
+// static
+bool MainFrameCounter::has_main_frame() {
+  return main_frame_count_ > 0;
+}
+
+// static
+void MainFrameCounter::IncrementCount() {
+  main_frame_count_++;
+}
+
+// static
+void MainFrameCounter::DecrementCount() {
+  // If this check fails, we have miscounted somewhere.
+  DCHECK_GT(main_frame_count_, 0u);
+  main_frame_count_--;
+}
+
+};  // namespace content
diff --git a/content/common/main_frame_counter.h b/content/common/main_frame_counter.h
new file mode 100644
index 0000000..2feecc14
--- /dev/null
+++ b/content/common/main_frame_counter.h
@@ -0,0 +1,38 @@
+// 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 CONTENT_COMMON_MAIN_FRAME_COUNTER_H_
+#define CONTENT_COMMON_MAIN_FRAME_COUNTER_H_
+
+#include <stddef.h>
+
+#include "base/component_export.h"
+
+namespace content {
+
+// This should only be used from the Renderer process, but is placed in common
+// so the Browser process can access it for testing only.
+//
+// This keeps track of how many main frames exist in the current Renderer
+// process.
+//
+// This is for an ongoing experiment to reduce memory usage in Renderers that
+// only contain subframes; it should be removed if the experiment does not
+// end up shipping. See: crbug.com/1331368 for tracking.
+class COMPONENT_EXPORT(MAIN_FRAME_COUNTER) MainFrameCounter final {
+ public:
+  static bool has_main_frame();
+
+ private:
+  friend class RenderFrameImpl;
+
+  static void IncrementCount();
+  static void DecrementCount();
+
+  static size_t main_frame_count_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_COMMON_MAIN_FRAME_COUNTER_H_
diff --git a/content/common/partition_alloc_support.cc b/content/common/partition_alloc_support.cc
index eafb934..8836c94 100644
--- a/content/common/partition_alloc_support.cc
+++ b/content/common/partition_alloc_support.cc
@@ -25,6 +25,7 @@
 #include "base/no_destructor.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 
 #if BUILDFLAG(IS_ANDROID)
@@ -454,7 +455,7 @@
   }
 }
 
-void PartitionAllocSupport::OnForegrounded() {
+void PartitionAllocSupport::OnForegrounded(bool has_main_frame) {
 #if defined(PA_THREAD_CACHE_SUPPORTED) && \
     BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
   {
@@ -463,7 +464,10 @@
       return;
   }
 
-  ::partition_alloc::ThreadCache::SetLargestCachedSize(largest_cached_size_);
+  if (!base::FeatureList::IsEnabled(
+          features::kLowerMemoryLimitForNonMainRenderers) ||
+      has_main_frame)
+    ::partition_alloc::ThreadCache::SetLargestCachedSize(largest_cached_size_);
 #endif  // defined(PA_THREAD_CACHE_SUPPORTED) &&
         // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 }
diff --git a/content/common/partition_alloc_support.h b/content/common/partition_alloc_support.h
index 21ceeca9..9da58fa 100644
--- a/content/common/partition_alloc_support.h
+++ b/content/common/partition_alloc_support.h
@@ -47,7 +47,8 @@
   void ReconfigureAfterFeatureListInit(const std::string& process_type);
   void ReconfigureAfterTaskRunnerInit(const std::string& process_type);
 
-  void OnForegrounded();
+  // |has_main_frame| tells us if the renderer contains a main frame.
+  void OnForegrounded(bool has_main_frame);
   void OnBackgrounded();
 
   static PartitionAllocSupport* Get() {
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 82a07cf..af3a1acc 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -749,6 +749,14 @@
   return nullptr;
 }
 
+void ContentBrowserClient::AddPresentationObserver(
+    PresentationObserver* observer,
+    WebContents* web_contents) {}
+
+void ContentBrowserClient::RemovePresentationObserver(
+    PresentationObserver* observer,
+    WebContents* web_contents) {}
+
 void ContentBrowserClient::OpenURL(
     content::SiteInstance* site_instance,
     const content::OpenURLParams& params,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 2f47543..c58c09df 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -216,6 +216,7 @@
 class NavigationThrottle;
 class NavigationUIData;
 class PrefetchServiceDelegate;
+class PresentationObserver;
 class QuotaPermissionContext;
 class ReceiverPresentationServiceDelegate;
 class RenderFrameHost;
@@ -1248,6 +1249,12 @@
   virtual ReceiverPresentationServiceDelegate*
   GetReceiverPresentationServiceDelegate(WebContents* web_contents);
 
+  // Add or remove an observer for presentations associated with `web_contents`.
+  virtual void AddPresentationObserver(PresentationObserver* observer,
+                                       WebContents* web_contents);
+  virtual void RemovePresentationObserver(PresentationObserver* observer,
+                                          WebContents* web_contents);
+
   // Allows programmatic opening of a new tab/window without going through
   // another WebContents. For example, from a Worker. |site_instance|
   // describes the context initiating the navigation. |callback| will be
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 317cf5b..80661db1 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -533,6 +533,11 @@
 #endif
 };
 
+// Configures whether we set a lower limit for renderers that do not have a main
+// frame, similar to the limit that is already done for backgrounded renderers.
+const base::Feature kLowerMemoryLimitForNonMainRenderers{
+    "LowerMemoryLimitForNonMainRenderers", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // The MBI mode controls whether or not communication over the
 // AgentSchedulingGroup is ordered with respect to the render-process-global
 // legacy IPC channel, as well as the granularity of AgentSchedulingGroup
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 65f451c..080db50 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -141,6 +141,7 @@
 CONTENT_EXPORT extern const base::Feature kLazyInitializeMediaControls;
 CONTENT_EXPORT extern const base::Feature kLegacyWindowsDWriteFontFallback;
 CONTENT_EXPORT extern const base::Feature kLogJsConsoleMessages;
+CONTENT_EXPORT extern const base::Feature kLowerMemoryLimitForNonMainRenderers;
 CONTENT_EXPORT extern const base::Feature kMBIMode;
 enum class MBIMode {
   // In this mode, the AgentSchedulingGroup will use the process-wide legacy IPC
diff --git a/content/public/test/unittest_test_suite.cc b/content/public/test/unittest_test_suite.cc
index ce01728..ba4ef98 100644
--- a/content/public/test/unittest_test_suite.cc
+++ b/content/public/test/unittest_test_suite.cc
@@ -135,13 +135,7 @@
   listeners.Append(CreateTestEventListener());
   listeners.Append(new CheckForLeakedWebUIRegistrations);
 
-  // The ThreadPool created by the test launcher is never destroyed.
-  // Similarly, the FeatureList created here is never destroyed so it
-  // can safely be accessed by the ThreadPool.
-  std::unique_ptr<base::FeatureList> feature_list =
-      std::make_unique<base::FeatureList>();
-  feature_list->InitializeFromCommandLine(enabled, disabled);
-  base::FeatureList::SetInstance(std::move(feature_list));
+  scoped_feature_list_.InitFromCommandLine(enabled, disabled);
 
   // Do this here even though TestBlinkWebUnitTestSupport calls it since a
   // multi process unit test won't get to create TestBlinkWebUnitTestSupport.
diff --git a/content/public/test/unittest_test_suite.h b/content/public/test/unittest_test_suite.h
index 27f1984..57506b4 100644
--- a/content/public/test/unittest_test_suite.h
+++ b/content/public/test/unittest_test_suite.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 
 namespace base {
@@ -69,6 +70,8 @@
   std::unique_ptr<TestHostResolver> test_host_resolver_;
 
   base::RepeatingCallback<std::unique_ptr<ContentClients>()> create_clients_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 }  // namespace content
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index ac56b78..e77edb5 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -252,6 +252,7 @@
     "//content/child",
     "//content/common",
     "//content/common:buildflags",
+    "//content/common:main_frame_counter",
     "//content/gpu:gpu_sources",
     "//content/public/child:child_sources",
     "//content/services/auction_worklet:auction_worklet",
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 9d47696..a2ff3043 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -58,6 +58,7 @@
 #include "content/common/debug_utils.h"
 #include "content/common/frame.mojom.h"
 #include "content/common/frame_messages.mojom.h"
+#include "content/common/main_frame_counter.h"
 #include "content/common/navigation_client.mojom.h"
 #include "content/common/navigation_gesture.h"
 #include "content/common/navigation_params_utils.h"
@@ -1886,6 +1887,9 @@
 
   web_media_stream_device_observer_.reset();
 
+  if (initialized_ && is_main_frame_)
+    MainFrameCounter::DecrementCount();
+
   base::trace_event::TraceLog::GetInstance()->RemoveProcessLabel(routing_id_);
   g_routing_id_frame_map.Get().erase(routing_id_);
   agent_scheduling_group_.RemoveRoute(routing_id_);
@@ -1894,6 +1898,8 @@
 void RenderFrameImpl::Initialize(blink::WebFrame* parent) {
   initialized_ = true;
   is_main_frame_ = !parent;
+  if (is_main_frame_)
+    MainFrameCounter::IncrementCount();
 
   TRACE_EVENT1("navigation,rail", "RenderFrameImpl::Initialize", "routing_id",
                routing_id_);
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index c68231a..a15cfb2 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -68,6 +68,7 @@
 #include "content/common/buildflags.h"
 #include "content/common/content_constants_internal.h"
 #include "content/common/content_switches_internal.h"
+#include "content/common/main_frame_counter.h"
 #include "content/common/partition_alloc_support.h"
 #include "content/common/process_visibility_tracker.h"
 #include "content/common/pseudonymization_salt.h"
@@ -78,6 +79,7 @@
 #include "content/public/common/gpu_stream_constants.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/renderer/content_renderer_client.h"
+#include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread_observer.h"
 #include "content/renderer/agent_scheduling_group.h"
 #include "content/renderer/browser_exposed_renderer_interfaces.h"
@@ -1690,7 +1692,8 @@
 void RenderThreadImpl::OnRendererForegrounded() {
   main_thread_scheduler_->SetRendererBackgrounded(false);
   discardable_memory_allocator_->OnForegrounded();
-  internal::PartitionAllocSupport::Get()->OnForegrounded();
+  internal::PartitionAllocSupport::Get()->OnForegrounded(
+      MainFrameCounter::has_main_frame());
   process_foregrounded_count_++;
 }
 
diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn
index 7ee1910..8b61840 100644
--- a/content/services/auction_worklet/BUILD.gn
+++ b/content/services/auction_worklet/BUILD.gn
@@ -52,6 +52,8 @@
     "debug_command_queue.h",
     "for_debugging_only_bindings.cc",
     "for_debugging_only_bindings.h",
+    "private_aggregation_bindings.cc",
+    "private_aggregation_bindings.h",
     "register_ad_beacon_bindings.cc",
     "register_ad_beacon_bindings.h",
     "report_bindings.cc",
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index 0c29b6e..244363c 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -24,7 +24,9 @@
 #include "base/trace_event/trace_event.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/for_debugging_only_bindings.h"
+#include "content/services/auction_worklet/private_aggregation_bindings.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
 #include "content/services/auction_worklet/set_bid_bindings.h"
@@ -356,6 +358,7 @@
   ContextRecycler context_recycler(v8_helper_.get());
   context_recycler.AddReportBindings();
   context_recycler.AddRegisterAdBeaconBindings();
+  context_recycler.AddPrivateAggregationBindings();
   ContextRecyclerScope context_recycler_scope(context_recycler);
   v8::Local<v8::Context> context = context_recycler_scope.GetContext();
 
@@ -365,9 +368,10 @@
       !AppendJsonValueOrNull(v8_helper_.get(), context, per_buyer_signals_json,
                              &args) ||
       !v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) {
-    PostReportWinCallbackToUserThread(
-        std::move(callback), /*report_url=*/absl::nullopt,
-        /*ad_beacon_map=*/{}, /*errors=*/std::vector<std::string>());
+    PostReportWinCallbackToUserThread(std::move(callback),
+                                      /*report_url=*/absl::nullopt,
+                                      /*ad_beacon_map=*/{}, /*pa_requests=*/{},
+                                      /*errors=*/std::vector<std::string>());
     return;
   }
 
@@ -396,9 +400,10 @@
       (bidding_signals_data_version.has_value() &&
        !browser_signals_dict.Set("dataVersion",
                                  bidding_signals_data_version.value()))) {
-    PostReportWinCallbackToUserThread(
-        std::move(callback), /*report_url=*/absl::nullopt,
-        /*ad_beacon_map=*/{}, /*errors=*/std::vector<std::string>());
+    PostReportWinCallbackToUserThread(std::move(callback),
+                                      /*report_url=*/absl::nullopt,
+                                      /*ad_beacon_map=*/{}, /*pa_requests=*/{},
+                                      /*errors=*/std::vector<std::string>());
     return;
   }
   args.push_back(browser_signals);
@@ -419,9 +424,14 @@
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_win", trace_id);
 
   if (script_failed) {
+    // Keep Private Aggregation API requests since `reportWin()` might use it to
+    // detect script timeout or failures.
     PostReportWinCallbackToUserThread(
         std::move(callback), /*report_url=*/absl::nullopt,
-        /*ad_beacon_map=*/{}, std::move(errors_out));
+        /*ad_beacon_map=*/{},
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        std::move(errors_out));
     return;
   }
 
@@ -430,6 +440,8 @@
   PostReportWinCallbackToUserThread(
       std::move(callback), context_recycler.report_bindings()->report_url(),
       context_recycler.register_ad_beacon_bindings()->TakeAdBeaconMap(),
+      context_recycler.private_aggregation_bindings()
+          ->TakePrivateAggregationRequests(),
       std::move(errors_out));
 }
 
@@ -461,6 +473,7 @@
   // repeated calls to this worklet, or to calls to any other worklet.
   ContextRecycler context_recycler(v8_helper_.get());
   context_recycler.AddForDebuggingOnlyBindings();
+  context_recycler.AddPrivateAggregationBindings();
   context_recycler.AddSetBidBindings();
   context_recycler.AddSetPriorityBindings();
 
@@ -638,11 +651,13 @@
   if (!context_recycler.set_bid_bindings()->has_bid()) {
     // If we either don't have a valid return value, or we have no return value
     // and no intermediate result was given through setBid, return an error.
-    // Keep debug loss reports since `generateBid()` might use it to detect
-    // script timeout or failures.
+    // Keep debug loss reports and Private Aggregation API requests since
+    // `generateBid()` might use them to detect script timeout or failures.
     PostErrorBidCallbackToUserThread(
         std::move(callback), std::move(errors_out),
-        context_recycler.for_debugging_only_bindings()->TakeLossReportUrl());
+        context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests());
     return;
   }
 
@@ -654,6 +669,8 @@
           context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
           context_recycler.for_debugging_only_bindings()->TakeWinReportUrl(),
           context_recycler.set_priority_bindings()->set_priority(),
+          context_recycler.private_aggregation_bindings()
+              ->TakePrivateAggregationRequests(),
           std::move(errors_out)));
 }
 
@@ -688,17 +705,20 @@
     ReportWinCallbackInternal callback,
     const absl::optional<GURL>& report_url,
     base::flat_map<std::string, GURL> ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
   user_thread_->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), std::move(report_url),
-                                std::move(ad_beacon_map), std::move(errors)));
+                                std::move(ad_beacon_map),
+                                std::move(pa_requests), std::move(errors)));
 }
 
 void BidderWorklet::V8State::PostErrorBidCallbackToUserThread(
     GenerateBidCallbackInternal callback,
     std::vector<std::string> error_msgs,
-    absl::optional<GURL> debug_loss_report_url) {
+    absl::optional<GURL> debug_loss_report_url,
+    PrivateAggregationRequests private_aggregation_requests) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
   user_thread_->PostTask(
       FROM_HERE,
@@ -706,7 +726,9 @@
                      /*bidding_signals_data_version=*/absl::nullopt,
                      /*debug_loss_report_url=*/std::move(debug_loss_report_url),
                      /*debug_win_report_url=*/absl::nullopt,
-                     /*set_priority=*/absl::nullopt, std::move(error_msgs)));
+                     /*set_priority=*/absl::nullopt,
+                     std::move(private_aggregation_requests),
+                     std::move(error_msgs)));
 }
 
 void BidderWorklet::ResumeIfPaused() {
@@ -899,6 +921,7 @@
     absl::optional<GURL> debug_loss_report_url,
     absl::optional<GURL> debug_win_report_url,
     absl::optional<double> set_priority,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> error_msgs) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
 
@@ -912,7 +935,7 @@
       .Run(std::move(bid), bidding_signals_data_version.value_or(0),
            bidding_signals_data_version.has_value(), debug_loss_report_url,
            debug_win_report_url, set_priority.value_or(0),
-           set_priority.has_value(), error_msgs);
+           set_priority.has_value(), std::move(pa_requests), error_msgs);
   generate_bid_tasks_.erase(task);
 }
 
@@ -920,12 +943,14 @@
     ReportWinTaskList::iterator task,
     absl::optional<GURL> report_url,
     base::flat_map<std::string, GURL> ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
   errors.insert(errors.end(), load_code_error_msgs_.begin(),
                 load_code_error_msgs_.end());
   std::move(task->callback)
-      .Run(std::move(report_url), std::move(ad_beacon_map), errors);
+      .Run(std::move(report_url), std::move(ad_beacon_map),
+           std::move(pa_requests), std::move(errors));
   report_win_tasks_.erase(task);
 }
 
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h
index 5ada948..7d45cfb 100644
--- a/content/services/auction_worklet/bidder_worklet.h
+++ b/content/services/auction_worklet/bidder_worklet.h
@@ -22,6 +22,7 @@
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
 #include "content/services/auction_worklet/trusted_signals.h"
 #include "content/services/auction_worklet/trusted_signals_request_manager.h"
 #include "content/services/auction_worklet/worklet_loader.h"
@@ -64,6 +65,9 @@
   using ClosePipeCallback =
       base::OnceCallback<void(const std::string& description)>;
 
+  using PrivateAggregationRequests =
+      std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
   // Starts loading the worklet script on construction, as well as the trusted
   // bidding data, if necessary. Will then call the script's generateBid()
   // function and invoke the callback with the results. Callback will always be
@@ -203,10 +207,12 @@
         absl::optional<GURL> debug_loss_report_url,
         absl::optional<GURL> debug_win_report_url,
         absl::optional<double> set_priority,
+        PrivateAggregationRequests pa_requests,
         std::vector<std::string> error_msgs)>;
     using ReportWinCallbackInternal =
         base::OnceCallback<void(absl::optional<GURL> report_url,
                                 base::flat_map<std::string, GURL> ad_beacon_map,
+                                PrivateAggregationRequests pa_requests,
                                 std::vector<std::string> errors)>;
 
     void ReportWin(const std::string& interest_group_name,
@@ -251,12 +257,14 @@
         ReportWinCallbackInternal callback,
         const absl::optional<GURL>& report_url,
         base::flat_map<std::string, GURL> ad_beacon_map,
+        PrivateAggregationRequests pa_requests,
         std::vector<std::string> errors);
 
     void PostErrorBidCallbackToUserThread(
         GenerateBidCallbackInternal callback,
         std::vector<std::string> error_msgs = std::vector<std::string>(),
-        absl::optional<GURL> debug_loss_report_url = absl::nullopt);
+        absl::optional<GURL> debug_loss_report_url = absl::nullopt,
+        PrivateAggregationRequests private_aggregation_requests = {});
 
     static void PostResumeToUserThread(
         base::WeakPtr<BidderWorklet> parent,
@@ -316,6 +324,7 @@
       absl::optional<GURL> debug_loss_report_url,
       absl::optional<GURL> debug_win_report_url,
       absl::optional<double> set_priority,
+      PrivateAggregationRequests pa_requests,
       std::vector<std::string> error_msgs);
 
   // Invokes the `callback` of `task` with the provided values, and removes
@@ -324,6 +333,7 @@
       ReportWinTaskList::iterator task,
       absl::optional<GURL> report_url,
       base::flat_map<std::string, GURL> ad_beacon_map,
+      PrivateAggregationRequests pa_requests,
       std::vector<std::string> errors);
 
   // Returns true if unpaused and the script and WASM helper (if needed) have
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 9d402d6..41e1cb2 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -23,6 +23,7 @@
 #include "base/time/time.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/worklet_devtools_debug_test_util.h"
 #include "content/services/auction_worklet/worklet_test_util.h"
 #include "content/services/auction_worklet/worklet_v8_debug_test_util.h"
@@ -33,6 +34,7 @@
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
@@ -47,6 +49,8 @@
 namespace auction_worklet {
 namespace {
 
+using PrivateAggregationRequests = BidderWorklet::PrivateAggregationRequests;
+
 // This was produced by running wat2wasm on this:
 // (module
 //  (global (export "test_const") i32 (i32.const 123))
@@ -185,11 +189,13 @@
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
       const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
-      const absl::optional<double> expected_set_priority = absl::nullopt) {
+      const absl::optional<double> expected_set_priority = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     RunGenerateBidWithJavascriptExpectingResult(
         CreateGenerateBidScript(raw_return_value), std::move(expected_bid),
         expected_data_version, expected_errors, expected_debug_loss_report_url,
-        expected_debug_win_report_url, expected_set_priority);
+        expected_debug_win_report_url, expected_set_priority,
+        std::move(expected_pa_requests));
   }
 
   // Configures `url_loader_factory_` to return a script with the specified
@@ -202,14 +208,15 @@
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
       const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
-      const absl::optional<double> expected_set_priority = absl::nullopt) {
+      const absl::optional<double> expected_set_priority = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     SCOPED_TRACE(javascript);
     AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
                           javascript);
     RunGenerateBidExpectingResult(
         std::move(expected_bid), expected_data_version, expected_errors,
         expected_debug_loss_report_url, expected_debug_win_report_url,
-        expected_set_priority);
+        expected_set_priority, std::move(expected_pa_requests));
   }
 
   // Loads and runs a generateBid() script, expecting the provided result.
@@ -220,7 +227,8 @@
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
       const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
-      const absl::optional<double> expected_set_priority = absl::nullopt) {
+      const absl::optional<double> expected_set_priority = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     auto bidder_worklet = CreateWorkletAndGenerateBid();
 
     EXPECT_EQ(expected_bid.is_null(), bid_.is_null());
@@ -238,6 +246,7 @@
     EXPECT_EQ(expected_data_version, data_version_);
     EXPECT_EQ(expected_debug_loss_report_url, bid_debug_loss_report_url_);
     EXPECT_EQ(expected_debug_win_report_url, bid_debug_win_report_url_);
+    EXPECT_EQ(expected_pa_requests, pa_requests_);
     EXPECT_EQ(expected_errors, bid_errors_);
     EXPECT_EQ(expected_set_priority, set_priority_);
   }
@@ -249,11 +258,13 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     RunReportWinWithJavascriptExpectingResult(
         CreateReportWinScript(function_body), expected_report_url,
-        expected_ad_beacon_map, expected_errors);
+        expected_ad_beacon_map, std::move(expected_pa_requests),
+        expected_errors);
   }
 
   // Configures `url_loader_factory_` to return a reportWin() script with the
@@ -263,12 +274,14 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     SCOPED_TRACE(javascript);
     AddJavascriptResponse(&url_loader_factory_, interest_group_bidding_url_,
                           javascript);
     RunReportWinExpectingResult(expected_report_url, expected_ad_beacon_map,
+                                std::move(expected_pa_requests),
                                 expected_errors);
   }
 
@@ -279,6 +292,7 @@
       mojom::BidderWorklet* bidder_worklet,
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
+      PrivateAggregationRequests expected_pa_requests,
       const std::vector<std::string>& expected_errors,
       base::OnceClosure done_closure) {
     bidder_worklet->ReportWin(
@@ -292,17 +306,21 @@
         base::BindOnce(
             [](const absl::optional<GURL>& expected_report_url,
                const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
+               PrivateAggregationRequests expected_pa_requests,
                const std::vector<std::string>& expected_errors,
                base::OnceClosure done_closure,
                const absl::optional<GURL>& report_url,
                const base::flat_map<std::string, GURL>& ad_beacon_map,
+               PrivateAggregationRequests pa_requests,
                const std::vector<std::string>& errors) {
               EXPECT_EQ(expected_report_url, report_url);
               EXPECT_EQ(expected_errors, errors);
               EXPECT_EQ(expected_ad_beacon_map, ad_beacon_map);
+              EXPECT_EQ(expected_pa_requests, pa_requests);
               std::move(done_closure).Run();
             },
-            expected_report_url, expected_ad_beacon_map, expected_errors,
+            expected_report_url, expected_ad_beacon_map,
+            std::move(expected_pa_requests), expected_errors,
             std::move(done_closure)));
   }
 
@@ -312,6 +330,7 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     auto bidder_worklet = CreateWorklet();
@@ -319,8 +338,9 @@
 
     base::RunLoop run_loop;
     RunReportWinExpectingResultAsync(bidder_worklet.get(), expected_report_url,
-                                     expected_ad_beacon_map, expected_errors,
-                                     run_loop.QuitClosure());
+                                     expected_ad_beacon_map,
+                                     std::move(expected_pa_requests),
+                                     expected_errors, run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -401,6 +421,7 @@
                           const absl::optional<GURL>& debug_loss_report_url,
                           const absl::optional<GURL>& debug_win_report_url,
                           double set_priority, bool has_set_priority,
+                          PrivateAggregationRequests pa_requests,
                           const std::vector<std::string>& errors) {
           ADD_FAILURE() << "Callback should not be invoked.";
         }));
@@ -427,6 +448,7 @@
                            const absl::optional<GURL>& debug_win_report_url,
                            double set_priority,
                            bool has_set_priority,
+                           PrivateAggregationRequests pa_requests,
                            const std::vector<std::string>& errors) {
     absl::optional<uint32_t> maybe_data_version;
     if (has_data_version)
@@ -439,6 +461,7 @@
     bid_debug_loss_report_url_ = debug_loss_report_url;
     bid_debug_win_report_url_ = debug_win_report_url;
     set_priority_ = maybe_set_priority;
+    pa_requests_ = std::move(pa_requests);
     bid_errors_ = errors;
     load_script_run_loop_->Quit();
   }
@@ -534,6 +557,7 @@
   absl::optional<GURL> bid_debug_loss_report_url_;
   absl::optional<GURL> bid_debug_win_report_url_;
   absl::optional<double> set_priority_;
+  PrivateAggregationRequests pa_requests_;
   std::vector<std::string> bid_errors_;
 
   network::TestURLLoaderFactory url_loader_factory_;
@@ -1562,6 +1586,7 @@
                   const absl::optional<GURL>& debug_loss_report_url,
                   const absl::optional<GURL>& debug_win_report_url,
                   double set_priority, bool has_set_priority,
+                  PrivateAggregationRequests pa_requests,
                   const std::vector<std::string>& errors) {
                 EXPECT_EQ(bid_value, bid->bid);
                 EXPECT_EQ(base::NumberToString(bid_value), bid->ad);
@@ -1655,6 +1680,7 @@
                 const absl::optional<GURL>& debug_loss_report_url,
                 const absl::optional<GURL>& debug_win_report_url,
                 double set_priority, bool has_set_priority,
+                PrivateAggregationRequests pa_requests,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -1756,6 +1782,7 @@
                 const absl::optional<GURL>& debug_loss_report_url,
                 const absl::optional<GURL>& debug_win_report_url,
                 double set_priority, bool has_set_priority,
+                PrivateAggregationRequests pa_requests,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -1863,6 +1890,7 @@
                 const absl::optional<GURL>& debug_loss_report_url,
                 const absl::optional<GURL>& debug_win_report_url,
                 double set_priority, bool has_set_priority,
+                PrivateAggregationRequests pa_requests,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -1949,6 +1977,7 @@
                 const absl::optional<GURL>& debug_loss_report_url,
                 const absl::optional<GURL>& debug_win_report_url,
                 double set_priority, bool has_set_priority,
+                PrivateAggregationRequests pa_requests,
                 const std::vector<std::string>& errors) {
               EXPECT_EQ(base::NumberToString(i), bid->ad);
               EXPECT_EQ(i + 1, bid->bid);
@@ -2358,6 +2387,7 @@
       base::BindLambdaForTesting(
           [&run_loop](const absl::optional<GURL>& report_url,
                       const base::flat_map<std::string, GURL>& ad_beacon_map,
+                      PrivateAggregationRequests pa_requests,
                       const std::vector<std::string>& errors) {
             run_loop.Quit();
           }));
@@ -2389,6 +2419,7 @@
   RunReportWinExpectingResultAsync(
       bidder_worklet.get(), GURL("https://foo.test"),
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       /*expected_errors=*/{},
       base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
   task_environment_.RunUntilIdle();
@@ -2833,6 +2864,138 @@
       /*expected_set_priority=*/9.0);
 }
 
+TEST_F(BidderWorkletTest, GenerateBidPrivateAggregationRequests) {
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest1 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/123,
+              /*value=*/45),
+          content::mojom::AggregationServiceMode::kDefault);
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest2 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/absl::MakeInt128(/*high=*/1, /*low=*/0),
+              /*value=*/1),
+          content::mojom::AggregationServiceMode::kDefault);
+
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          )"),
+        /*expected_bid=*/
+        mojom::BidderWorkletBid::New(
+            "\"ad\"", 1, GURL("https://response.test/"),
+            /*ad_components=*/absl::nullopt, base::TimeDelta()),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_errors=*/{},
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        /*expected_set_priority=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // BigInt bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.sendHistogramReport({bucket: 123n, value: 45});
+          )"),
+        /*expected_bid=*/
+        mojom::BidderWorkletBid::New(
+            "\"ad\"", 1, GURL("https://response.test/"),
+            /*ad_components=*/absl::nullopt, base::TimeDelta()),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_errors=*/{},
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        /*expected_set_priority=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // Large bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.sendHistogramReport(
+                {bucket: 18446744073709551616n, value: 1});
+          )"),
+        /*expected_bid=*/
+        mojom::BidderWorkletBid::New(
+            "\"ad\"", 1, GURL("https://response.test/"),
+            /*ad_components=*/absl::nullopt, base::TimeDelta()),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_errors=*/{},
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        /*expected_set_priority=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // Multiple requests
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+            privateAggregation.sendHistogramReport(
+                {bucket: 18446744073709551616n, value: 1});
+          )"),
+        /*expected_bid=*/
+        mojom::BidderWorkletBid::New(
+            "\"ad\"", 1, GURL("https://response.test/"),
+            /*ad_components=*/absl::nullopt, base::TimeDelta()),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_errors=*/{},
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        /*expected_set_priority=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // An unrelated exception after sendHistogramReport shouldn't block the report
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunGenerateBidWithJavascriptExpectingResult(
+        CreateGenerateBidScript(
+            R"({ad: "ad", bid:1, render:"https://response.test/" })",
+            /*extra_code=*/R"(
+            privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+            error;
+          )"),
+        /*expected_bid=*/mojom::BidderWorkletBidPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_errors=*/
+        {"https://url.test/:6 Uncaught ReferenceError: error is not defined."},
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        /*expected_set_priority=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+}
+
 TEST_F(BidderWorkletTest, ReportWin) {
   RunReportWinWithFunctionBodyExpectingResult(
       "", /*expected_report_url =*/absl::nullopt);
@@ -2849,24 +3012,28 @@
       R"(sendReportTo("http://http.not.allowed.test"))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a "
        "valid HTTPS url."});
   RunReportWinWithFunctionBodyExpectingResult(
       R"(sendReportTo("file:///file.not.allowed.test"))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a "
        "valid HTTPS url."});
 
   RunReportWinWithFunctionBodyExpectingResult(
       R"(sendReportTo(""))", /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: sendReportTo must be passed a "
        "valid HTTPS url."});
 
   RunReportWinWithFunctionBodyExpectingResult(
       R"(sendReportTo("https://foo.test");sendReportTo("https://foo.test"))",
       /*expected_report_url =*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: sendReportTo may be called at "
        "most once."});
 }
@@ -2908,6 +3075,7 @@
       /*trace_id=*/1,
       base::BindOnce([](const absl::optional<GURL>& report_url,
                         const base::flat_map<std::string, GURL>& ad_beacon_map,
+                        PrivateAggregationRequests pa_requests,
                         const std::vector<std::string>& errors) {
         ADD_FAILURE() << "Callback should not be invoked since worklet deleted";
       }));
@@ -2953,6 +3121,7 @@
               [&run_loop, &num_report_win_calls, i](
                   const absl::optional<GURL>& report_url,
                   const base::flat_map<std::string, GURL>& ad_beacon_map,
+                  PrivateAggregationRequests pa_requests,
                   const std::vector<std::string>& errors) {
                 EXPECT_EQ(GURL(base::StringPrintf("https://foo.test/%zu", i)),
                           report_url);
@@ -2995,6 +3164,7 @@
         base::BindOnce(
             [](const absl::optional<GURL>& report_url,
                const base::flat_map<std::string, GURL>& ad_beacon_map,
+               PrivateAggregationRequests pa_requests,
                const std::vector<std::string>& errors) {
               ADD_FAILURE() << "Callback should not be invoked.";
             }));
@@ -3014,6 +3184,7 @@
       R"(sendReportTo("https://foo.test/" + Date().toString()))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught ReferenceError: Date is not defined."});
 }
 
@@ -3207,6 +3378,7 @@
         base::BindLambdaForTesting(
             [&run_loop](const absl::optional<GURL>& report_url,
                         const base::flat_map<std::string, GURL>& ad_beacon_map,
+                        PrivateAggregationRequests pa_requests,
                         const std::vector<std::string>& errors) {
               EXPECT_EQ(GURL("https://23.test/"), report_url);
               EXPECT_TRUE(errors.empty());
@@ -3637,7 +3809,7 @@
   // Now ask for reporting. This should hit the other breakpoint.
   base::RunLoop run_loop;
   RunReportWinExpectingResultAsync(worklet.get(), GURL("https://foo.test/"), {},
-                                   {}, run_loop.QuitClosure());
+                                   {}, {}, run_loop.QuitClosure());
 
   TestDevToolsAgentClient::Event breakpoint_hit2 =
       debug.WaitForMethodNotification("Debugger.paused");
@@ -3896,6 +4068,7 @@
       registerAdBeacon())",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:14 Uncaught TypeError: registerAdBeacon may be "
        "called at most once."});
 
@@ -3924,6 +4097,7 @@
       R"(registerAdBeacon())",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -3932,6 +4106,7 @@
       R"(registerAdBeacon("foo"))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -3940,6 +4115,7 @@
       R"(registerAdBeacon("foo"))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -3951,6 +4127,7 @@
       }))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon object "
        "attributes must be strings."});
 
@@ -3962,6 +4139,7 @@
       }))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon invalid "
        "reporting url for key 'view': 'gopher://view.example.com/'."});
 
@@ -3973,9 +4151,101 @@
       }))",
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught TypeError: registerAdBeacon invalid "
        "reporting url for key 'view': 'http://view.example.com/'."});
 }
 
+TEST_F(BidderWorkletTest, ReportWinPrivateAggregationRequests) {
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest1 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/123,
+              /*value=*/45),
+          content::mojom::AggregationServiceMode::kDefault);
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest2 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/absl::MakeInt128(/*high=*/1, /*low=*/0),
+              /*value=*/1),
+          content::mojom::AggregationServiceMode::kDefault);
+
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+        )",
+        /*expected_report_url =*/absl::nullopt,
+        /*expected_ad_beacon_map=*/{}, std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // BigInt bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123n, value: 45});
+        )",
+        /*expected_report_url =*/absl::nullopt,
+        /*expected_ad_beacon_map=*/{}, std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // Large bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 18446744073709551616n,
+                                                  value: 1});
+        )",
+        /*expected_report_url =*/absl::nullopt,
+        /*expected_ad_beacon_map=*/{}, std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // Multiple requests
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          privateAggregation.sendHistogramReport({bucket: 18446744073709551616n,
+                                                  value: 1});
+        )",
+        /*expected_report_url =*/absl::nullopt,
+        /*expected_ad_beacon_map=*/{}, std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // An unrelated exception after sendHistogramReport shouldn't block the report
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportWinWithFunctionBodyExpectingResult(
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          error;
+        )",
+        /*expected_report_url =*/absl::nullopt,
+        /*expected_ad_beacon_map=*/{}, std::move(expected_pa_requests),
+        /*expected_errors=*/
+        {"https://url.test/:12 Uncaught ReferenceError: error is not "
+         "defined."});
+  }
+}
+
 }  // namespace
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/context_recycler.cc b/content/services/auction_worklet/context_recycler.cc
index fcdc8f9..dd6b4711 100644
--- a/content/services/auction_worklet/context_recycler.cc
+++ b/content/services/auction_worklet/context_recycler.cc
@@ -4,8 +4,12 @@
 
 #include "content/services/auction_worklet/context_recycler.h"
 
+#include <memory>
+
 #include "base/check.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/for_debugging_only_bindings.h"
+#include "content/services/auction_worklet/private_aggregation_bindings.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
 #include "content/services/auction_worklet/set_bid_bindings.h"
@@ -29,6 +33,13 @@
   AddBindings(for_debugging_only_bindings_.get());
 }
 
+void ContextRecycler::AddPrivateAggregationBindings() {
+  DCHECK(!private_aggregation_bindings_);
+  private_aggregation_bindings_ =
+      std::make_unique<PrivateAggregationBindings>(v8_helper_);
+  AddBindings(private_aggregation_bindings_.get());
+}
+
 void ContextRecycler::AddRegisterAdBeaconBindings() {
   DCHECK(!register_ad_beacon_bindings_);
   register_ad_beacon_bindings_ =
diff --git a/content/services/auction_worklet/context_recycler.h b/content/services/auction_worklet/context_recycler.h
index 48ed970..07dd960 100644
--- a/content/services/auction_worklet/context_recycler.h
+++ b/content/services/auction_worklet/context_recycler.h
@@ -16,6 +16,7 @@
 namespace auction_worklet {
 
 class ForDebuggingOnlyBindings;
+class PrivateAggregationBindings;
 class RegisterAdBeaconBindings;
 class ReportBindings;
 class SetBidBindings;
@@ -51,6 +52,11 @@
     return for_debugging_only_bindings_.get();
   }
 
+  void AddPrivateAggregationBindings();
+  PrivateAggregationBindings* private_aggregation_bindings() {
+    return private_aggregation_bindings_.get();
+  }
+
   void AddRegisterAdBeaconBindings();
   RegisterAdBeaconBindings* register_ad_beacon_bindings() {
     return register_ad_beacon_bindings_.get();
@@ -84,6 +90,7 @@
   v8::Global<v8::Context> context_;
 
   std::unique_ptr<ForDebuggingOnlyBindings> for_debugging_only_bindings_;
+  std::unique_ptr<PrivateAggregationBindings> private_aggregation_bindings_;
   std::unique_ptr<RegisterAdBeaconBindings> register_ad_beacon_bindings_;
   std::unique_ptr<ReportBindings> report_bindings_;
   std::unique_ptr<SetBidBindings> set_bid_bindings_;
diff --git a/content/services/auction_worklet/context_recycler_unittest.cc b/content/services/auction_worklet/context_recycler_unittest.cc
index a782e08..213be81c 100644
--- a/content/services/auction_worklet/context_recycler_unittest.cc
+++ b/content/services/auction_worklet/context_recycler_unittest.cc
@@ -13,9 +13,13 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "content/common/aggregatable_report.mojom-shared.h"
+#include "content/common/aggregatable_report.mojom.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/for_debugging_only_bindings.h"
+#include "content/services/auction_worklet/private_aggregation_bindings.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
 #include "content/services/auction_worklet/report_bindings.h"
 #include "content/services/auction_worklet/set_bid_bindings.h"
@@ -24,9 +28,11 @@
 #include "gin/dictionary.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/interest_group/interest_group.h"
 #include "v8/include/v8-context.h"
+#include "v8/include/v8-primitive.h"
 
 using testing::ElementsAre;
 using testing::Pair;
@@ -503,4 +509,354 @@
   }
 }
 
+// Exercise PrivateAggregationBindings, and make sure they reset properly.
+TEST_F(ContextRecyclerTest, PrivateAggregationBindings) {
+  using PrivateAggregationRequests =
+      std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
+  const char kScript[] = R"(
+    function test(args) {
+      // Passing BigInts in directly is complicated so we construct them from
+      // strings.
+      if (typeof args.bucket === "string") {
+        args.bucket = BigInt(args.bucket);
+      }
+      privateAggregation.sendHistogramReport(args);
+    }
+  )";
+
+  v8::Local<v8::UnboundScript> script = Compile(kScript);
+  ASSERT_FALSE(script.IsEmpty());
+
+  ContextRecycler context_recycler(helper_.get());
+  context_recycler.AddPrivateAggregationBindings();
+
+  // Basic test
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 123);
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(/*bucket=*/123, /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // BigInt bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("123"));
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(/*bucket=*/123, /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // Large bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("18446744073709551616"));
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(
+            /*bucket=*/absl::MakeUint128(/*high=*/1, /*low=*/0), /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // Maximum bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("340282366920938463463374607431768211455"));
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(/*bucket=*/absl::Uint128Max(), /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // Zero bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 0);
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(/*bucket=*/0, /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // Zero value
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 123);
+    dict.Set("value", 0);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(error_msgs, ElementsAre());
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution(/*bucket=*/123, /*value=*/0);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request(
+        expected_contribution.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 1u);
+    EXPECT_EQ(pa_requests[0], expected_request.Clone());
+  }
+
+  // Multiple requests
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    {
+      gin::Dictionary dict_1 = gin::Dictionary::CreateEmpty(helper_->isolate());
+      dict_1.Set("bucket", 123);
+      dict_1.Set("value", 45);
+
+      Run(scope, script, "test", error_msgs,
+          gin::ConvertToV8(helper_->isolate(), dict_1));
+      EXPECT_THAT(error_msgs, ElementsAre());
+    }
+    {
+      gin::Dictionary dict_2 = gin::Dictionary::CreateEmpty(helper_->isolate());
+      dict_2.Set("bucket", 678);
+      dict_2.Set("value", 90);
+
+      Run(scope, script, "test", error_msgs,
+          gin::ConvertToV8(helper_->isolate(), dict_2));
+      EXPECT_THAT(error_msgs, ElementsAre());
+    }
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution_1(/*bucket=*/123, /*value=*/45);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request_1(
+        expected_contribution_1.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    content::mojom::AggregatableReportHistogramContribution
+        expected_contribution_2(/*bucket=*/678, /*value=*/90);
+    auction_worklet::mojom::PrivateAggregationRequest expected_request_2(
+        expected_contribution_2.Clone(),
+        content::mojom::AggregationServiceMode::kDefault);
+
+    PrivateAggregationRequests pa_requests =
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests();
+    ASSERT_EQ(pa_requests.size(), 2u);
+    EXPECT_EQ(pa_requests[0], expected_request_1.Clone());
+    EXPECT_EQ(pa_requests[1], expected_request_2.Clone());
+  }
+
+  // Non-integer bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 12.3);
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "Bucket must be either an integer Number or BigInt."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Non-integer value
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 123);
+    dict.Set("value", 4.5);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "Value must be an integer Number."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Too large bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("340282366920938463463374607431768211456"));
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "BigInt is too large."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Negative bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", -1);
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "Bucket must be either an integer Number or BigInt."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Negative BigInt bucket
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", std::string("-1"));
+    dict.Set("value", 45);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "BigInt must be non-negative."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+
+  // Negative value
+  {
+    ContextRecyclerScope scope(context_recycler);
+    std::vector<std::string> error_msgs;
+
+    gin::Dictionary dict = gin::Dictionary::CreateEmpty(helper_->isolate());
+    dict.Set("bucket", 123);
+    dict.Set("value", -1);
+
+    Run(scope, script, "test", error_msgs,
+        gin::ConvertToV8(helper_->isolate(), dict));
+    EXPECT_THAT(
+        error_msgs,
+        ElementsAre("https://example.org/script.js:8 Uncaught TypeError: "
+                    "Value must be non-negative."));
+
+    EXPECT_TRUE(context_recycler.private_aggregation_bindings()
+                    ->TakePrivateAggregationRequests()
+                    .empty());
+  }
+}
+
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/private_aggregation_bindings.cc b/content/services/auction_worklet/private_aggregation_bindings.cc
new file mode 100644
index 0000000..3b1694d
--- /dev/null
+++ b/content/services/auction_worklet/private_aggregation_bindings.cc
@@ -0,0 +1,217 @@
+// 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 "content/services/auction_worklet/private_aggregation_bindings.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "content/common/aggregatable_report.mojom.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "v8/include/v8-context.h"
+#include "v8/include/v8-exception.h"
+#include "v8/include/v8-external.h"
+#include "v8/include/v8-function-callback.h"
+#include "v8/include/v8-local-handle.h"
+#include "v8/include/v8-object.h"
+#include "v8/include/v8-template.h"
+
+namespace auction_worklet {
+
+namespace {
+
+// If returns `absl::nullopt`, will output an error to `error_out`.
+absl::optional<absl::uint128> ConvertBigIntToUint128(
+    v8::MaybeLocal<v8::BigInt> maybe_bigint,
+    std::string* error_out) {
+  if (maybe_bigint.IsEmpty()) {
+    *error_out = "Failed to interpret as BigInt";
+    return absl::nullopt;
+  }
+
+  v8::Local<v8::BigInt> local_bigint = maybe_bigint.ToLocalChecked();
+  if (local_bigint.IsEmpty()) {
+    *error_out = "Failed to interpret as BigInt";
+    return absl::nullopt;
+  }
+  if (local_bigint->WordCount() > 2) {
+    *error_out = "BigInt is too large";
+    return absl::nullopt;
+  }
+  // Signals the size of the `words` array to `ToWordsArray()`. The number of
+  // elements actually used is then written here by the function.
+  int word_count = 2;
+  int sign_bit = 0;
+  uint64_t words[2];  // Least significant to most significant.
+  local_bigint->ToWordsArray(&sign_bit, &word_count, words);
+  if (sign_bit) {
+    *error_out = "BigInt must be non-negative";
+    return absl::nullopt;
+  }
+  if (word_count == 0) {
+    words[0] = 0;
+  }
+  if (word_count <= 1) {
+    words[1] = 0;
+  }
+
+  return absl::MakeUint128(words[1], words[0]);
+}
+
+}  // namespace
+
+PrivateAggregationBindings::PrivateAggregationBindings(
+    AuctionV8Helper* v8_helper)
+    : v8_helper_(v8_helper) {}
+
+PrivateAggregationBindings::~PrivateAggregationBindings() = default;
+
+void PrivateAggregationBindings::FillInGlobalTemplate(
+    v8::Local<v8::ObjectTemplate> global_template) {
+  v8::Local<v8::External> v8_this =
+      v8::External::New(v8_helper_->isolate(), this);
+
+  v8::Local<v8::ObjectTemplate> private_aggregation_template =
+      v8::ObjectTemplate::New(v8_helper_->isolate());
+  v8::Local<v8::FunctionTemplate> function_template = v8::FunctionTemplate::New(
+      v8_helper_->isolate(), &PrivateAggregationBindings::SendHistogramReport,
+      v8_this);
+  function_template->RemovePrototype();
+
+  private_aggregation_template->Set(
+      v8_helper_->CreateStringFromLiteral("sendHistogramReport"),
+      function_template);
+
+  global_template->Set(
+      v8_helper_->CreateStringFromLiteral("privateAggregation"),
+      private_aggregation_template);
+}
+
+void PrivateAggregationBindings::Reset() {
+  private_aggregation_requests_.clear();
+}
+
+std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
+PrivateAggregationBindings::TakePrivateAggregationRequests() {
+  return std::move(private_aggregation_requests_);
+}
+
+void PrivateAggregationBindings::SendHistogramReport(
+    const v8::FunctionCallbackInfo<v8::Value>& args) {
+  PrivateAggregationBindings* bindings =
+      static_cast<PrivateAggregationBindings*>(
+          v8::External::Cast(*args.Data())->Value());
+  v8::Isolate* isolate = args.GetIsolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+  AuctionV8Helper* v8_helper = bindings->v8_helper_;
+
+  if (args.Length() != 1 || args[0].IsEmpty() || !args[0]->IsObject()) {
+    isolate->ThrowException(
+        v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+            "sendHistogramReport requires 1 object parameter")));
+    return;
+  }
+
+  gin::Dictionary dict(isolate);
+
+  if (!gin::ConvertFromV8(isolate, args[0], &dict)) {
+    isolate->ThrowException(
+        v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+            "Invalid argument in sendHistogramReport")));
+    return;
+  }
+
+  v8::Local<v8::Value> js_bucket;
+  v8::Local<v8::Value> js_value;
+
+  if (!dict.Get("bucket", &js_bucket) || js_bucket.IsEmpty()) {
+    isolate->ThrowException(
+        v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+            "Invalid or missing bucket in sendHistogramReport argument")));
+    return;
+  }
+
+  if (!dict.Get("value", &js_value) || js_value.IsEmpty()) {
+    isolate->ThrowException(
+        v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+            "Invalid or missing value in sendHistogramReport argument")));
+    return;
+  }
+
+  absl::uint128 bucket;
+  int value;
+
+  if (js_bucket->IsUint32()) {
+    v8::Maybe<uint32_t> maybe_bucket = js_bucket->Uint32Value(context);
+    if (maybe_bucket.IsNothing()) {
+      isolate->ThrowException(
+          v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+              "Failed to interpret value as integer")));
+      return;
+    }
+    bucket = maybe_bucket.ToChecked();
+  } else if (js_bucket->IsBigInt()) {
+    std::string error;
+    absl::optional<absl::uint128> maybe_bucket =
+        ConvertBigIntToUint128(js_bucket->ToBigInt(context), &error);
+    if (!maybe_bucket.has_value()) {
+      DCHECK(base::IsStringUTF8(error));
+      isolate->ThrowException(v8::Exception::TypeError(
+          v8_helper->CreateUtf8String(error).ToLocalChecked()));
+      return;
+    }
+    bucket = maybe_bucket.value();
+  } else {
+    isolate->ThrowException(
+        v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+            "Bucket must be either an integer Number or BigInt")));
+    return;
+  }
+
+  if (js_value->IsInt32()) {
+    v8::Maybe<int32_t> maybe_value = js_value->Int32Value(context);
+    if (maybe_value.IsNothing()) {
+      isolate->ThrowException(
+          v8::Exception::TypeError(v8_helper->CreateStringFromLiteral(
+              "Failed to interpret value as integer")));
+      return;
+    }
+    value = maybe_value.ToChecked();
+  } else if (js_value->IsBigInt()) {
+    isolate->ThrowException(v8::Exception::TypeError(
+        v8_helper->CreateStringFromLiteral("Value cannot be a BigInt")));
+    return;
+  } else {
+    isolate->ThrowException(v8::Exception::TypeError(
+        v8_helper->CreateStringFromLiteral("Value must be an integer Number")));
+    return;
+  }
+
+  if (value < 0) {
+    isolate->ThrowException(v8::Exception::TypeError(
+        v8_helper->CreateStringFromLiteral("Value must be non-negative")));
+    return;
+  }
+
+  content::mojom::AggregatableReportHistogramContributionPtr contribution =
+      content::mojom::AggregatableReportHistogramContribution::New(bucket,
+                                                                   value);
+
+  bindings->private_aggregation_requests_.push_back(
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          std::move(contribution),
+          // TODO(alexmt): consider allowing this to be set
+          content::mojom::AggregationServiceMode::kDefault));
+}
+
+}  // namespace auction_worklet
diff --git a/content/services/auction_worklet/private_aggregation_bindings.h b/content/services/auction_worklet/private_aggregation_bindings.h
new file mode 100644
index 0000000..b04e844
--- /dev/null
+++ b/content/services/auction_worklet/private_aggregation_bindings.h
@@ -0,0 +1,52 @@
+// 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 CONTENT_SERVICES_AUCTION_WORKLET_PRIVATE_AGGREGATION_BINDINGS_H_
+#define CONTENT_SERVICES_AUCTION_WORKLET_PRIVATE_AGGREGATION_BINDINGS_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "content/common/content_export.h"
+#include "content/services/auction_worklet/context_recycler.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
+#include "v8/include/v8-forward.h"
+
+namespace auction_worklet {
+
+class AuctionV8Helper;
+
+// Class to manage bindings for the Private Aggregation API. Expected to be used
+// for a context managed by `ContextRecycler`. Throws exceptions when invalid
+// arguments are detected.
+class CONTENT_EXPORT PrivateAggregationBindings : public Bindings {
+ public:
+  explicit PrivateAggregationBindings(AuctionV8Helper* v8_helper);
+  PrivateAggregationBindings(const PrivateAggregationBindings&) = delete;
+  PrivateAggregationBindings& operator=(const PrivateAggregationBindings&) =
+      delete;
+  ~PrivateAggregationBindings() override;
+
+  // Add privateAggregation object to `global_template`. `this` must outlive the
+  // template.
+  void FillInGlobalTemplate(
+      v8::Local<v8::ObjectTemplate> global_template) override;
+  void Reset() override;
+
+  std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
+  TakePrivateAggregationRequests();
+
+ private:
+  static void SendHistogramReport(
+      const v8::FunctionCallbackInfo<v8::Value>& args);
+
+  const raw_ptr<AuctionV8Helper> v8_helper_;
+
+  std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>
+      private_aggregation_requests_;
+};
+
+}  // namespace auction_worklet
+
+#endif  // CONTENT_SERVICES_AUCTION_WORKLET_PRIVATE_AGGREGATION_BINDINGS_H_
diff --git a/content/services/auction_worklet/public/mojom/BUILD.gn b/content/services/auction_worklet/public/mojom/BUILD.gn
index d52f717..9b4b558 100644
--- a/content/services/auction_worklet/public/mojom/BUILD.gn
+++ b/content/services/auction_worklet/public/mojom/BUILD.gn
@@ -11,9 +11,11 @@
   sources = [
     "auction_worklet_service.mojom",
     "bidder_worklet.mojom",
+    "private_aggregation_request.mojom",
     "seller_worklet.mojom",
   ]
   deps = [
+    "//content/common:mojo_bindings",
     "//mojo/public/mojom/base",
     "//sandbox/policy/mojom",
     "//services/network/public/mojom",
diff --git a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
index 0f4a825..000c034d 100644
--- a/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/bidder_worklet.mojom
@@ -4,6 +4,7 @@
 
 module auction_worklet.mojom;
 
+import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
@@ -161,6 +162,8 @@
   //  update to the interest group priority.
   //  TODO(https://crbug.com/657632): Update when optional integers supported.
   //
+  // `pa_requests` The various requests made to the Private Aggregation API.
+  //
   // `errors` The various error messages to be used for debugging. These are too
   //  sensitive for the renderer to see. There may be errors even when a bid
   //  is offered, and there may be no errors when there's no bid. Includes
@@ -182,6 +185,7 @@
           url.mojom.Url? debug_win_report_url,
           double set_priority,
           bool has_set_priority,
+          array<PrivateAggregationRequest> pa_requests,
           array<string> errors);
 
   // Sends pending bidding signals URL requests, if any. Unlike with
@@ -252,6 +256,8 @@
   // `ad_beacon_map` The map of ad reporting events to URLs for fenced frame
   //  reporting.
   //
+  // `pa_requests` The various requests made to the Private Aggregation API.
+  //
   // `errors` is an array of any errors that occurred while attempting
   //  to run the worklet's reportWin() method. These are too sensitive for
   //  the renderer to see. There may be errors even when a `report_url` is
@@ -272,6 +278,7 @@
             uint64 trace_id) => (
                 url.mojom.Url? report_url,
                 map<string, url.mojom.Url> ad_beacon_map,
+                array<PrivateAggregationRequest> pa_requests,
                 array<string> errors);
 
   // Establishes a debugger connection to the worklet.
diff --git a/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom b/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom
new file mode 100644
index 0000000..6326a63a
--- /dev/null
+++ b/content/services/auction_worklet/public/mojom/private_aggregation_request.mojom
@@ -0,0 +1,14 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module auction_worklet.mojom;
+
+import "content/common/aggregatable_report.mojom";
+
+// Represents a request made to the Private Aggregation API (e.g. via
+// `privateAggregation.sendHistogramReport()`).
+struct PrivateAggregationRequest {
+  content.mojom.AggregatableReportHistogramContribution contribution;
+  content.mojom.AggregationServiceMode aggregation_mode;
+};
diff --git a/content/services/auction_worklet/public/mojom/seller_worklet.mojom b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
index 0358716..6265c844 100644
--- a/content/services/auction_worklet/public/mojom/seller_worklet.mojom
+++ b/content/services/auction_worklet/public/mojom/seller_worklet.mojom
@@ -4,6 +4,7 @@
 
 module auction_worklet.mojom;
 
+import "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "third_party/blink/public/mojom/devtools/devtools_agent.mojom";
@@ -134,6 +135,8 @@
   //  API that will be replaced with standardized reporting APIs once available.
   //  It must be a valid HTTPS URL.
   //
+  // `pa_requests` The various requests made to the Private Aggregation API.
+  //
   // `errors` are various error messages to be used for debugging. These are too
   //  sensitive for the renderers to see. `errors` should not be assumed to be
   //  empty if `score` is positive, nor should it be assumed to be non-empty if
@@ -156,6 +159,7 @@
               bool has_scoring_signals_data_version,
               url.mojom.Url? debug_loss_report_url,
               url.mojom.Url? debug_win_report_url,
+              array<PrivateAggregationRequest> pa_requests,
               array<string> errors);
 
   // Hint to the worklet to send a network request for any needed trusted
@@ -222,6 +226,8 @@
   // `ad_beacon_map` The map of ad reporting events to URLs for fenced frame
   //  reporting.
   //
+  // `pa_requests` The various requests made to the Private Aggregation API.
+  //
   // `errors` are various error messages to be used for debugging. These are too
   //  sensitive for the renderers to see. `errors` should not be assumed to be
   //  empty if the other values are populated, nor should it be assumed to be
@@ -243,6 +249,7 @@
           (string? signals_for_winner,
            url.mojom.Url? report_url,
            map<string, url.mojom.Url> ad_beacon_map,
+           array<PrivateAggregationRequest> pa_requests,
            array<string> error_msgs);
 
   // Establishes a debugger connection to the worklet.
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index aa72572..e3d9e14 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -23,6 +23,7 @@
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/context_recycler.h"
 #include "content/services/auction_worklet/for_debugging_only_bindings.h"
+#include "content/services/auction_worklet/private_aggregation_bindings.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/register_ad_beacon_bindings.h"
@@ -449,6 +450,7 @@
   // repeated calls to this worklet, or to calls to any other worklet.
   ContextRecycler context_recycler(v8_helper_.get());
   context_recycler.AddForDebuggingOnlyBindings();
+  context_recycler.AddPrivateAggregationBindings();
   ContextRecyclerScope context_recycler_scope(context_recycler);
   v8::Local<v8::Context> context = context_recycler_scope.GetContext();
 
@@ -529,15 +531,18 @@
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "score_ad", trace_id);
 
   if (!got_return_value) {
-    // Keep debug loss reports since `scoreAd()` might use it to detect script
-    // timeout or failures.
+    // Keep debug loss reports and Private Aggregation API requests since
+    // `scoreAd()` might use them to detect script timeout or failures.
     PostScoreAdCallbackToUserThread(
         std::move(callback), /*score=*/0,
         /*component_auction_modified_bid_params=*/nullptr,
         /*scoring_signals_data_version=*/absl::nullopt,
         /*debug_loss_report_url=*/
         context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
-        /*debug_win_report_url=*/absl::nullopt, std::move(errors_out));
+        /*debug_win_report_url=*/absl::nullopt,
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
+        std::move(errors_out));
     return;
   }
 
@@ -554,8 +559,10 @@
       errors_out.push_back(
           base::StrCat({decision_logic_url_.spec(),
                         " scoreAd() did not return an object or a number."}));
-      PostScoreAdCallbackToUserThreadOnError(std::move(callback),
-                                             std::move(errors_out));
+      PostScoreAdCallbackToUserThreadOnError(
+          std::move(callback), std::move(errors_out),
+          context_recycler.private_aggregation_bindings()
+              ->TakePrivateAggregationRequests());
       return;
     }
 
@@ -565,8 +572,10 @@
       errors_out.push_back(
           base::StrCat({decision_logic_url_.spec(),
                         " scoreAd() return value has incorrect structure."}));
-      PostScoreAdCallbackToUserThreadOnError(std::move(callback),
-                                             std::move(errors_out));
+      PostScoreAdCallbackToUserThreadOnError(
+          std::move(callback), std::move(errors_out),
+          context_recycler.private_aggregation_bindings()
+              ->TakePrivateAggregationRequests());
       return;
     }
 
@@ -604,8 +613,10 @@
         {decision_logic_url_.spec(),
          " scoreAd() return value does not have allowComponentAuction set to "
          "true. Ad dropped from component auction."}));
-    PostScoreAdCallbackToUserThreadOnError(std::move(callback),
-                                           std::move(errors_out));
+    PostScoreAdCallbackToUserThreadOnError(
+        std::move(callback), std::move(errors_out),
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests());
     return;
   }
 
@@ -613,8 +624,10 @@
   if (std::isnan(score) || !std::isfinite(score)) {
     errors_out.push_back(base::StrCat(
         {decision_logic_url_.spec(), " scoreAd() returned an invalid score."}));
-    PostScoreAdCallbackToUserThreadOnError(std::move(callback),
-                                           std::move(errors_out));
+    PostScoreAdCallbackToUserThreadOnError(
+        std::move(callback), std::move(errors_out),
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests());
     return;
   }
 
@@ -627,6 +640,8 @@
         scoring_signals_data_version,
         context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
         context_recycler.for_debugging_only_bindings()->TakeWinReportUrl(),
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
         std::move(errors_out));
     return;
   }
@@ -641,8 +656,10 @@
         component_auction_modified_bid_params->bid <= 0.0) {
       errors_out.push_back(base::StrCat(
           {decision_logic_url_.spec(), " scoreAd() returned an invalid bid."}));
-      PostScoreAdCallbackToUserThreadOnError(std::move(callback),
-                                             std::move(errors_out));
+      PostScoreAdCallbackToUserThreadOnError(
+          std::move(callback), std::move(errors_out),
+          context_recycler.private_aggregation_bindings()
+              ->TakePrivateAggregationRequests());
       return;
     }
   }
@@ -653,6 +670,8 @@
       scoring_signals_data_version,
       context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(),
       context_recycler.for_debugging_only_bindings()->TakeWinReportUrl(),
+      context_recycler.private_aggregation_bindings()
+          ->TakePrivateAggregationRequests(),
       std::move(errors_out));
 }
 
@@ -681,6 +700,7 @@
   ContextRecycler context_recycler(v8_helper_.get());
   context_recycler.AddReportBindings();
   context_recycler.AddRegisterAdBeaconBindings();
+  context_recycler.AddPrivateAggregationBindings();
   ContextRecyclerScope context_recycler_scope(context_recycler);
   v8::Local<v8::Context> context = context_recycler_scope.GetContext();
 
@@ -692,6 +712,7 @@
                                          /*signals_for_winner=*/absl::nullopt,
                                          /*report_url=*/absl::nullopt,
                                          /*ad_beacon_map=*/{},
+                                         /*pa_requests=*/{},
                                          /*errors=*/std::vector<std::string>());
     return;
   }
@@ -718,6 +739,7 @@
                                          /*signals_for_winner=*/absl::nullopt,
                                          /*report_url=*/absl::nullopt,
                                          /*ad_beacon_map=*/{},
+                                         /*pa_requests=*/{},
                                          /*errors=*/std::vector<std::string>());
     return;
   }
@@ -738,6 +760,7 @@
           /*signals_for_winner=*/absl::nullopt,
           /*report_url=*/absl::nullopt,
           /*ad_beacon_map=*/{},
+          /*pa_requests=*/{},
           /*errors=*/std::vector<std::string>());
       return;
     }
@@ -759,9 +782,13 @@
   TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_result", trace_id);
 
   if (!got_return_value) {
+    // Keep Private Aggregation API requests since `reportReport()` might use it
+    // to detect script timeout or failures.
     PostReportResultCallbackToUserThread(
         std::move(callback), /*signals_for_winner=*/absl::nullopt,
         /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{},
+        context_recycler.private_aggregation_bindings()
+            ->TakePrivateAggregationRequests(),
         std::move(errors_out));
     return;
   }
@@ -778,6 +805,8 @@
       std::move(callback), std::move(signals_for_winner),
       context_recycler.report_bindings()->report_url(),
       context_recycler.register_ad_beacon_bindings()->TakeAdBeaconMap(),
+      context_recycler.private_aggregation_bindings()
+          ->TakePrivateAggregationRequests(),
       std::move(errors_out));
 }
 
@@ -810,13 +839,15 @@
 
 void SellerWorklet::V8State::PostScoreAdCallbackToUserThreadOnError(
     ScoreAdCallbackInternal callback,
-    std::vector<std::string> errors) {
+    std::vector<std::string> errors,
+    PrivateAggregationRequests pa_requests) {
   PostScoreAdCallbackToUserThread(
       std::move(callback), /*score=*/0,
       /*component_auction_modified_bid_params=*/nullptr,
       /*scoring_signals_data_version=*/absl::nullopt,
       /*debug_loss_report_url=*/absl::nullopt,
-      /*debug_win_report_url=*/absl::nullopt, std::move(errors));
+      /*debug_win_report_url=*/absl::nullopt, std::move(pa_requests),
+      std::move(errors));
 }
 
 void SellerWorklet::V8State::PostScoreAdCallbackToUserThread(
@@ -827,6 +858,7 @@
     absl::optional<uint32_t> scoring_signals_data_version,
     absl::optional<GURL> debug_loss_report_url,
     absl::optional<GURL> debug_win_report_url,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
   user_thread_->PostTask(
@@ -835,7 +867,8 @@
                      std::move(component_auction_modified_bid_params),
                      scoring_signals_data_version,
                      std::move(debug_loss_report_url),
-                     std::move(debug_win_report_url), std::move(errors)));
+                     std::move(debug_win_report_url), std::move(pa_requests),
+                     std::move(errors)));
 }
 
 void SellerWorklet::V8State::PostReportResultCallbackToUserThread(
@@ -843,13 +876,14 @@
     absl::optional<std::string> signals_for_winner,
     absl::optional<GURL> report_url,
     base::flat_map<std::string, GURL> ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
   user_thread_->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), std::move(signals_for_winner),
                      std::move(report_url), std::move(ad_beacon_map),
-                     std::move(errors)));
+                     std::move(pa_requests), std::move(errors)));
 }
 
 void SellerWorklet::ResumeIfPaused() {
@@ -965,6 +999,7 @@
     absl::optional<uint32_t> scoring_signals_data_version,
     absl::optional<GURL> debug_loss_report_url,
     absl::optional<GURL> debug_win_report_url,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
 
@@ -977,7 +1012,7 @@
       .Run(score, std::move(component_auction_modified_bid_params),
            scoring_signals_data_version.value_or(0),
            scoring_signals_data_version.has_value(), debug_loss_report_url,
-           debug_win_report_url, errors);
+           debug_win_report_url, std::move(pa_requests), std::move(errors));
   score_ad_tasks_.erase(task);
 }
 
@@ -1012,6 +1047,7 @@
     const absl::optional<std::string> signals_for_winner,
     const absl::optional<GURL> report_url,
     base::flat_map<std::string, GURL> ad_beacon_map,
+    PrivateAggregationRequests pa_requests,
     std::vector<std::string> errors) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
 
@@ -1019,7 +1055,8 @@
     errors.insert(errors.begin(), load_script_error_msg_.value());
 
   std::move(task->callback)
-      .Run(signals_for_winner, report_url, ad_beacon_map, errors);
+      .Run(signals_for_winner, report_url, ad_beacon_map,
+           std::move(pa_requests), std::move(errors));
   report_result_tasks_.erase(task);
 }
 
diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h
index 33980aa..fc8d24a5 100644
--- a/content/services/auction_worklet/seller_worklet.h
+++ b/content/services/auction_worklet/seller_worklet.h
@@ -21,6 +21,7 @@
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
+#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
 #include "content/services/auction_worklet/trusted_signals.h"
 #include "content/services/auction_worklet/trusted_signals_request_manager.h"
@@ -52,6 +53,9 @@
   using ClosePipeCallback =
       base::OnceCallback<void(const std::string& description)>;
 
+  using PrivateAggregationRequests =
+      std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
   // Starts loading the worklet script on construction.
   SellerWorklet(scoped_refptr<AuctionV8Helper> v8_helper,
                 bool pause_for_debugger_on_start,
@@ -190,11 +194,13 @@
         absl::optional<uint32_t> scoring_signals_data_version,
         absl::optional<GURL> debug_loss_report_url,
         absl::optional<GURL> debug_win_report_url,
+        PrivateAggregationRequests pa_requests,
         std::vector<std::string> errors)>;
     using ReportResultCallbackInternal =
         base::OnceCallback<void(absl::optional<std::string> signals_for_winner,
                                 absl::optional<GURL> report_url,
                                 base::flat_map<std::string, GURL> ad_beacon_map,
+                                PrivateAggregationRequests pa_requests,
                                 std::vector<std::string> errors)>;
 
     V8State(scoped_refptr<AuctionV8Helper> v8_helper,
@@ -251,7 +257,8 @@
     // report URLs, even if the methods to set them have been invoked.
     void PostScoreAdCallbackToUserThreadOnError(
         ScoreAdCallbackInternal callback,
-        std::vector<std::string> errors);
+        std::vector<std::string> errors,
+        PrivateAggregationRequests pa_requests = {});
 
     void PostScoreAdCallbackToUserThread(
         ScoreAdCallbackInternal callback,
@@ -261,6 +268,7 @@
         absl::optional<uint32_t> scoring_signals_data_version,
         absl::optional<GURL> debug_loss_report_url,
         absl::optional<GURL> debug_win_report_url,
+        PrivateAggregationRequests pa_requests,
         std::vector<std::string> errors);
 
     void PostReportResultCallbackToUserThread(
@@ -268,6 +276,7 @@
         absl::optional<std::string> signals_for_winner,
         absl::optional<GURL> report_url,
         base::flat_map<std::string, GURL> ad_beacon_map,
+        PrivateAggregationRequests pa_requests,
         std::vector<std::string> errors);
 
     static void PostResumeToUserThread(
@@ -318,6 +327,7 @@
       absl::optional<uint32_t> scoring_signals_data_version,
       absl::optional<GURL> debug_loss_report_url,
       absl::optional<GURL> debug_win_report_url,
+      PrivateAggregationRequests pa_requests,
       std::vector<std::string> errors);
 
   // Runs the specified queued ReportWinTask. All code must already be loaded by
@@ -329,6 +339,7 @@
       absl::optional<std::string> signals_for_winner,
       absl::optional<GURL> report_url,
       base::flat_map<std::string, GURL> ad_beacon_map,
+      PrivateAggregationRequests pa_requests,
       std::vector<std::string> errors);
 
   // Returns true if unpaused and the script has loaded.
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index 78077162..5c743df8 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -40,6 +40,9 @@
 namespace auction_worklet {
 namespace {
 
+using PrivateAggregationRequests =
+    std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>;
+
 // Very short time used by some tests that want to wait until just before a
 // timer triggers.
 constexpr base::TimeDelta kTinyTime = base::Microseconds(1);
@@ -166,13 +169,13 @@
       absl::optional<uint32_t> expected_data_version = {},
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
-      const absl::optional<GURL>& expected_debug_win_report_url =
-          absl::nullopt) {
+      const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     RunScoreAdWithJavascriptExpectingResult(
         CreateScoreAdScript(raw_return_value), expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
-        expected_debug_win_report_url);
+        expected_debug_win_report_url, std::move(expected_pa_requests));
   }
 
   // Behaves just like RunScoreAdWithReturnValueExpectingResult(), but
@@ -192,8 +195,8 @@
       absl::optional<uint32_t> expected_data_version = {},
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
-      const absl::optional<GURL>& expected_debug_win_report_url =
-          absl::nullopt) {
+      const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
                           CreateScoreAdScript(raw_return_value));
     auto seller_worklet = CreateWorklet();
@@ -203,7 +206,8 @@
         seller_worklet.get(), expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
-        expected_debug_win_report_url, run_loop.QuitClosure());
+        expected_debug_win_report_url, std::move(expected_pa_requests),
+        run_loop.QuitClosure());
     task_environment_.FastForwardBy(expected_duration - kTinyTime);
     EXPECT_FALSE(run_loop.AnyQuitCalled());
     task_environment_.FastForwardBy(kTinyTime);
@@ -223,8 +227,8 @@
       absl::optional<uint32_t> expected_data_version = {},
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
-      const absl::optional<GURL>& expected_debug_win_report_url =
-          absl::nullopt) {
+      const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     SCOPED_TRACE(javascript);
     AddJavascriptResponse(&url_loader_factory_, decision_logic_url_,
                           javascript);
@@ -232,7 +236,7 @@
         expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
-        expected_debug_win_report_url);
+        expected_debug_win_report_url, std::move(expected_pa_requests));
   }
 
   // Runs score_ad() script, checking result and invoking provided closure
@@ -246,6 +250,7 @@
       absl::optional<uint32_t> expected_data_version,
       const absl::optional<GURL>& expected_debug_loss_report_url,
       const absl::optional<GURL>& expected_debug_win_report_url,
+      PrivateAggregationRequests expected_pa_requests,
       base::OnceClosure done_closure) {
     seller_worklet->ScoreAd(
         ad_metadata_, bid_, auction_ad_config_non_shared_params_,
@@ -261,6 +266,7 @@
                absl::optional<uint32_t> expected_data_version,
                const absl::optional<GURL>& expected_debug_loss_report_url,
                const absl::optional<GURL>& expected_debug_win_report_url,
+               PrivateAggregationRequests expected_pa_requests,
                std::vector<std::string> expected_errors,
                base::OnceClosure done_closure, double score,
                mojom::ComponentAuctionModifiedBidParamsPtr
@@ -268,6 +274,7 @@
                uint32_t data_version, bool has_data_version,
                const absl::optional<GURL>& debug_loss_report_url,
                const absl::optional<GURL>& debug_win_report_url,
+               PrivateAggregationRequests pa_requests,
                const std::vector<std::string>& errors) {
               absl::optional<uint32_t> maybe_data_version;
               if (has_data_version)
@@ -291,14 +298,15 @@
               EXPECT_EQ(expected_debug_loss_report_url, debug_loss_report_url);
               EXPECT_EQ(expected_debug_win_report_url, debug_win_report_url);
               EXPECT_EQ(expected_data_version, maybe_data_version);
+              EXPECT_EQ(expected_pa_requests, pa_requests);
               EXPECT_EQ(expected_errors, errors);
               std::move(done_closure).Run();
             },
             expected_score,
             std::move(expected_component_auction_modified_bid_params),
             expected_data_version, expected_debug_loss_report_url,
-            expected_debug_win_report_url, expected_errors,
-            std::move(done_closure)));
+            expected_debug_win_report_url, std::move(expected_pa_requests),
+            expected_errors, std::move(done_closure)));
   }
 
   void RunScoreAdOnWorkletExpectingCallbackNeverInvoked(
@@ -317,6 +325,7 @@
                           bool has_scoring_signals_data_version,
                           const absl::optional<GURL>& debug_loss_report_url,
                           const absl::optional<GURL>& debug_win_report_url,
+                          PrivateAggregationRequests pa_requests,
                           const std::vector<std::string>& errors) {
           ADD_FAILURE() << "This should not be invoked";
         }));
@@ -334,14 +343,15 @@
       absl::optional<uint32_t> expected_data_version = absl::nullopt,
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
-      const absl::optional<GURL>& expected_debug_win_report_url =
-          absl::nullopt) {
+      const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     base::RunLoop run_loop;
     RunScoreAdOnWorkletAsync(
         seller_worklet, expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
-        expected_debug_win_report_url, run_loop.QuitClosure());
+        expected_debug_win_report_url, std::move(expected_pa_requests),
+        run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -356,15 +366,15 @@
       absl::optional<uint32_t> expected_data_version = absl::nullopt,
       const absl::optional<GURL>& expected_debug_loss_report_url =
           absl::nullopt,
-      const absl::optional<GURL>& expected_debug_win_report_url =
-          absl::nullopt) {
+      const absl::optional<GURL>& expected_debug_win_report_url = absl::nullopt,
+      PrivateAggregationRequests expected_pa_requests = {}) {
     auto seller_worklet = CreateWorklet();
     ASSERT_TRUE(seller_worklet);
     RunScoreAdExpectingResultOnWorklet(
         seller_worklet.get(), expected_score, expected_errors,
         std::move(expected_component_auction_modified_bid_params),
         expected_data_version, expected_debug_loss_report_url,
-        expected_debug_win_report_url);
+        expected_debug_win_report_url, std::move(expected_pa_requests));
   }
 
   // Configures `url_loader_factory_` to return a report_result() script created
@@ -377,12 +387,14 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     RunReportResultWithJavascriptExpectingResult(
         CreateReportToScript(raw_return_value, extra_code),
         expected_signals_for_winner, expected_report_url,
-        expected_ad_beacon_map, expected_errors);
+        expected_ad_beacon_map, std::move(expected_pa_requests),
+        expected_errors);
   }
 
   // Configures `url_loader_factory_` to return the provided script, and then
@@ -394,6 +406,7 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     SCOPED_TRACE(javascript);
@@ -401,6 +414,7 @@
                           javascript);
     RunReportResultExpectingResult(expected_signals_for_winner,
                                    expected_report_url, expected_ad_beacon_map,
+                                   std::move(expected_pa_requests),
                                    expected_errors);
   }
 
@@ -412,6 +426,7 @@
       const absl::optional<std::string>& expected_signals_for_winner,
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
+      PrivateAggregationRequests expected_pa_requests,
       const std::vector<std::string>& expected_errors,
       base::OnceClosure done_closure) {
     seller_worklet->ReportResult(
@@ -428,20 +443,24 @@
             [](const absl::optional<std::string>& expected_signals_for_winner,
                const absl::optional<GURL>& expected_report_url,
                const base::flat_map<std::string, GURL>& expected_ad_beacon_map,
+               PrivateAggregationRequests expected_pa_requests,
                const std::vector<std::string>& expected_errors,
                base::OnceClosure done_closure,
                const absl::optional<std::string>& signals_for_winner,
                const absl::optional<GURL>& report_url,
                const base::flat_map<std::string, GURL>& ad_beacon_map,
+               PrivateAggregationRequests pa_requests,
                const std::vector<std::string>& errors) {
               EXPECT_EQ(expected_signals_for_winner, signals_for_winner);
               EXPECT_EQ(expected_report_url, report_url);
               EXPECT_EQ(expected_ad_beacon_map, ad_beacon_map);
+              EXPECT_EQ(expected_pa_requests, pa_requests);
               EXPECT_EQ(expected_errors, errors);
               std::move(done_closure).Run();
             },
             expected_signals_for_winner, expected_report_url,
-            expected_ad_beacon_map, expected_errors, std::move(done_closure)));
+            expected_ad_beacon_map, std::move(expected_pa_requests),
+            expected_errors, std::move(done_closure)));
   }
 
   void RunReportResultExpectingCallbackNeverInvoked(
@@ -460,6 +479,7 @@
             [](const absl::optional<std::string>& signals_for_winner,
                const absl::optional<GURL>& report_url,
                const base::flat_map<std::string, GURL>& ad_beacon_map,
+               PrivateAggregationRequests pa_requests,
                const std::vector<std::string>& errors) {
               ADD_FAILURE() << "This should not be invoked";
             }));
@@ -471,6 +491,7 @@
       const absl::optional<GURL>& expected_report_url,
       const base::flat_map<std::string, GURL>& expected_ad_beacon_map =
           base::flat_map<std::string, GURL>(),
+      PrivateAggregationRequests expected_pa_requests = {},
       const std::vector<std::string>& expected_errors =
           std::vector<std::string>()) {
     auto seller_worklet = CreateWorklet();
@@ -479,7 +500,8 @@
     base::RunLoop run_loop;
     RunReportResultExpectingResultAsync(
         seller_worklet.get(), expected_signals_for_winner, expected_report_url,
-        expected_ad_beacon_map, expected_errors, run_loop.QuitClosure());
+        expected_ad_beacon_map, std::move(expected_pa_requests),
+        expected_errors, run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -1203,6 +1225,7 @@
                              /*expected_data_version=*/absl::nullopt,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1244,6 +1267,7 @@
                              /*expected_data_version=*/absl::nullopt,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1307,6 +1331,7 @@
                              /*expected_data_version=*/i,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1373,6 +1398,7 @@
                              /*expected_data_version=*/absl::nullopt,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1430,6 +1456,7 @@
                              /*expected_data_version=*/10,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1496,6 +1523,7 @@
                              /*expected_data_version=*/10,
                              /*expected_debug_loss_report_url=*/absl::nullopt,
                              /*expected_debug_win_report_url=*/absl::nullopt,
+                             /*expected_pa_requests=*/{},
                              base::BindLambdaForTesting([&]() {
                                ++num_completed_worklets;
                                if (num_completed_worklets == kNumWorklets)
@@ -1542,6 +1570,110 @@
   run_loop.Run();
 }
 
+TEST_F(SellerWorkletTest, ScoreAdPrivateAggregationRequests) {
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest1 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/123,
+              /*value=*/45),
+          content::mojom::AggregationServiceMode::kDefault);
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest2 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/absl::MakeInt128(/*high=*/1, /*low=*/0),
+              /*value=*/1),
+          content::mojom::AggregationServiceMode::kDefault);
+
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript(
+            "5",
+            "privateAggregation.sendHistogramReport({bucket: 123, value: 45})"),
+        5, /*expected_errors=*/{},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // BigInt bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript("5",
+                            "privateAggregation.sendHistogramReport("
+                            "{bucket: 123n, value: 45})"),
+        5, /*expected_errors=*/{},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // Large bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript("5",
+                            "privateAggregation.sendHistogramReport("
+                            "{bucket: 18446744073709551616n, value: 1})"),
+        5, /*expected_errors=*/{},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // Multiple requests
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript("5", R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          privateAggregation.sendHistogramReport({bucket: 18446744073709551616n,
+                                                  value: 1});
+        )"),
+        5, /*expected_errors=*/{},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+
+  // An unrelated exception after sendHistogramReport shouldn't block the report
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunScoreAdWithJavascriptExpectingResult(
+        CreateScoreAdScript("5", R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          error;
+        )"),
+        0, /*expected_errors=*/
+        {"https://url.test/:6 Uncaught ReferenceError: error is not defined."},
+        mojom::ComponentAuctionModifiedBidParamsPtr(),
+        /*expected_data_version=*/absl::nullopt,
+        /*expected_debug_loss_report_url=*/absl::nullopt,
+        /*expected_debug_win_report_url=*/absl::nullopt,
+        std::move(expected_pa_requests));
+  }
+}
+
 // Test multiple ReportWin() calls on a single worklet, in parallel. Do this
 // twice, once before the worklet has loaded its Javascript, and once after, to
 // make sure both cases work.
@@ -1568,6 +1700,7 @@
           /*expected_report_url=*/
           GURL("https://" + base::NumberToString(bid_)),
           /*expected_ad_beacon_map=*/{},
+          /*expected_pa_requests=*/{},
           /*expected_errors=*/{},
           base::BindLambdaForTesting([&run_loop, &num_report_result_calls]() {
             ++num_report_result_calls;
@@ -1635,6 +1768,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:10 Uncaught ReferenceError: "
        "shrimp is not defined."});
 }
@@ -1654,6 +1788,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo must be passed a valid HTTPS url."});
   RunReportResultCreatedScriptExpectingResult(
@@ -1661,6 +1796,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo must be passed a valid HTTPS url."});
 
@@ -1671,6 +1807,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo may be called at most once."});
 
@@ -1689,6 +1826,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo must be passed a valid HTTPS url."});
   RunReportResultCreatedScriptExpectingResult(
@@ -1696,6 +1834,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo requires 1 string parameter."});
   RunReportResultCreatedScriptExpectingResult(
@@ -1703,6 +1842,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: "
        "sendReportTo requires 1 string parameter."});
 }
@@ -1713,6 +1853,7 @@
       /*expected_signals_for_winner=*/absl::nullopt,
       /*expected_report_url=*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught ReferenceError: Date is not defined."});
 }
 
@@ -1945,6 +2086,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:13 Uncaught TypeError: registerAdBeacon may be "
        "called at most once."});
 
@@ -1978,6 +2120,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -1987,6 +2130,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -1996,6 +2140,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon requires 1 "
        "object parameter."});
 
@@ -2009,6 +2154,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon object "
        "attributes must be strings."});
 
@@ -2022,6 +2168,7 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon invalid "
        "reporting url for key 'view': 'gopher://view.example.com/'."});
 
@@ -2035,10 +2182,106 @@
       /*expected_signals_for_winner=*/{},
       /*expected_report_url =*/absl::nullopt,
       /*expected_ad_beacon_map=*/{},
+      /*expected_pa_requests=*/{},
       {"https://url.test/:9 Uncaught TypeError: registerAdBeacon invalid "
        "reporting url for key 'view': 'http://view.example.com/'."});
 }
 
+TEST_F(SellerWorkletTest, ReportResultPrivateAggregationRequests) {
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest1 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/123,
+              /*value=*/45),
+          content::mojom::AggregationServiceMode::kDefault);
+  auction_worklet::mojom::PrivateAggregationRequestPtr kExpectedRequest2 =
+      auction_worklet::mojom::PrivateAggregationRequest::New(
+          content::mojom::AggregatableReportHistogramContribution::New(
+              /*bucket=*/absl::MakeInt128(/*high=*/1, /*low=*/0),
+              /*value=*/1),
+          content::mojom::AggregationServiceMode::kDefault);
+
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportResultCreatedScriptExpectingResult(
+        R"(5)",
+        R"(privateAggregation.sendHistogramReport({bucket: 123, value: 45});)",
+        /*expected_signals_for_winner=*/"5",
+        /*expected_report_url=*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // BigInt bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportResultCreatedScriptExpectingResult(
+        R"(5)",
+        R"(privateAggregation.sendHistogramReport({bucket: 123n, value: 45});)",
+        /*expected_signals_for_winner=*/"5",
+        /*expected_report_url=*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // Large bucket
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunReportResultCreatedScriptExpectingResult(
+        R"(5)",
+        R"(privateAggregation.sendHistogramReport(
+            {bucket: 18446744073709551616n, value: 1});)",
+        /*expected_signals_for_winner=*/"5",
+        /*expected_report_url=*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // Multiple requests
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+    expected_pa_requests.push_back(kExpectedRequest2.Clone());
+
+    RunReportResultCreatedScriptExpectingResult(
+        R"(5)",
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          privateAggregation.sendHistogramReport({bucket: 18446744073709551616n,
+                                                  value: 1});
+        )",
+        /*expected_signals_for_winner=*/"5",
+        /*expected_report_url=*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/{});
+  }
+
+  // An unrelated exception after sendHistogramReport shouldn't block the report
+  {
+    PrivateAggregationRequests expected_pa_requests;
+    expected_pa_requests.push_back(kExpectedRequest1.Clone());
+
+    RunReportResultCreatedScriptExpectingResult(
+        R"(5)",
+        R"(
+          privateAggregation.sendHistogramReport({bucket: 123, value: 45});
+          error;
+        )",
+        /*expected_signals_for_winner=*/absl::nullopt,
+        /*expected_report_url=*/absl::nullopt, /*expected_ad_beacon_map=*/{},
+        std::move(expected_pa_requests),
+        /*expected_errors=*/
+        {"https://url.test/:11 Uncaught ReferenceError: error is not "
+         "defined."});
+  }
+}
+
 TEST_F(SellerWorkletTest, ReportResultBid) {
   bid_ = 5;
   RunReportResultCreatedScriptExpectingResult(
@@ -2247,6 +2490,7 @@
                           bool has_scoring_signals_data_version,
                           const absl::optional<GURL>& debug_loss_report_url,
                           const absl::optional<GURL>& debug_win_report_url,
+                          PrivateAggregationRequests pa_requests,
                           const std::vector<std::string>& errors) {
                 EXPECT_EQ(2, score);
                 EXPECT_FALSE(has_scoring_signals_data_version);
@@ -2273,6 +2517,7 @@
                   const absl::optional<std::string>& signals_for_winner,
                   const absl::optional<GURL>& report_url,
                   const base::flat_map<std::string, GURL>& ad_beacon_map,
+                  PrivateAggregationRequests pa_requests,
                   const std::vector<std::string>& errors) {
                 EXPECT_EQ("2", signals_for_winner);
                 EXPECT_TRUE(errors.empty());
@@ -2304,6 +2549,7 @@
                         bool has_scoring_signals_data_version,
                         const absl::optional<GURL>& debug_loss_report_url,
                         const absl::optional<GURL>& debug_win_report_url,
+                        PrivateAggregationRequests pa_requests,
                         const std::vector<std::string>& errors) {
         ADD_FAILURE() << "Callback should not be invoked since worklet deleted";
       }));
@@ -2334,6 +2580,7 @@
       base::BindOnce([](const absl::optional<std::string>& signals_for_winner,
                         const absl::optional<GURL>& report_url,
                         const base::flat_map<std::string, GURL>& ad_beacon_map,
+                        PrivateAggregationRequests pa_requests,
                         const std::vector<std::string>& errors) {
         ADD_FAILURE() << "Callback should not be invoked since worklet deleted";
       }));
@@ -2356,12 +2603,13 @@
   // Queue a ScoreAd() call, which should not happen immediately since loading
   // is paused.
   base::RunLoop run_loop;
-  RunScoreAdOnWorkletAsync(
-      worklet.get(), /*expected_score=*/10, /*expected_errors=*/{},
-      mojom::ComponentAuctionModifiedBidParamsPtr(),
-      /*expected_data_version=*/absl::nullopt,
-      /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop.QuitClosure());
+  RunScoreAdOnWorkletAsync(worklet.get(), /*expected_score=*/10,
+                           /*expected_errors=*/{},
+                           mojom::ComponentAuctionModifiedBidParamsPtr(),
+                           /*expected_data_version=*/absl::nullopt,
+                           /*expected_debug_loss_report_url=*/absl::nullopt,
+                           /*expected_debug_win_report_url=*/absl::nullopt,
+                           /*expected_pa_requests=*/{}, run_loop.QuitClosure());
 
   // Give it a chance to fetch.
   task_environment_.RunUntilIdle();
@@ -2442,7 +2690,8 @@
       /*expected_errors=*/{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
       /*expected_data_version=*/absl::nullopt,
       /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop1.QuitClosure());
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      /*expected_pa_requests=*/{}, run_loop1.QuitClosure());
 
   decision_logic_url_ = kUrl2;
   SellerWorklet* worklet_impl2 = nullptr;
@@ -2454,7 +2703,8 @@
       /*expected_errors=*/{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
       /*expected_data_version=*/absl::nullopt,
       /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop2.QuitClosure());
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      /*expected_pa_requests=*/{}, run_loop2.QuitClosure());
 
   int id1 = worklet_impl1->context_group_id_for_testing();
   int id2 = worklet_impl2->context_group_id_for_testing();
@@ -2572,7 +2822,8 @@
       worklet1.get(), 100.5, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
       /*expected_data_version=*/absl::nullopt,
       /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop1.QuitClosure());
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      /*expected_pa_requests=*/{}, run_loop1.QuitClosure());
 
   decision_logic_url_ = GURL(kUrl2);
   auto worklet2 = CreateWorklet(/*pause_for_debugger_on_start=*/true);
@@ -2584,6 +2835,7 @@
                            /*expected_data_version=*/absl::nullopt,
                            /*expected_debug_loss_report_url=*/absl::nullopt,
                            /*expected_debug_win_report_url=*/absl::nullopt,
+                           /*expected_pa_requests=*/{},
                            run_loop2.QuitClosure());
 
   mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent1, agent2;
@@ -2722,11 +2974,12 @@
   decision_logic_url_ = GURL(kUrl);
   auto worklet = CreateWorklet(/*pause_for_debugger_on_start=*/true);
   base::RunLoop run_loop;
-  RunScoreAdOnWorkletAsync(
-      worklet.get(), 1.0, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
-      /*expected_data_version=*/absl::nullopt,
-      /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop.QuitClosure());
+  RunScoreAdOnWorkletAsync(worklet.get(), 1.0, {},
+                           mojom::ComponentAuctionModifiedBidParamsPtr(),
+                           /*expected_data_version=*/absl::nullopt,
+                           /*expected_debug_loss_report_url=*/absl::nullopt,
+                           /*expected_debug_win_report_url=*/absl::nullopt,
+                           /*expected_pa_requests=*/{}, run_loop.QuitClosure());
 
   mojo::AssociatedRemote<blink::mojom::DevToolsAgent> agent;
   worklet->ConnectDevToolsAgent(agent.BindNewEndpointAndPassReceiver());
@@ -2779,8 +3032,8 @@
   base::RunLoop run_loop2;
   RunReportResultExpectingResultAsync(
       worklet.get(), "1", GURL("https://foo.test/"),
-      /*expected_ad_beacon_map=*/{}, /*expected_errors=*/{},
-      run_loop2.QuitClosure());
+      /*expected_ad_beacon_map=*/{}, /*expected_pa_requests=*/{},
+      /*expected_errors=*/{}, run_loop2.QuitClosure());
   TestDevToolsAgentClient::Event breakpoint_hit2 =
       debug.WaitForMethodNotification("Debugger.paused");
   const std::string* breakpoint2 =
@@ -2802,7 +3055,8 @@
       worklet.get(), 1.0, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
       /*expected_data_version=*/absl::nullopt,
       /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, run_loop3.QuitClosure());
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      /*expected_pa_requests=*/{}, run_loop3.QuitClosure());
 
   TestDevToolsAgentClient::Event breakpoint_hit3 =
       debug.WaitForMethodNotification("Debugger.paused");
@@ -2863,7 +3117,8 @@
       worklet.get(), 1.0, {}, mojom::ComponentAuctionModifiedBidParamsPtr(),
       /*expected_data_version=*/absl::nullopt,
       /*expected_debug_loss_report_url=*/absl::nullopt,
-      /*expected_debug_win_report_url=*/absl::nullopt, base::BindOnce([]() {
+      /*expected_debug_win_report_url=*/absl::nullopt,
+      /*expected_pa_requests=*/{}, base::BindOnce([]() {
         ADD_FAILURE() << "scoreAd shouldn't actually get to finish.";
       }));
 
@@ -3184,6 +3439,7 @@
                         bool has_scoring_signals_data_version,
                         const absl::optional<GURL>& debug_loss_report_url,
                         const absl::optional<GURL>& debug_win_report_url,
+                        PrivateAggregationRequests pa_requests,
                         const std::vector<std::string>& errors) {
               if (score == 1) {
                 EXPECT_TRUE(debug_loss_report_url.has_value());
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index bde274f..560e0fb 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -150,6 +150,8 @@
     "browser/shell_speech_recognition_manager_delegate.h",
     "browser/shell_web_contents_view_delegate.h",
     "browser/shell_web_contents_view_delegate_creator.h",
+    "common/main_frame_counter_test_impl.cc",
+    "common/main_frame_counter_test_impl.h",
     "common/power_monitor_test_impl.cc",
     "common/power_monitor_test_impl.h",
     "common/shell_content_client.cc",
@@ -251,6 +253,7 @@
     "//components/web_cache/renderer",
     "//content:content_resources",
     "//content:dev_ui_content_resources",
+    "//content/common:main_frame_counter",
     "//content/public/common",
     "//content/test:content_test_mojo_bindings",
     "//content/test:test_support",
@@ -850,7 +853,10 @@
 }
 
 mojom("content_browsertests_mojom") {
-  sources = [ "common/power_monitor_test.mojom" ]
+  sources = [
+    "common/main_frame_counter_test.mojom",
+    "common/power_monitor_test.mojom",
+  ]
   public_deps = [ "//sandbox/policy/mojom" ]
 }
 
diff --git a/content/shell/common/main_frame_counter_test.mojom b/content/shell/common/main_frame_counter_test.mojom
new file mode 100644
index 0000000..683ff22
--- /dev/null
+++ b/content/shell/common/main_frame_counter_test.mojom
@@ -0,0 +1,13 @@
+// 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;
+
+// This interface is only for the sake of browser test to query the number of
+// main frames in a remote child process.
+interface MainFrameCounterTest {
+    // Returns whether or not the renderer this is called on has a local main
+    // frame.
+    HasMainFrame() => (bool has_main_frame);
+};
diff --git a/content/shell/common/main_frame_counter_test_impl.cc b/content/shell/common/main_frame_counter_test_impl.cc
new file mode 100644
index 0000000..fb557f1
--- /dev/null
+++ b/content/shell/common/main_frame_counter_test_impl.cc
@@ -0,0 +1,30 @@
+// 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 "content/shell/common/main_frame_counter_test_impl.h"
+#include "base/no_destructor.h"
+#include "content/common/main_frame_counter.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace content {
+
+MainFrameCounterTestImpl* GetMainFrameCounterTestImpl() {
+  static base::NoDestructor<MainFrameCounterTestImpl> instance;
+  return instance.get();
+}
+
+MainFrameCounterTestImpl::MainFrameCounterTestImpl() = default;
+MainFrameCounterTestImpl::~MainFrameCounterTestImpl() = default;
+
+// static
+void MainFrameCounterTestImpl::Bind(
+    mojo::PendingReceiver<mojom::MainFrameCounterTest> receiver) {
+  GetMainFrameCounterTestImpl()->receiver_.Bind(std::move(receiver));
+}
+
+void MainFrameCounterTestImpl::HasMainFrame(HasMainFrameCallback callback) {
+  std::move(callback).Run(MainFrameCounter::has_main_frame());
+}
+
+};  // namespace content
diff --git a/content/shell/common/main_frame_counter_test_impl.h b/content/shell/common/main_frame_counter_test_impl.h
new file mode 100644
index 0000000..0ab65b7
--- /dev/null
+++ b/content/shell/common/main_frame_counter_test_impl.h
@@ -0,0 +1,27 @@
+// 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 CONTENT_SHELL_COMMON_MAIN_FRAME_COUNTER_TEST_IMPL_H_
+#define CONTENT_SHELL_COMMON_MAIN_FRAME_COUNTER_TEST_IMPL_H_
+
+#include "content/shell/common/main_frame_counter_test.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace content {
+
+class MainFrameCounterTestImpl final : public mojom::MainFrameCounterTest {
+ public:
+  MainFrameCounterTestImpl();
+  ~MainFrameCounterTestImpl() override;
+  static void Bind(mojo::PendingReceiver<mojom::MainFrameCounterTest> receiver);
+
+  void HasMainFrame(HasMainFrameCallback) override;
+
+ private:
+  mojo::Receiver<mojom::MainFrameCounterTest> receiver_{this};
+};
+
+};  // namespace content
+
+#endif  // CONTENT_SHELL_COMMON_MAIN_FRAME_COUNTER_TEST_IMPL_H_
diff --git a/content/shell/renderer/shell_content_renderer_client.cc b/content/shell/renderer/shell_content_renderer_client.cc
index 1fe5a1c4..d7fe54a 100644
--- a/content/shell/renderer/shell_content_renderer_client.cc
+++ b/content/shell/renderer/shell_content_renderer_client.cc
@@ -14,6 +14,7 @@
 #include "components/cdm/renderer/external_clear_key_key_system_properties.h"
 #include "components/web_cache/renderer/web_cache_impl.h"
 #include "content/public/test/test_service.mojom.h"
+#include "content/shell/common/main_frame_counter_test_impl.h"
 #include "content/shell/common/power_monitor_test_impl.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/renderer/shell_render_frame_observer.h"
@@ -139,6 +140,9 @@
   binders->Add<mojom::PowerMonitorTest>(
       base::BindRepeating(&PowerMonitorTestImpl::MakeSelfOwnedReceiver),
       base::ThreadTaskRunnerHandle::Get());
+  binders->Add<mojom::MainFrameCounterTest>(
+      base::BindRepeating(&MainFrameCounterTestImpl::Bind),
+      base::ThreadTaskRunnerHandle::Get());
   binders->Add<web_cache::mojom::WebCache>(
       base::BindRepeating(&web_cache::WebCacheImpl::BindReceiver,
                           base::Unretained(web_cache_impl_.get())),
diff --git a/content/test/data/indexeddb/bug_1203335.js b/content/test/data/indexeddb/bug_1203335.js
index f8e1330..3ceed70 100644
--- a/content/test/data/indexeddb/bug_1203335.js
+++ b/content/test/data/indexeddb/bug_1203335.js
@@ -8,17 +8,18 @@
 // transactions.
 
 function test() {
-  if (window.webkitStorageInfo) {
+  if (navigator.storage) {
     window.jsTestIsAsync = true;
-    navigator.webkitTemporaryStorage.queryUsageAndQuota(
-        initUsageCallback, unexpectedErrorCallback);
+    navigator.storage.estimate()
+        .then(initUsageCallback)
+        .catch(unexpectedErrorCallback);
   } else
-    debug('This test requires window.webkitStorageInfo.');
+    debug('This test requires navigator.storage.');
 }
 
-function initUsageCallback(usage, quota) {
-  origReturnedUsage = returnedUsage = usage;
-  origReturnedQuota = returnedQuota = quota;
+function initUsageCallback(result) {
+  origReturnedUsage = returnedUsage = result.usage;
+  origReturnedQuota = returnedQuota = result.quota;
   debug('original quota is ' + displaySize(origReturnedQuota));
   debug('original usage is ' + displaySize(origReturnedUsage));
 
diff --git a/content/test/data/indexeddb/quota_test.js b/content/test/data/indexeddb/quota_test.js
index afb2983..25ff82b 100644
--- a/content/test/data/indexeddb/quota_test.js
+++ b/content/test/data/indexeddb/quota_test.js
@@ -3,18 +3,18 @@
 // found in the LICENSE file.
 
 function test() {
-  if (window.webkitStorageInfo) {
+  if (navigator.storage) {
     window.jsTestIsAsync = true;
-    navigator.webkitTemporaryStorage.queryUsageAndQuota(
-      initUsageCallback,
-      unexpectedErrorCallback);
+    navigator.storage.estimate()
+        .then(initUsageCallback)
+        .catch(unexpectedErrorCallback);
   } else
-    debug("This test requires window.webkitStorageInfo.");
+    debug('This test requires navigator.storage.');
 }
 
-function initUsageCallback(usage, quota) {
-  origReturnedUsage = returnedUsage = usage;
-  origReturnedQuota = returnedQuota = quota;
+function initUsageCallback(result) {
+  origReturnedUsage = returnedUsage = result.usage;
+  origReturnedQuota = returnedQuota = result.quota;
   debug("original quota is " + displaySize(origReturnedQuota));
   debug("original usage is " + displaySize(origReturnedUsage));
 
diff --git a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
index 92fd673..6bf9842 100644
--- a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
@@ -11,7 +11,7 @@
 import sys
 import tempfile
 import threading
-from typing import Any, List
+from typing import Any, Dict, List
 import unittest
 
 import websockets  # pylint:disable=import-error
@@ -42,12 +42,24 @@
 HTML_FILENAME = os.path.join('webgpu-cts', 'test_page.html')
 
 JAVASCRIPT_DURATION = 'javascript_duration'
+MESSAGE_TYPE_TEST_STARTED = 'TEST_STARTED'
+MESSAGE_TYPE_TEST_HEARTBEAT = 'TEST_HEARTBEAT'
+MESSAGE_TYPE_TEST_STATUS = 'TEST_STATUS'
+MESSAGE_TYPE_TEST_LOG = 'TEST_LOG'
 MESSAGE_TYPE_TEST_FINISHED = 'TEST_FINISHED'
 
 # These are tests that, for whatever reason, don't like being run in parallel.
 SERIAL_TESTS = {}
 
 
+class WebGpuTestResult():
+  """Struct-like object for holding a single test result."""
+
+  def __init__(self):
+    self.status = None
+    self.log_pieces = []
+
+
 async def StartWebsocketServer() -> None:
   async def HandleWebsocketConnection(
       websocket: ws_server.WebSocketServerProtocol) -> None:
@@ -268,12 +280,14 @@
 
   def RunActualGpuTest(self, test_path: str, args: ct.TestArgs) -> None:
     self._query, self._run_in_worker = args
-    timeout = self._GetTestTimeout()
     # Only a single instance is used to run tests despite a number of instances
     # (~2x the number of total tests) being initialized, so make sure to clear
     # this state so we don't accidentally keep it around from a previous test.
     if JAVASCRIPT_DURATION in self.additionalTags:
       del self.additionalTags[JAVASCRIPT_DURATION]
+
+    timeout = self._GetTestTimeout()
+
     try:
       self._NavigateIfNecessary(test_path)
       asyncio.run_coroutine_threadsafe(
@@ -282,38 +296,10 @@
                   'q': self._query,
                   'w': self._run_in_worker
               })), WebGpuCtsIntegrationTest.event_loop)
-      # Loop until we receive a message saying that the test is finished. This
-      # currently has no practical effect, but it is an intermediate step to
-      # supporting a heartbeat mechanism. See crbug.com/1340602.
-      while True:
-        future = asyncio.run_coroutine_threadsafe(
-            asyncio.wait_for(WebGpuCtsIntegrationTest.websocket.recv(),
-                             timeout), WebGpuCtsIntegrationTest.event_loop)
-        response = future.result()
-        response = json.loads(response)
-        if response['type'] == MESSAGE_TYPE_TEST_FINISHED:
-          break
+      result = self.HandleMessageLoop(timeout)
 
-      status = response['s']
-      logs_pieces = [response['l']]
-      is_final_payload = response['final']
-      js_duration = response['js_duration_ms'] / 1000
-      # Specify the precision to avoid scientific notation. Nanoseconds should
-      # be more precision than we need anyways.
-      self.additionalTags[JAVASCRIPT_DURATION] = '%.9fs' % js_duration
-      # Get multiple log pieces if necessary, e.g. if a monolithic log would
-      # have gone over the max payload size.
-      while not is_final_payload:
-        future = asyncio.run_coroutine_threadsafe(
-            asyncio.wait_for(WebGpuCtsIntegrationTest.websocket.recv(),
-                             MULTI_PAYLOAD_TIMEOUT),
-            WebGpuCtsIntegrationTest.event_loop)
-        response = future.result()
-        response = json.loads(response)
-        logs_pieces.append(response['l'])
-        is_final_payload = response['final']
-
-      log_str = ''.join(logs_pieces)
+      log_str = ''.join(result.log_pieces)
+      status = result.status
       if status == 'skip':
         self.skipTest('WebGPU CTS JavaScript reported test skip with logs ' +
                       log_str)
@@ -329,6 +315,96 @@
     finally:
       WebGpuCtsIntegrationTest.total_tests_run += 1
 
+  def HandleMessageLoop(self, timeout: float) -> WebGpuTestResult:
+    """Helper function to handle the loop for the message protocol.
+
+    See //docs/gpu/webgpu_cts_harness_message_protocol.md for more information
+    on the message format.
+
+    TODO(crbug.com/1340602): Update this to be the total test timeout once the
+    heartbeat mechanism is implemented.
+
+    Args:
+      timeout: A float denoting the number of seconds to the test is allowed
+          to wait between test messages before timing out.
+
+    Returns:
+      A filled WebGpuTestResult instance.
+    """
+    result = WebGpuTestResult()
+    message_state = {
+        MESSAGE_TYPE_TEST_STARTED: False,
+        MESSAGE_TYPE_TEST_STATUS: False,
+        MESSAGE_TYPE_TEST_LOG: False,
+    }
+    # Loop until we receive a message saying that the test is finished. This
+    # currently has no practical effect, but it is an intermediate step to
+    # supporting a heartbeat mechanism. See crbug.com/1340602.
+    while True:
+      future = asyncio.run_coroutine_threadsafe(
+          asyncio.wait_for(WebGpuCtsIntegrationTest.websocket.recv(), timeout),
+          WebGpuCtsIntegrationTest.event_loop)
+      response = future.result()
+      response = json.loads(response)
+      response_type = response['type']
+
+      if response == MESSAGE_TYPE_TEST_STARTED:
+        # If we ever want the adapter information from WebGPU, we would
+        # retrieve it from the message here. However, to avoid pylint
+        # complaining about unused variables, don't grab it until we actually
+        # need it.
+        VerifyMessageOrderTestStarted(message_state)
+
+      elif response_type == MESSAGE_TYPE_TEST_HEARTBEAT:
+        VerifyMessageOrderTestHeartbeat(message_state)
+        continue
+
+      elif response_type == MESSAGE_TYPE_TEST_STATUS:
+        VerifyMessageOrderTestStatus(message_state)
+        result.status = response['status']
+        js_duration = response['js_duration_ms'] / 1000
+        # Specify the precision to avoid scientific notation. Nanoseconds
+        # should be more precision than we need anyways.
+        self.additionalTags[JAVASCRIPT_DURATION] = '%.9fs' % js_duration
+
+      elif response_type == MESSAGE_TYPE_TEST_LOG:
+        VerifyMessageOrderTestLog(message_state)
+        result.log_pieces.append(response['log'])
+
+      elif response_type == MESSAGE_TYPE_TEST_FINISHED:
+        VerifyMessageOrderTestFinished(message_state)
+        # TODO(crbug.com/1340602): Remove log, etc. once the Dawn code has
+        # been updated to send multiple message types.
+        if 's' in response:
+          result.status = response['s']
+        if 'l' in response:
+          result.log_pieces.append(response['l'])
+        if 'js_duration_ms' in response:
+          js_duration = response['js_duration_ms'] / 1000
+          # Specify the precision to avoid scientific notation. Nanoseconds
+          # should be more precision than we need anyways.
+          self.additionalTags[JAVASCRIPT_DURATION] = '%.9fs' % js_duration
+
+        if 'final' in response:
+          is_final_payload = response['final']
+          # Get multiple log pieces if necessary, e.g. if a monolithic log
+          # would have gone over the max payload size.
+          while not is_final_payload:
+            future = asyncio.run_coroutine_threadsafe(
+                asyncio.wait_for(WebGpuCtsIntegrationTest.websocket.recv(),
+                                 MULTI_PAYLOAD_TIMEOUT),
+                WebGpuCtsIntegrationTest.event_loop)
+            response = future.result()
+            response = json.loads(response)
+            result.log_pieces.append(response['l'])
+            is_final_payload = response['final']
+        break
+
+      else:
+        raise WebGpuMessageProtocolError('Received unknown message type %s' %
+                                         response_type)
+    return result
+
   @classmethod
   def CleanUpExistingWebsocket(cls) -> None:
     if cls.connection_stopper:
@@ -404,6 +480,98 @@
     return [EXPECTATIONS_FILE]
 
 
+class WebGpuMessageProtocolError(RuntimeError):
+  pass
+
+
+def VerifyMessageOrderTestStarted(message_state: Dict[str, bool]) -> None:
+  """Helper function to verify that messages are ordered correctly.
+
+  Handles MESSAGE_TYPE_TEST_STARTED messages.
+
+  Split out to reduce the number of branches within a single function.
+
+  Args:
+    message_state: A map from message type to a boolean denoting whether a
+        message of that type has been received before.
+  """
+  if message_state[MESSAGE_TYPE_TEST_STARTED]:
+    raise WebGpuMessageProtocolError(
+        'Received multiple start messages for one test')
+  message_state[MESSAGE_TYPE_TEST_STARTED] = True
+
+
+def VerifyMessageOrderTestHeartbeat(message_state: Dict[str, bool]) -> None:
+  """Helper function to verify that messages are ordered correctly.
+
+  Handles MESSAGE_TYPE_TEST_HEARTBEAT messages.
+
+  Split out to reduce the number of branches within a single function.
+
+  Args:
+    message_state: A map from message type to a boolean denoting whether a
+        message of that type has been received before.
+  """
+  if not message_state[MESSAGE_TYPE_TEST_STARTED]:
+    raise WebGpuMessageProtocolError('Received heartbeat before test start')
+  if message_state[MESSAGE_TYPE_TEST_STATUS]:
+    raise WebGpuMessageProtocolError(
+        'Received heartbeat after test supposedly done')
+
+
+def VerifyMessageOrderTestStatus(message_state: Dict[str, bool]) -> None:
+  """Helper function to verify that messages are ordered correctly.
+
+  Handles MESSAGE_TYPE_TEST_STATUS messages.
+
+  Split out to reduce the number of branches within a single function.
+
+  Args:
+    message_state: A map from message type to a boolean denoting whether a
+        message of that type has been received before.
+  """
+  if not message_state[MESSAGE_TYPE_TEST_STARTED]:
+    raise WebGpuMessageProtocolError(
+        'Received test status message before test start')
+  if message_state[MESSAGE_TYPE_TEST_STATUS]:
+    raise WebGpuMessageProtocolError(
+        'Received multiple status messages for one test')
+  message_state[MESSAGE_TYPE_TEST_STATUS] = True
+
+
+def VerifyMessageOrderTestLog(message_state: Dict[str, bool]) -> None:
+  """Helper function to verify that messages are ordered correctly.
+
+  Handles MESSAGE_TYPE_TEST_LOG messages.
+
+  Split out to reduce the number of branches within a single function.
+
+  Args:
+    message_state: A map from message type to a boolean denoting whether a
+        message of that type has been received before.
+  """
+  if not message_state[MESSAGE_TYPE_TEST_STATUS]:
+    raise WebGpuMessageProtocolError(
+        'Received log message before status message')
+  message_state[MESSAGE_TYPE_TEST_LOG] = True
+
+
+def VerifyMessageOrderTestFinished(message_state: Dict[str, bool]) -> None:
+  """Helper function to verify that messages are ordered correctly.
+
+  Handles MESSAGE_TYPE_TEST_FINISHED messages.
+
+  Split out to reduce the number of branches within a single function.
+
+  Args:
+    message_state: A map from message type to a boolean denoting whether a
+        message of that type has been received before.
+  """
+  # TODO(crbug.com/1340602): Add message state verification once the Dawn code
+  # has been updated to send multiple message types.
+  del message_state  # currently unused
+
+
 def TestNameFromInputs(query: str, worker: bool) -> str:
   return 'worker_%s' % query if worker else query
 
diff --git a/device/vr/android/arcore/arcore_gl.cc b/device/vr/android/arcore/arcore_gl.cc
index 16336b67..2dd51af 100644
--- a/device/vr/android/arcore/arcore_gl.cc
+++ b/device/vr/android/arcore/arcore_gl.cc
@@ -45,6 +45,7 @@
 #include "ui/gl/gl_fence_egl.h"
 #include "ui/gl/gl_image_ahardwarebuffer.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace {
@@ -444,10 +445,15 @@
   // TODO(crbug.com/1170580): support ANGLE with cardboard?
   gl::init::DisableANGLE();
 
-  if (gl::GetGLImplementation() == gl::kGLImplementationNone &&
-      !gl::init::InitializeGLOneOff(/*system_device_id=*/0)) {
-    DLOG(ERROR) << "gl::init::InitializeGLOneOff failed";
-    return false;
+  gl::GLDisplay* display = nullptr;
+  if (gl::GetGLImplementation() == gl::kGLImplementationNone) {
+    display = gl::init::InitializeGLOneOff(/*system_device_id=*/0);
+    if (!display) {
+      DLOG(ERROR) << "gl::init::InitializeGLOneOff failed";
+      return false;
+    }
+  } else {
+    display = gl::GetDefaultDisplayEGL();
   }
 
   DCHECK(gl::GetGLImplementation() != gl::kGLImplementationEGLANGLE);
@@ -456,10 +462,10 @@
   // surface for Offscreen usage.
   scoped_refptr<gl::GLSurface> surface;
   if (drawing_widget != gfx::kNullAcceleratedWidget) {
-    surface = gl::init::CreateViewGLSurface(drawing_widget);
+    surface = gl::init::CreateViewGLSurface(display, drawing_widget);
   } else {
     surface = gl::init::CreateOffscreenGLSurfaceWithFormat(
-        {0, 0}, gl::GLSurfaceFormat());
+        display, {0, 0}, gl::GLSurfaceFormat());
   }
   DVLOG(3) << "surface=" << surface.get();
   if (!surface.get()) {
diff --git a/docs/gpu/webgpu_cts_harness_message_protocol.md b/docs/gpu/webgpu_cts_harness_message_protocol.md
new file mode 100644
index 0000000..836f25f
--- /dev/null
+++ b/docs/gpu/webgpu_cts_harness_message_protocol.md
@@ -0,0 +1,136 @@
+# WebGPU CTS Harness Message Protocol
+
+The WebGPU conformance test suite harness makes use of a websocket connection to
+pass information between the Python and JavaScript code. This document outlines
+all valid message types, when they should be sent, etc. All messages are JSON
+objects with at least a `type` field that differentiates message types from each
+other.
+
+## TEST_STARTED
+
+### Description
+
+A message sent exactly once by the JavaScript code once it starts running the
+requested test. In addition to serving as an ack, it also sends information
+about how the test will be run.
+
+Sending more than one message of this type during a test is considered an error.
+
+### Fields
+
+* `type` - A string denoting the message type
+* `adapter_info` - An optional object containing the same information as the
+  [GpuAdapterInfo] the test will be using
+
+[GpuAdapterInfo]: https://gpuweb.github.io/gpuweb/#gpu-adapterinfo
+
+### Example
+
+```
+{
+  'type': 'TEST_STARTED',
+  'adapter_info': {
+    'vendor': 'NVIDIA',
+    'architecture': 'Turing',
+    'device': '2184',
+    'description': 'GTX 1660',
+  },
+}
+```
+
+## TEST_HEARTBEAT
+
+### Description
+
+A message sent periodically zero or more times by the JavaScript code to
+prevent the Python test harness from timing out.
+
+Sending a message of this type before TEST_STARTED or after TEST_STATUS is
+considered an error.
+
+### Fields
+
+* `type` - A string denoting the message type
+
+### Example
+
+```
+{
+  'type': 'TEST_HEARTBEAT',
+}
+```
+
+## TEST_STATUS
+
+### Description
+
+A message sent exactly once when the actual test is completed. Contains
+information about the status/result of the test.
+
+Sending more than one message of this type or sending before TEST_STARTED is
+considered an error.
+
+### Fields
+
+* `type` - As string denoting the message type
+* `status` - A string containing the status of the test, e.g. `skip` or `fail`
+* `js_duration_ms` - An int containing the number of milliseconds the
+  JavaScript code took to run the test
+
+### Example
+
+```
+{
+  'type': 'TEST_STATUS',
+  'status': 'fail',
+  'js_duration_ms': 243,
+}
+```
+
+## TEST_LOG
+
+### Description
+
+A message sent one or more times containing test logs. Multiple messages will be
+sent if a single message would exceed the max payload size, in which case the
+Python test harness will concatenate them together in the order received.
+
+Sending a message of this type before TEST_STATUS is considered an error.
+
+### Fields
+
+* `type` - A string denoting the message type
+* `log` - A string containing log content output by the test
+
+### Example
+
+```
+{
+  'type': 'TEST_LOG',
+  'log': 'Logging is fun',
+}
+```
+
+## TEST_FINISHED
+
+### Description
+
+A message sent exactly once after all other messages have been sent. This
+signals to the Python test harness that it should stop listening for any
+additional messages and proceed with test cleanup.
+
+Sending a message of this type before TEST_LOG is considered an error. Sending
+more than one message of this type is erroneous, but will not be caught until
+the following test is run.
+
+### Fields
+
+* `type` - A string denoting the message type
+
+### Example
+
+```
+{
+  'type': 'TEST_FINISHED',
+}
+```
diff --git a/docs/speed/binary_size/fuchsia_binary_size_trybot.md b/docs/speed/binary_size/fuchsia_binary_size_trybot.md
index 8703a36..184b509 100644
--- a/docs/speed/binary_size/fuchsia_binary_size_trybot.md
+++ b/docs/speed/binary_size/fuchsia_binary_size_trybot.md
@@ -35,7 +35,8 @@
 
 ### Binary Size Increase
 
-- **What:** Checks that [compressed fuchsia archive] size increases by no more than 12kb.
+- **What:** Checks that [compressed fuchsia archive] size increases by no more
+  than 12kb.
   - These packages are `cast_runner` and `web_engine`.
 - **Why:** Chrome-Fuchsia deploys on platforms with small footprints. As each
   release rolls, Fuchsia runs its own set of size checks that will reject a
@@ -47,16 +48,23 @@
 
 #### What to do if the Check Fails?
 
-- Look at the provided `commit size analysis files` stdout to understand where the size is coming from.
+- Look at the provided `commit size analysis files` stdout to understand where
+  the size is coming from.
   - The `Read diff results` stdout will also give a breakdown of growth by
     package, if any.
+  - If the compressed size grew (this is what we measure), but the
+    uncompressed size decreased, then **ignore** this failure. Add a
+    [footer](#skipping-the-check) to
+    the CL (see below) to document this (and ignore this failure).
 - See if any of the generic [optimization advice] is applicable.
 - If you are writing a new feature or including a new library you might want to
-  think about skipping the `web_engine`/`cast_runner` binaries and to restrict this new
-  feature/library to desktop platforms that might care less about binary size.
+  think about skipping the `web_engine`/`cast_runner` binaries and to restrict
+  this new feature/library to desktop platforms that might care less about
+  binary size.
   - This can be done by removing it with the `is_fuchsia` BUILD tag and
     `OS_FUCHSIA` macro.
-  - If this change belongs on a full-browser, but not `web_engine`/`cast_runner`,
+  - If this change belongs on a full-browser, but not
+   `web_engine`/`cast_runner`,
     you should also guard against the  `ARCH_CPU_ARM64` tag, as this
     CPU-architecture is the only (current) set that requires size-checks.
 - See [the section below](#obvious-regressions)
@@ -66,14 +74,18 @@
     - If you think that there might not be a consensus that the code your adding
       is worth the added file size, then add why you think it is.
 
-- Add a footer to the commit description along the lines of:
+### Skipping the check
+Add a **footer** to the commit description along the lines of:
+
     - `Fuchsia-Binary-Size: Size increase is unavoidable (see above).`
+    - `Fuchsia-Binary-Size: Uncompressed size actually decreased.`
     - `Fuchsia-Binary-Size: Increase is temporary.`
-    - `Fuchsia-Binary-Size: See commit description.` <-- use this if longer than one line.
+    - `Fuchsia-Binary-Size: See commit description.` <-- use this if longer
+    than one line.
 
 ***note
-**Note:** Make sure there are no blank lines between `Fuchsia-Binary-Size:` and other
-footers.
+**Note:** Make sure there are no blank lines between `Fuchsia-Binary-Size:` and
+other footers.
 ***
 
 [optimization advice]: /docs/speed/binary_size/optimization_advice.md
@@ -83,8 +95,8 @@
 The size metric we care about the most is the compressed size. This is an
 **estimate** of how large the Chrome-Fuchsia packages will be when delivered on
 device (actual compression can vary between devices, so the computed numbers may
-not be accurate). However,  you may see the uncompressed and compressed size grow by
-different amounts (and sometimes the compressed size is larger than the
+not be accurate). However,  you may see the uncompressed and compressed size
+grow by different amounts (and sometimes the compressed size is larger than the
 uncompressed)!
 
 This is due to how sizes are calculated and how the compression is done.
@@ -94,16 +106,21 @@
 Compression is done via the `blobfs-compression` tool, exported from the
 Fuchsia SDK. This compresses the file into a package that is ready to be
 deployed to the Fuchsia device. With the current (default) compression-mode,
-this compresses the package in page-sizes designed for the device and filesystem.
-Since each page is at least 8K, **increments in the compressed size are always
-multiples of 8K with the current compression mode**. So, if your change causes
-the previous compression page to go over the limit, you may see an 8K increase
-for an otherwise small change.
+this compresses the package in page-sizes designed for the device and
+filesystem. Since each page is at least 8K, **increments in the compressed size
+are always multiples of 8K with the current compression mode**. So, if your
+change causes the previous compression page to go over the limit, you may see
+an 8K increase for an otherwise small change.
 
 Large changes will increase more than a page's work (to at least 16K), which is
 why we only monitor 12K+ changes (12K isn't possible due to the 8K page size)
 and not 8K+.
 
+**You are responsible only for pre-compression size increases. If your change
+did not cause a pre-compression size increase, but still failed the builder,
+please ignore it using the `Fuchsia-Binary-Size:`
+[footer](#skipping-the-check).**
+
 ## Running a local binary-size check
 
 If you want to check your changes impact to binary size locally (instead of
@@ -159,8 +176,8 @@
 ```
 
 The size breakdown by blob and package will be given, followed by a summary at
-the end, for `chrome_fuchsia`, `web_engine`, and `cast_runner`. The number that is
-deployed to the device is the `compressed` version.
+the end, for `chrome_fuchsia`, `web_engine`, and `cast_runner`. The number that
+is deployed to the device is the `compressed` version.
 
 ## How to reduce your binary-size for Fuchsia
 TODO(crbug.com/1296349): Fill this out.
@@ -169,7 +186,8 @@
 
 #### Many new blobs
 (shamelessly stolen from this
-[doc](https://docs.google.com/document/d/1K3fOJJ3rsKA5WtvRCJtuLQSqn7MtOuVUzJA9cFXQYVs/edit#), but looking for any tips on how to improve this for Fuchsia specifically)
+[doc](https://docs.google.com/document/d/1K3fOJJ3rsKA5WtvRCJtuLQSqn7MtOuVUzJA9cFXQYVs/edit#),
+but looking for any tips on how to improve this for Fuchsia specifically)
 
 Look at blobs and see that there aren't a huge number of blobs added in.
 
@@ -180,6 +198,56 @@
   - Try searching BUILD files to see if the .so was included from somewhere
     in particular.
 
+### Bloaty
+
+[Bloaty](https://github.com/google/bloaty) can be used
+to determine the composition of the binary (and can be helpful for determining
+the cause of the increase).
+
+0. (first time only) Install Bloaty using these
+[instructions](https://github.com/google/bloaty#install).
+1. Build two copies of the packages (see
+   [above](#running-a-local-binary_size-check)) -
+   with and without your changes in two separate output directories.
+2. Generate Bloaty results. You can run Bloaty against the stripped binaries
+   (`<out dir>/web_engine_exe` and `<out dir>/cast_runner_exe`). However, if
+   you want more information, you will have to run it against the unstripped
+   binaries (located in `<out dir>/exe.unstripped`. You only need to run Bloaty
+   against the binary your change affected.
+
+```bash
+$ bloaty -d compileunits,symbols $OUT_DIR/exe.unstripped/web_engine_exe \
+  -n $ROW_LIMIT -s vm
+```
+
+`-n $ROW_LIMIT` determines the number of rows to show per level before
+collapsing. Setting to `0` shows all rows. Default is 20.
+
+`-s vm` indicates to sort by Virtual Memory (VM) size increase. This is the
+metric that grows somewhat closely to the binary-size bot's size metric.
+
+**NOTE**: that the sizes reported from Bloaty will not be exactly the same as
+those reported by the [`binary_sizes` script](#run-the-size-script) since
+Bloaty analyzes the uncompressed (and potentially unstripped) binary, but
+the reported relative growth can point you in the right direction. The
+`File Size` can vary a lot due to debug symbol information. The `VM Size` is
+usually a good lead.
+
+**If Bloaty reports your change decreased the uncompressed size, use a
+[footer](#skipping-the-check) to
+ignore the check.**
+
+You can also directly generate a comparison with the following:
+
+```bash
+$ bloaty -d compileunits,symbols \
+  $OUT_DIR_WITH_CHANGE/exe.unstripped/web_engine_exe -n $ROW_LIMIT -s vm -- \
+  $OUT_DIR_WITHOUT_CHANGE/exe.unstripped/web_engine_exe
+```
+
+You can find out more about sections of ELF binaries
+[here](https://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/specialsections.html).
+
 ## If All Else Fails
 
 - For help, email [chrome-fuchsia-team@google.com]. They're expert
@@ -187,8 +255,9 @@
 - Not all checks are perfect and sometimes you want to overrule the trybot (for
   example if you did your best and are unable to reduce binary size any
   further).
-- Adding a “Fuchsia-Binary-Size: $ANY\_TEXT\_HERE” footer to your cl (next to “Bug:”)
-  will bypass the bot assertions.
+- Adding a “Fuchsia-Binary-Size: $ANY\_TEXT\_HERE”
+  [footer](#skipping-the-check) to your cl (next to “Bug:”)  will bypass
+  the bot assertions.
     - Most commits that trigger the warnings will also result in Telemetry
       alerts and be reviewed by a binary size sheriff. Failing to write an
       adequate justification may lead to the binary size sheriff filing a bug
diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc
index 032672a..ed5e8fb 100644
--- a/extensions/browser/api/file_system/file_system_api.cc
+++ b/extensions/browser/api/file_system/file_system_api.cc
@@ -68,7 +68,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "extensions/browser/api/file_handlers/non_native_file_system_delegate.h"
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 using storage::IsolatedContext;
 
@@ -90,10 +90,10 @@
 const char kRetainEntryIncognitoError[] =
     "Could not retain file entry in incognito mode";
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 const char kNotSupportedOnNonKioskSessionError[] =
     "Operation only supported for kiosk apps running in a kiosk session.";
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 namespace extensions {
 
@@ -1044,30 +1044,14 @@
   return RespondNow(NoArguments());
 }
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() =
+#if BUILDFLAG(IS_CHROMEOS)
+/******** FileSystemRequestFileSystemFunction ********/
+
+FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction() =
     default;
 
-ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
-  using file_system::RequestFileSystem::Params;
-  const std::unique_ptr<Params> params(Params::Create(args()));
-  EXTENSION_FUNCTION_VALIDATE(params);
-
-  NOTIMPLEMENTED();
-  return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
-}
-
-FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() = default;
-
-ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
-  NOTIMPLEMENTED();
-  return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
-}
-#else
-
-FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction() {}
-
-FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {}
+FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() =
+    default;
 
 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
   using file_system::RequestFileSystem::Params;
@@ -1106,9 +1090,11 @@
   Respond(Error(error));
 }
 
-FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction() {}
+/******** FileSystemGetVolumeListFunction ********/
 
-FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {}
+FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction() = default;
+
+FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() = default;
 
 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
   FileSystemDelegate* delegate =
@@ -1136,6 +1122,29 @@
 void FileSystemGetVolumeListFunction::OnError(const std::string& error) {
   Respond(Error(error));
 }
-#endif
+#else   // BUILDFLAG(IS_CHROMEOS)
+/******** FileSystemRequestFileSystemFunction ********/
+
+FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() =
+    default;
+
+ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
+  using file_system::RequestFileSystem::Params;
+  const std::unique_ptr<Params> params(Params::Create(args()));
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  NOTIMPLEMENTED();
+  return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
+}
+
+/******** FileSystemGetVolumeListFunction ********/
+
+FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() = default;
+
+ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
+  NOTIMPLEMENTED();
+  return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
+}
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace extensions
diff --git a/extensions/browser/api/file_system/file_system_api.h b/extensions/browser/api/file_system/file_system_api.h
index 5a47260..23a6bb3a 100644
--- a/extensions/browser/api/file_system/file_system_api.h
+++ b/extensions/browser/api/file_system/file_system_api.h
@@ -248,34 +248,7 @@
   ResponseAction Run() override;
 };
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-// Stub for non Chrome OS operating systems.
-class FileSystemRequestFileSystemFunction : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("fileSystem.requestFileSystem",
-                             FILESYSTEM_REQUESTFILESYSTEM)
-
- protected:
-  ~FileSystemRequestFileSystemFunction() override;
-
-  // ExtensionFunction overrides.
-  ExtensionFunction::ResponseAction Run() override;
-};
-
-// Stub for non Chrome OS operating systems.
-class FileSystemGetVolumeListFunction : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
-                             FILESYSTEM_GETVOLUMELIST)
-
- protected:
-  ~FileSystemGetVolumeListFunction() override;
-
-  // ExtensionFunction overrides.
-  ExtensionFunction::ResponseAction Run() override;
-};
-
-#else
+#if BUILDFLAG(IS_CHROMEOS)
 // Requests a file system for the specified volume id.
 class FileSystemRequestFileSystemFunction : public ExtensionFunction {
  public:
@@ -313,7 +286,33 @@
   void OnGotVolumeList(const std::vector<api::file_system::Volume>& volumes);
   void OnError(const std::string& error);
 };
-#endif
+#else   // BUILDFLAG(IS_CHROMEOS)
+// Stub for non Chrome OS operating systems.
+class FileSystemRequestFileSystemFunction : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileSystem.requestFileSystem",
+                             FILESYSTEM_REQUESTFILESYSTEM)
+
+ protected:
+  ~FileSystemRequestFileSystemFunction() override;
+
+  // ExtensionFunction overrides.
+  ExtensionFunction::ResponseAction Run() override;
+};
+
+// Stub for non Chrome OS operating systems.
+class FileSystemGetVolumeListFunction : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
+                             FILESYSTEM_GETVOLUMELIST)
+
+ protected:
+  ~FileSystemGetVolumeListFunction() override;
+
+  // ExtensionFunction overrides.
+  ExtensionFunction::ResponseAction Run() override;
+};
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace extensions
 
diff --git a/extensions/browser/api/file_system/file_system_delegate.h b/extensions/browser/api/file_system/file_system_delegate.h
index e56a18b..87d8b17 100644
--- a/extensions/browser/api/file_system/file_system_delegate.h
+++ b/extensions/browser/api/file_system/file_system_delegate.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -18,10 +19,6 @@
 
 class ExtensionFunction;
 
-namespace base {
-class FilePath;
-}  // namespace base
-
 namespace content {
 class BrowserContext;
 class WebContents;
@@ -77,7 +74,7 @@
   // string ID is found.
   virtual int GetDescriptionIdForAcceptType(const std::string& accept_type) = 0;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // Checks whether the extension can be granted access.
   virtual bool IsGrantable(content::BrowserContext* browser_context,
                            const Extension& extension) = 0;
@@ -96,7 +93,7 @@
   virtual void GetVolumeList(content::BrowserContext* browser_context,
                              VolumeListCallback success_callback,
                              ErrorCallback error_callback) = 0;
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   virtual SavedFilesServiceInterface* GetSavedFilesService(
       content::BrowserContext* browser_context) = 0;
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc
index c07424a..148b796 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -40,16 +40,16 @@
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
 using ::ash::NetworkCertificateHandler;
+using ::ash::NetworkHandler;
+using ::ash::NetworkStateHandler;
 using ::ash::NetworkTypePattern;
-using chromeos::NetworkHandler;
-using chromeos::NetworkStateHandler;
 using extensions::NetworkingPrivateDelegate;
 
 namespace private_api = extensions::api::networking_private;
 
 namespace {
 
-chromeos::NetworkStateHandler* GetStateHandler() {
+NetworkStateHandler* GetStateHandler() {
   return NetworkHandler::Get()->network_state_handler();
 }
 
@@ -680,7 +680,7 @@
 
 void NetworkingPrivateChromeOS::GetEnabledNetworkTypes(
     EnabledNetworkTypesCallback callback) {
-  chromeos::NetworkStateHandler* state_handler = GetStateHandler();
+  NetworkStateHandler* state_handler = GetStateHandler();
 
   base::Value network_list(base::Value::Type::LIST);
 
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
index b6114f26..9de65c6 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -135,8 +135,7 @@
 
   void SetUpNetworkPolicy() {
     ash::ManagedNetworkConfigurationHandler* config_handler =
-        chromeos::NetworkHandler::Get()
-            ->managed_network_configuration_handler();
+        ash::NetworkHandler::Get()->managed_network_configuration_handler();
 
     const std::string user_policy_ssid = kManagedUserWifiSsid;
     std::unique_ptr<base::Value> user_policy_onc =
@@ -283,7 +282,7 @@
   base::Value GetNetworkProperties(const std::string& service_path) {
     base::RunLoop run_loop;
     absl::optional<base::Value> properties;
-    chromeos::NetworkHandler::Get()
+    ash::NetworkHandler::Get()
         ->network_configuration_handler()
         ->GetShillProperties(
             service_path,
@@ -325,7 +324,7 @@
   bool GetUserSettingStringData(const std::string& guid,
                                 const std::string& key,
                                 std::string* value = nullptr) {
-    const ash::NetworkState* network = chromeos::NetworkHandler::Get()
+    const ash::NetworkState* network = ash::NetworkHandler::Get()
                                            ->network_state_handler()
                                            ->GetNetworkStateFromGuid(guid);
 
@@ -381,7 +380,7 @@
   EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
 
   const ash::NetworkState* network =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_state_handler()
           ->GetNetworkStateFromGuid(kSharedWifiGuid);
   ASSERT_TRUE(network);
@@ -398,7 +397,7 @@
   EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
 
   const ash::NetworkState* network =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_state_handler()
           ->GetNetworkStateFromGuid(kPrivateWifiGuid);
   ASSERT_TRUE(network);
@@ -547,7 +546,7 @@
   ASSERT_TRUE(result->is_string());
 
   std::string guid = result->GetString();
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
+  const ash::NetworkState* network = ash::NetworkHandler::Get()
                                          ->network_state_handler()
                                          ->GetNetworkStateFromGuid(guid);
   ASSERT_TRUE(network);
@@ -574,7 +573,7 @@
 
   // Test the created config can be changed now.
   std::string guid = result->GetString();
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
+  const ash::NetworkState* network = ash::NetworkHandler::Get()
                                          ->network_state_handler()
                                          ->GetNetworkStateFromGuid(guid);
   ASSERT_TRUE(network);
@@ -895,7 +894,7 @@
   ASSERT_TRUE(result->is_string());
 
   std::string guid = result->GetString();
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
+  const ash::NetworkState* network = ash::NetworkHandler::Get()
                                          ->network_state_handler()
                                          ->GetNetworkStateFromGuid(guid);
 
@@ -934,7 +933,7 @@
 
   // Test the created config can be changed now.
   const std::string guid = result->GetString();
-  const ash::NetworkState* network = chromeos::NetworkHandler::Get()
+  const ash::NetworkState* network = ash::NetworkHandler::Get()
                                          ->network_state_handler()
                                          ->GetNetworkStateFromGuid(guid);
   ASSERT_TRUE(network);
@@ -1212,7 +1211,7 @@
                 base::StringPrintf(R"(["%s"])", kManagedUserWifiGuid)));
 
   const ash::NetworkState* network =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_state_handler()
           ->GetNetworkStateFromGuid(kManagedUserWifiGuid);
 
@@ -1233,7 +1232,7 @@
                 base::StringPrintf(R"(["%s"])", kManagedUserWifiGuid)));
 
   const ash::NetworkState* network =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_state_handler()
           ->GetNetworkStateFromGuid(kManagedUserWifiGuid);
 
@@ -1245,7 +1244,7 @@
 
 TEST_F(NetworkingPrivateApiTest, ForgetDevicePolicyNetworkWebUI) {
   const ash::NetworkState* network =
-      chromeos::NetworkHandler::Get()
+      ash::NetworkHandler::Get()
           ->network_state_handler()
           ->GetNetworkStateFromGuid(kManagedDeviceWifiGuid);
   ASSERT_TRUE(network);
diff --git a/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc b/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc
index fa28f97..57e8182 100644
--- a/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc
@@ -22,9 +22,9 @@
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
 using ::ash::DeviceState;
+using ::ash::NetworkHandler;
 using ::ash::NetworkState;
-using chromeos::NetworkHandler;
-using chromeos::NetworkStateHandler;
+using ::ash::NetworkStateHandler;
 
 namespace extensions {
 
diff --git a/extensions/shell/browser/api/file_system/shell_file_system_delegate.cc b/extensions/shell/browser/api/file_system/shell_file_system_delegate.cc
index 4a0f7b9..2d60fe5 100644
--- a/extensions/shell/browser/api/file_system/shell_file_system_delegate.cc
+++ b/extensions/shell/browser/api/file_system/shell_file_system_delegate.cc
@@ -63,6 +63,28 @@
   return 0;
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+bool ShellFileSystemDelegate::IsGrantable(
+    content::BrowserContext* browser_context,
+    const Extension& extension) {
+  return false;
+}
+
+void ShellFileSystemDelegate::RequestFileSystem(
+    content::BrowserContext* browser_context,
+    scoped_refptr<ExtensionFunction> requester,
+    const Extension& extension,
+    std::string volume_id,
+    bool writable,
+    FileSystemCallback success_callback,
+    ErrorCallback error_callback) {}
+
+void ShellFileSystemDelegate::GetVolumeList(
+    content::BrowserContext* browser_context,
+    VolumeListCallback success_callback,
+    ErrorCallback error_callback) {}
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 SavedFilesServiceInterface* ShellFileSystemDelegate::GetSavedFilesService(
     content::BrowserContext* browser_context) {
   return apps::SavedFilesService::Get(browser_context);
diff --git a/extensions/shell/browser/api/file_system/shell_file_system_delegate.h b/extensions/shell/browser/api/file_system/shell_file_system_delegate.h
index cc7d61d..a78e62f 100644
--- a/extensions/shell/browser/api/file_system/shell_file_system_delegate.h
+++ b/extensions/shell/browser/api/file_system/shell_file_system_delegate.h
@@ -7,6 +7,8 @@
 
 #include "extensions/browser/api/file_system/file_system_delegate.h"
 
+#include "build/build_config.h"
+
 namespace extensions {
 
 class ShellFileSystemDelegate : public FileSystemDelegate {
@@ -36,6 +38,20 @@
                                        base::OnceClosure on_accept,
                                        base::OnceClosure on_cancel) override;
   int GetDescriptionIdForAcceptType(const std::string& accept_type) override;
+#if BUILDFLAG(IS_CHROMEOS)
+  bool IsGrantable(content::BrowserContext* browser_context,
+                   const Extension& extension) override;
+  void RequestFileSystem(content::BrowserContext* browser_context,
+                         scoped_refptr<ExtensionFunction> requester,
+                         const Extension& extension,
+                         std::string volume_id,
+                         bool writable,
+                         FileSystemCallback success_callback,
+                         ErrorCallback error_callback) override;
+  void GetVolumeList(content::BrowserContext* browser_context,
+                     VolumeListCallback success_callback,
+                     ErrorCallback error_callback) override;
+#endif  // BUILDFLAG(IS_CHROMEOS)
   SavedFilesServiceInterface* GetSavedFilesService(
       content::BrowserContext* browser_context) override;
 };
diff --git a/extensions/shell/browser/shell_browser_main_parts.cc b/extensions/shell/browser/shell_browser_main_parts.cc
index fa05bba9..b20c3fa 100644
--- a/extensions/shell/browser/shell_browser_main_parts.cc
+++ b/extensions/shell/browser/shell_browser_main_parts.cc
@@ -141,7 +141,7 @@
   // Depends on CrosDisksClient.
   ash::disks::DiskMountManager::Initialize();
 
-  chromeos::NetworkHandler::Initialize();
+  ash::NetworkHandler::Initialize();
   network_controller_ = std::make_unique<ShellNetworkController>(
       base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
           switches::kAppShellPreferredNetwork));
@@ -306,7 +306,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   network_controller_.reset();
-  chromeos::NetworkHandler::Shutdown();
+  ash::NetworkHandler::Shutdown();
   ash::disks::DiskMountManager::Shutdown();
   chromeos::PowerManagerClient::Shutdown();
   chromeos::CrosDisksClient::Shutdown();
diff --git a/extensions/shell/browser/shell_network_controller_chromeos.cc b/extensions/shell/browser/shell_network_controller_chromeos.cc
index 48c003a..b0aaf15 100644
--- a/extensions/shell/browser/shell_network_controller_chromeos.cc
+++ b/extensions/shell/browser/shell_network_controller_chromeos.cc
@@ -42,8 +42,8 @@
 
 // Returns true if shill is either connected or connecting to a network.
 bool IsConnectedOrConnecting() {
-  chromeos::NetworkStateHandler* state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   return state_handler->ConnectedNetworkByType(
              ash::NetworkTypePattern::Default()) ||
          state_handler->ConnectingNetworkByType(
@@ -57,8 +57,8 @@
     : state_(STATE_IDLE),
       preferred_network_name_(preferred_network_name),
       preferred_network_is_active_(false) {
-  chromeos::NetworkStateHandler* state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   state_handler->AddObserver(this, FROM_HERE);
   state_handler->SetTechnologyEnabled(
       ash::NetworkTypePattern::Primitive(shill::kTypeWifi), true,
@@ -69,7 +69,7 @@
 }
 
 ShellNetworkController::~ShellNetworkController() {
-  chromeos::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
+  ash::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
       this, FROM_HERE);
 }
 
@@ -105,8 +105,8 @@
 }
 
 void ShellNetworkController::SetCellularAllowRoaming(bool allow_roaming) {
-  chromeos::NetworkHandler* handler = chromeos::NetworkHandler::Get();
-  chromeos::NetworkStateHandler::NetworkStateList network_list;
+  ash::NetworkHandler* handler = ash::NetworkHandler::Get();
+  ash::NetworkStateHandler::NetworkStateList network_list;
 
   base::DictionaryValue properties;
   properties.SetKey(shill::kCellularAllowRoamingProperty,
@@ -123,8 +123,8 @@
 }
 
 const ash::NetworkState* ShellNetworkController::GetActiveWiFiNetwork() {
-  chromeos::NetworkStateHandler* state_handler =
-      chromeos::NetworkHandler::Get()->network_state_handler();
+  ash::NetworkStateHandler* state_handler =
+      ash::NetworkHandler::Get()->network_state_handler();
   const ash::NetworkState* network = state_handler->FirstNetworkByType(
       ash::NetworkTypePattern::Primitive(shill::kTypeWifi));
   return network &&
@@ -150,7 +150,7 @@
 
 void ShellNetworkController::RequestScan() {
   VLOG(1) << "Requesting scan";
-  chromeos::NetworkHandler::Get()->network_state_handler()->RequestScan(
+  ash::NetworkHandler::Get()->network_state_handler()->RequestScan(
       ash::NetworkTypePattern::Default());
 }
 
@@ -164,14 +164,13 @@
   const ash::NetworkState* best_network = NULL;
   bool can_connect_to_preferred_network = false;
 
-  chromeos::NetworkHandler* handler = chromeos::NetworkHandler::Get();
-  chromeos::NetworkStateHandler::NetworkStateList network_list;
+  ash::NetworkHandler* handler = ash::NetworkHandler::Get();
+  ash::NetworkStateHandler::NetworkStateList network_list;
   handler->network_state_handler()->GetVisibleNetworkListByType(
       ash::NetworkTypePattern::WiFi(), &network_list);
-  for (chromeos::NetworkStateHandler::NetworkStateList::const_iterator it =
+  for (ash::NetworkStateHandler::NetworkStateList::const_iterator it =
            network_list.begin();
-       it != network_list.end();
-       ++it) {
+       it != network_list.end(); ++it) {
     const ash::NetworkState* network = *it;
     if (!network->connectable())
       continue;
diff --git a/extensions/shell/browser/shell_network_controller_chromeos.h b/extensions/shell/browser/shell_network_controller_chromeos.h
index b38ccd1..94c9c843 100644
--- a/extensions/shell/browser/shell_network_controller_chromeos.h
+++ b/extensions/shell/browser/shell_network_controller_chromeos.h
@@ -65,7 +65,7 @@
   void HandleConnectionSuccess();
   void HandleConnectionError(const std::string& error_name);
 
-  // Current status of communication with the chromeos::NetworkStateHandler.
+  // Current status of communication with the ash::NetworkStateHandler.
   // This is tracked to avoid sending duplicate requests before the handler has
   // acknowledged the initial connection attempt.
   State state_;
diff --git a/fuchsia_web/common/test/run_all_integration_tests.cc b/fuchsia_web/common/test/run_all_integration_tests.cc
index be4ad2b7..ca7f99e 100644
--- a/fuchsia_web/common/test/run_all_integration_tests.cc
+++ b/fuchsia_web/common/test/run_all_integration_tests.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/system/sys_info.h"
 #include "base/test/launcher/unit_test_launcher.h"
 #include "base/test/test_suite.h"
@@ -21,5 +22,6 @@
 
   return base::LaunchUnitTestsWithOptions(
       argc, argv, jobs, kDefaultTestBatchLimit, true /* use_job_objects */,
+      base::DoNothing(),
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/gin/gin_features.cc b/gin/gin_features.cc
index 497ed28..9bfe5163 100644
--- a/gin/gin_features.cc
+++ b/gin/gin_features.cc
@@ -131,4 +131,9 @@
 const base::Feature kV8SlowHistogramsScriptAblation{
     "V8SlowHistogramsScriptAblation", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kV8DelayMemoryReducer{"V8DelayMemoryReducer",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+const base::FeatureParam<base::TimeDelta> kV8MemoryReducerStartDelay{
+    &kV8DelayMemoryReducer, "delay", base::Seconds(8)};
+
 }  // namespace features
diff --git a/gin/gin_features.h b/gin/gin_features.h
index 1af22c2..a2a6fd8 100644
--- a/gin/gin_features.h
+++ b/gin/gin_features.h
@@ -7,6 +7,7 @@
 
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/time/time.h"
 #include "gin/gin_export.h"
 
 namespace features {
@@ -47,6 +48,9 @@
 GIN_EXPORT extern const base::Feature kV8TurboFastApiCalls;
 GIN_EXPORT extern const base::Feature kV8Turboprop;
 GIN_EXPORT extern const base::Feature kV8UseMapSpace;
+GIN_EXPORT extern const base::Feature kV8DelayMemoryReducer;
+GIN_EXPORT extern const base::FeatureParam<base::TimeDelta>
+    kV8MemoryReducerStartDelay;
 
 }  // namespace features
 
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index ab2affb..cb3302bb 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -260,6 +260,12 @@
   SetV8FlagsIfOverridden(features::kV8OffThreadFinalization,
                          "--finalize-streaming-on-background",
                          "--no-finalize-streaming-on-background");
+  if (base::FeatureList::IsEnabled(features::kV8DelayMemoryReducer)) {
+    SetV8FlagsFormatted(
+        "--gc-memory-reducer-start-delay-ms=%i",
+        static_cast<int>(
+            features::kV8MemoryReducerStartDelay.Get().InMilliseconds()));
+  }
   SetV8FlagsIfOverridden(features::kV8LazyFeedbackAllocation,
                          "--lazy-feedback-allocation",
                          "--no-lazy-feedback-allocation");
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index 4148b86f..3e5cccc 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -2445,7 +2445,7 @@
       &passthrough_discardable_manager_, &shared_image_manager_);
 
   surface_ = gl::init::CreateOffscreenGLSurface(
-      context_creation_attribs_.offscreen_framebuffer_size);
+      display_, context_creation_attribs_.offscreen_framebuffer_size);
   context_ = gl::init::CreateGLContext(
       nullptr, surface_.get(),
       GenerateGLContextAttribs(context_creation_attribs_, group_.get()));
diff --git a/gpu/command_buffer/service/gr_cache_controller_unittest.cc b/gpu/command_buffer/service/gr_cache_controller_unittest.cc
index e8a9b4e..29f489e 100644
--- a/gpu/command_buffer/service/gr_cache_controller_unittest.cc
+++ b/gpu/command_buffer/service/gr_cache_controller_unittest.cc
@@ -32,7 +32,7 @@
 
     scoped_refptr<gl::GLShareGroup> share_group = new gl::GLShareGroup();
     scoped_refptr<gl::GLSurface> surface =
-        gl::init::CreateOffscreenGLSurface(gfx::Size());
+        gl::init::CreateOffscreenGLSurface(display_, gfx::Size());
     scoped_refptr<gl::GLContext> context = gl::init::CreateGLContext(
         share_group.get(), surface.get(), gl::GLContextAttribs());
     ASSERT_TRUE(context->MakeCurrent(surface.get()));
diff --git a/gpu/command_buffer/service/raster_decoder_unittest.cc b/gpu/command_buffer/service/raster_decoder_unittest.cc
index 85f0699..dfefb1d1 100644
--- a/gpu/command_buffer/service/raster_decoder_unittest.cc
+++ b/gpu/command_buffer/service/raster_decoder_unittest.cc
@@ -203,7 +203,7 @@
 
     scoped_refptr<gl::GLShareGroup> share_group = new gl::GLShareGroup();
     scoped_refptr<gl::GLSurface> surface =
-        gl::init::CreateOffscreenGLSurface(gfx::Size());
+        gl::init::CreateOffscreenGLSurface(display_, gfx::Size());
     scoped_refptr<gl::GLContext> context = gl::init::CreateGLContext(
         share_group.get(), surface.get(), gl::GLContextAttribs());
     ASSERT_TRUE(context->MakeCurrent(surface.get()));
diff --git a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
index f6c529e9..642f076 100644
--- a/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory_unittest.cc
@@ -34,6 +34,7 @@
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_image.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gpu {
@@ -46,8 +47,8 @@
     // should not be run on android versions less that O.
     if (!base::AndroidHardwareBufferCompat::IsSupportAvailable())
       return;
-
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                  gfx::Size());
     ASSERT_TRUE(surface_);
     context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                          gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
index b1e3e91..c6a1479 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
@@ -35,6 +35,7 @@
 #include "ui/gl/gl_image_d3d.h"
 #include "ui/gl/gl_image_memory.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(USE_DAWN)
@@ -135,8 +136,8 @@
   void SetUp() override {
     if (!IsD3DSharedImageSupported())
       return;
-
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                  gfx::Size());
     ASSERT_TRUE(surface_);
     context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                          gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
index b46518b..0bcf29d 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing_factory_unittest.cc
@@ -41,6 +41,7 @@
 #include "ui/gl/gl_image_shared_memory.h"
 #include "ui/gl/gl_image_stub.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 using testing::AtLeast;
@@ -53,7 +54,8 @@
                          scoped_refptr<gl::GLContext>& context,
                          scoped_refptr<SharedContextState>& context_state,
                          scoped_refptr<gles2::FeatureInfo>& feature_info) {
-  surface = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                               gfx::Size());
   ASSERT_TRUE(surface);
   context =
       gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory_unittest.cc
index 706b863..50461aec 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory_unittest.cc
@@ -29,6 +29,7 @@
 #include "ui/gl/buildflags.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(USE_DAWN)
@@ -65,7 +66,8 @@
 
     // Set up a GL context. We don't actually need it, but we can't make
     // a SharedContextState without one.
-    gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    gl_surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                     gfx::Size());
     DCHECK(gl_surface_);
     gl_context_ = gl::init::CreateGLContext(nullptr, gl_surface_.get(),
                                             gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
index 8ff5f049..5b0d4292 100644
--- a/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
@@ -41,6 +41,7 @@
 #include "ui/gl/gl_image_shared_memory.h"
 #include "ui/gl/gl_image_stub.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/gl/progress_reporter.h"
 
@@ -54,7 +55,8 @@
                          scoped_refptr<gl::GLContext>& context,
                          scoped_refptr<SharedContextState>& context_state,
                          scoped_refptr<gles2::FeatureInfo>& feature_info) {
-  surface = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   ASSERT_TRUE(surface);
   context =
       gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index cb0af15..a8a3a70e 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -37,6 +37,7 @@
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/gl/progress_reporter.h"
 
@@ -50,7 +51,8 @@
                          scoped_refptr<gl::GLContext>& context,
                          scoped_refptr<SharedContextState>& context_state,
                          scoped_refptr<gles2::FeatureInfo>& feature_info) {
-  surface = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   ASSERT_TRUE(surface);
   context =
       gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
index 681eba7..0e2a6d23 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
@@ -29,6 +29,7 @@
 #include "ui/gl/buildflags.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(USE_DAWN)
@@ -43,7 +44,8 @@
 class IOSurfaceImageBackingFactoryTest : public testing::Test {
  public:
   void SetUp() override {
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplayEGL(),
+                                                  gfx::Size());
     ASSERT_TRUE(surface_);
     context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                          gl::GLContextAttribs());
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory_unittest.cc b/gpu/command_buffer/service/shared_image/shared_image_factory_unittest.cc
index b4b725a0..1260e474 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory_unittest.cc
@@ -18,7 +18,9 @@
 #include "ui/gfx/color_space.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gpu {
@@ -27,7 +29,8 @@
 class SharedImageFactoryTest : public testing::Test {
  public:
   void SetUp() override {
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                  gfx::Size());
     ASSERT_TRUE(surface_);
     context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                          gl::GLContextAttribs());
diff --git a/gpu/command_buffer/tests/decoder_perftest.cc b/gpu/command_buffer/tests/decoder_perftest.cc
index 415188eb..1b297f6 100644
--- a/gpu/command_buffer/tests/decoder_perftest.cc
+++ b/gpu/command_buffer/tests/decoder_perftest.cc
@@ -37,6 +37,7 @@
 #include "ui/gl/gl_context_stub.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface_stub.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gpu {
@@ -171,7 +172,8 @@
       gl::GLContextAttribs attribs;
       if (gpu_preferences_.use_passthrough_cmd_decoder)
         attribs.bind_generates_resource = bind_generates_resource;
-      surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+      surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                    gfx::Size());
       context_ = gl::init::CreateGLContext(share_group_.get(), surface_.get(),
                                            attribs);
     }
diff --git a/gpu/command_buffer/tests/fuzzer_main.cc b/gpu/command_buffer/tests/fuzzer_main.cc
index 15f9ba37..d0e3fde 100644
--- a/gpu/command_buffer/tests/fuzzer_main.cc
+++ b/gpu/command_buffer/tests/fuzzer_main.cc
@@ -46,6 +46,7 @@
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/gl_surface_egl.h"
 #include "ui/gl/gl_surface_stub.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/gl/test/gl_surface_test_support.h"
 
@@ -341,11 +342,12 @@
 
     CHECK(gl::init::InitializeStaticGLBindingsImplementation(
         gl::GLImplementationParts(gl::kGLImplementationEGLANGLE), false));
-    CHECK(gl::init::InitializeGLOneOffPlatformImplementation(
+    display_ = gl::init::InitializeGLOneOffPlatformImplementation(
         /*fallback_to_software_gl=*/false,
         /*disable_gl_drawing=*/false,
         /*init_extensions=*/true,
-        /*system_device_id=*/0));
+        /*system_device_id=*/0);
+    CHECK(display_);
 #elif defined(GPU_FUZZER_USE_STUB)
     gl::GLSurfaceTestSupport::InitializeOneOffWithStubBindings();
     // Because the context depends on configuration bits, we want to recreate
@@ -362,12 +364,19 @@
     if (gpu_preferences_.use_passthrough_cmd_decoder)
       recreate_context_ = true;
 
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(display_, gfx::Size());
     if (!recreate_context_) {
       InitContext();
     }
   }
 
+  ~CommandBufferSetup() {
+    if (display_) {
+      gl::init::ShutdownGL(display_, false);
+      display_ = nullptr;
+    }
+  }
+
   bool InitDecoder() {
     if (!context_) {
       InitContext();
@@ -636,6 +645,7 @@
   std::unique_ptr<SharedImageFactory> shared_image_factory_;
 
   bool recreate_context_ = false;
+  gl::GLDisplay* display_ = nullptr;
   scoped_refptr<gl::GLSurface> surface_;
   scoped_refptr<gl::GLContext> context_;
   scoped_refptr<SharedContextState> context_state_;
diff --git a/gpu/command_buffer/tests/gl_manager.cc b/gpu/command_buffer/tests/gl_manager.cc
index 4c803c70..9955d71 100644
--- a/gpu/command_buffer/tests/gl_manager.cc
+++ b/gpu/command_buffer/tests/gl_manager.cc
@@ -47,6 +47,7 @@
 #include "ui/gl/gl_image_ref_counted_memory.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if BUILDFLAG(IS_MAC)
@@ -358,7 +359,8 @@
 
   command_buffer_->set_handler(decoder_.get());
 
-  surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface_ =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   ASSERT_TRUE(surface_.get() != nullptr)
       << "could not create offscreen surface";
 
@@ -427,7 +429,7 @@
         new scoped_refptr<gl::GLShareGroup>(new gl::GLShareGroup);
     gfx::Size size(4, 4);
     base_surface_ = new scoped_refptr<gl::GLSurface>(
-        gl::init::CreateOffscreenGLSurface(size));
+        gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), size));
     base_context_ = new scoped_refptr<gl::GLContext>(gl::init::CreateGLContext(
         base_share_group_->get(), base_surface_->get(),
         gl::GLContextAttribs()));
diff --git a/gpu/command_buffer/tests/gl_unittests_android.cc b/gpu/command_buffer/tests/gl_unittests_android.cc
index cc6eccb7..6901fc8 100644
--- a/gpu/command_buffer/tests/gl_unittests_android.cc
+++ b/gpu/command_buffer/tests/gl_unittests_android.cc
@@ -16,6 +16,7 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gl/android/surface_texture.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gpu {
@@ -40,7 +41,7 @@
   EXPECT_TRUE(window != nullptr);
 
   scoped_refptr<gl::GLSurface> gl_surface =
-      gl::init::CreateViewGLSurface(window);
+      gl::init::CreateViewGLSurface(gl::GetDefaultDisplayEGL(), window);
   EXPECT_TRUE(gl_surface.get() != nullptr);
 
   gl_.SetSurface(gl_surface.get());
diff --git a/gpu/config/gpu_info_collector.cc b/gpu/config/gpu_info_collector.cc
index aebd98c..d5fb536e 100644
--- a/gpu/config/gpu_info_collector.cc
+++ b/gpu/config/gpu_info_collector.cc
@@ -73,9 +73,9 @@
 #define EGL_FEATURE_CONDITION_ANGLE 0x3468
 #endif /* EGL_ANGLE_feature_control */
 
-scoped_refptr<gl::GLSurface> InitializeGLSurface() {
+scoped_refptr<gl::GLSurface> InitializeGLSurface(gl::GLDisplay* display) {
   scoped_refptr<gl::GLSurface> surface(
-      gl::init::CreateOffscreenGLSurface(gfx::Size()));
+      gl::init::CreateOffscreenGLSurface(display, gfx::Size()));
   if (!surface.get()) {
     LOG(ERROR) << "gl::GLContext::CreateOffscreenGLSurface failed";
     return nullptr;
@@ -334,7 +334,7 @@
   return CollectBasicGraphicsInfo(gpu_info);
 }
 
-bool CollectGraphicsInfoGL(GPUInfo* gpu_info) {
+bool CollectGraphicsInfoGL(GPUInfo* gpu_info, gl::GLDisplay* display) {
   TRACE_EVENT0("startup", "gpu_info_collector::CollectGraphicsInfoGL");
   DCHECK_NE(gl::GetGLImplementation(), gl::kGLImplementationNone);
 
@@ -343,7 +343,7 @@
     gpu_info->passthrough_cmd_decoder = false;
   }
 
-  scoped_refptr<gl::GLSurface> surface(InitializeGLSurface());
+  scoped_refptr<gl::GLSurface> surface(InitializeGLSurface(display));
   if (!surface.get()) {
     LOG(ERROR) << "Could not create surface for info collection.";
     return false;
@@ -386,11 +386,11 @@
   base::UmaHistogramSparse("GPU.MaxMSAASampleCount", max_samples);
 
 #if BUILDFLAG(IS_ANDROID)
-  gl::GLDisplayEGL* display = gl::GLSurfaceEGL::GetGLDisplayEGL();
+  gl::GLDisplayEGL* egl_display = display->GetAs<gl::GLDisplayEGL>();
   gpu_info->can_support_threaded_texture_mailbox =
-      display->ext->b_EGL_KHR_fence_sync &&
-      display->ext->b_EGL_KHR_image_base &&
-      display->ext->b_EGL_KHR_gl_texture_2D_image &&
+      egl_display->ext->b_EGL_KHR_fence_sync &&
+      egl_display->ext->b_EGL_KHR_image_base &&
+      egl_display->ext->b_EGL_KHR_gl_texture_2D_image &&
       gfx::HasExtension(extension_set, "GL_OES_EGL_image");
 #else
   gl::GLWindowSystemBindingInfo window_system_binding_info;
diff --git a/gpu/config/gpu_info_collector.h b/gpu/config/gpu_info_collector.h
index a589192..a0152aa 100644
--- a/gpu/config/gpu_info_collector.h
+++ b/gpu/config/gpu_info_collector.h
@@ -17,6 +17,10 @@
 #include <d3dcommon.h>
 #endif  // BUILDFLAG(IS_WIN)
 
+namespace gl {
+class GLDisplay;
+}
+
 namespace angle {
 struct SystemInfo;
 }
@@ -70,7 +74,8 @@
 #endif  // BUILDFLAG(IS_WIN)
 
 // Create a GL context and collect GL strings and versions.
-GPU_EXPORT bool CollectGraphicsInfoGL(GPUInfo* gpu_info);
+GPU_EXPORT bool CollectGraphicsInfoGL(GPUInfo* gpu_info,
+                                      gl::GLDisplay* display);
 
 // If more than one GPUs are identified, and GL strings are available,
 // identify the active GPU based on GL strings.
diff --git a/gpu/config/gpu_info_collector_android.cc b/gpu/config/gpu_info_collector_android.cc
index e529df6..20914857 100644
--- a/gpu/config/gpu_info_collector_android.cc
+++ b/gpu/config/gpu_info_collector_android.cc
@@ -9,6 +9,8 @@
 #include "base/android/build_info.h"
 #include "base/android/jni_android.h"
 #include "base/notreached.h"
+#include "ui/gl/gl_display.h"
+#include "ui/gl/gl_utils.h"
 
 namespace gpu {
 
@@ -21,7 +23,7 @@
   }
 
   // At this point GL bindings have been initialized already.
-  return CollectGraphicsInfoGL(gpu_info);
+  return CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplayEGL());
 }
 
 bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
diff --git a/gpu/config/gpu_info_collector_fuchsia.cc b/gpu/config/gpu_info_collector_fuchsia.cc
index 1065114..9df5affd9 100644
--- a/gpu/config/gpu_info_collector_fuchsia.cc
+++ b/gpu/config/gpu_info_collector_fuchsia.cc
@@ -6,6 +6,8 @@
 
 #include "base/trace_event/trace_event.h"
 #include "third_party/angle/src/gpu_info_util/SystemInfo.h"
+#include "ui/gl/gl_display.h"
+#include "ui/gl/gl_utils.h"
 
 namespace gpu {
 
@@ -14,7 +16,7 @@
 
   TRACE_EVENT0("gpu", "gpu_info_collector::CollectGraphicsInfo");
 
-  return CollectGraphicsInfoGL(gpu_info);
+  return CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplay());
 }
 
 bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
diff --git a/gpu/config/gpu_info_collector_linux.cc b/gpu/config/gpu_info_collector_linux.cc
index faab718..a8aa560 100644
--- a/gpu/config/gpu_info_collector_linux.cc
+++ b/gpu/config/gpu_info_collector_linux.cc
@@ -7,6 +7,8 @@
 #include "build/chromecast_buildflags.h"
 #include "gpu/config/gpu_info_collector.h"
 #include "third_party/angle/src/gpu_info_util/SystemInfo.h"
+#include "ui/gl/gl_display.h"
+#include "ui/gl/gl_utils.h"
 
 namespace gpu {
 
@@ -22,7 +24,7 @@
   gpu_info->machine_model_name = "Chromecast";
 #endif  // BUILDFLAG(IS_CASTOS)
 
-  return CollectGraphicsInfoGL(gpu_info);
+  return CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplay());
 }
 
 bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
diff --git a/gpu/config/gpu_info_collector_mac.mm b/gpu/config/gpu_info_collector_mac.mm
index 2fe78e2..57f8d8fc 100644
--- a/gpu/config/gpu_info_collector_mac.mm
+++ b/gpu/config/gpu_info_collector_mac.mm
@@ -9,6 +9,8 @@
 #include "base/trace_event/trace_event.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "third_party/angle/src/gpu_info_util/SystemInfo.h"
+#include "ui/gl/gl_display.h"
+#include "ui/gl/gl_utils.h"
 
 #import <Metal/Metal.h>
 
@@ -72,7 +74,7 @@
 
   RecordReadWriteMetalTexturesSupportedHistogram();
 
-  return CollectGraphicsInfoGL(gpu_info);
+  return CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplayEGL());
 }
 
 bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
diff --git a/gpu/config/gpu_info_collector_unittest.cc b/gpu/config/gpu_info_collector_unittest.cc
index 760337e..92c3a8cc 100644
--- a/gpu/config/gpu_info_collector_unittest.cc
+++ b/gpu/config/gpu_info_collector_unittest.cc
@@ -18,9 +18,11 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gl/gl_context_stub.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_mock.h"
 #include "ui/gl/gl_surface_stub.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/gl/test/gl_surface_test_support.h"
 
@@ -216,7 +218,7 @@
 // be fixed.
 TEST_P(GPUInfoCollectorTest, CollectGraphicsInfoGL) {
   GPUInfo gpu_info;
-  CollectGraphicsInfoGL(&gpu_info);
+  CollectGraphicsInfoGL(&gpu_info, gl::GetDefaultDisplay());
 #if BUILDFLAG(IS_WIN)
   if (GetParam() == kMockedWindows) {
     EXPECT_EQ(test_values_.gpu.driver_vendor, gpu_info.gpu.driver_vendor);
diff --git a/gpu/config/gpu_info_collector_win.cc b/gpu/config/gpu_info_collector_win.cc
index 5cb016e..2d247df 100644
--- a/gpu/config/gpu_info_collector_win.cc
+++ b/gpu/config/gpu_info_collector_win.cc
@@ -37,7 +37,9 @@
 #include "gpu/config/gpu_util.h"
 #include "ui/gl/direct_composition_support.h"
 #include "ui/gl/gl_angle_util_win.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 
 namespace gpu {
 
@@ -721,7 +723,7 @@
 
   DCHECK(gpu_info);
 
-  if (!CollectGraphicsInfoGL(gpu_info))
+  if (!CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplayEGL()))
     return false;
 
   // ANGLE's renderer strings are of the form:
diff --git a/gpu/dawn_end2end_tests_main.cc b/gpu/dawn_end2end_tests_main.cc
index 9df1925..c7721af 100644
--- a/gpu/dawn_end2end_tests_main.cc
+++ b/gpu/dawn_end2end_tests_main.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/test/launcher/unit_test_launcher.h"
@@ -34,9 +35,10 @@
   base::TestSuite test_suite(argc, argv);
   int rt = base::LaunchUnitTestsWithOptions(
       argc, argv,
-      1,     // Run tests serially.
-      0,     // Disable batching.
-      true,  // Use job objects.
+      1,                  // Run tests serially.
+      0,                  // Disable batching.
+      true,               // Use job objects.
+      base::DoNothing(),  // No special action on timeout.
       base::BindOnce(&RunHelper, base::Unretained(&test_suite)));
   return rt;
 }
diff --git a/gpu/gles2_conform_support/egl/display.cc b/gpu/gles2_conform_support/egl/display.cc
index 6ff47fc..affff0a 100644
--- a/gpu/gles2_conform_support/egl/display.cc
+++ b/gpu/gles2_conform_support/egl/display.cc
@@ -11,6 +11,8 @@
 #include "gpu/gles2_conform_support/egl/context.h"
 #include "gpu/gles2_conform_support/egl/surface.h"
 #include "gpu/gles2_conform_support/egl/thread_state.h"
+#include "ui/gl/gl_display.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gles2_conform_support {
@@ -20,8 +22,7 @@
     : is_initialized_(false),
       next_create_window_surface_creates_pbuffer_(false),
       window_surface_pbuffer_width_(0),
-      window_surface_pbuffer_height_(0) {
-}
+      window_surface_pbuffer_height_(0) {}
 
 Display::~Display() {
   surfaces_.clear();
@@ -183,7 +184,8 @@
                                            EGLint height) {
   lock_.AssertAcquired();
   scoped_refptr<gl::GLSurface> gl_surface;
-  gl_surface = gl::init::CreateOffscreenGLSurface(gfx::Size(width, height));
+  gl_surface = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                  gfx::Size(width, height));
   if (!gl_surface)
     return ts->ReturnError(EGL_BAD_ALLOC, nullptr);
   surfaces_.emplace_back(new Surface(gl_surface.get(), config));
@@ -223,7 +225,7 @@
 #else
   gfx::AcceleratedWidget widget = static_cast<gfx::AcceleratedWidget>(win);
 #endif
-  gl_surface = gl::init::CreateViewGLSurface(widget);
+  gl_surface = gl::init::CreateViewGLSurface(gl::GetDefaultDisplay(), widget);
   if (!gl_surface)
     return ts->ReturnError(EGL_BAD_ALLOC, EGL_NO_SURFACE);
   surfaces_.emplace_back(new Surface(gl_surface.get(), config));
diff --git a/gpu/gles2_conform_support/egl/display.h b/gpu/gles2_conform_support/egl/display.h
index 93602ea..322908d1 100644
--- a/gpu/gles2_conform_support/egl/display.h
+++ b/gpu/gles2_conform_support/egl/display.h
@@ -15,8 +15,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
 
-namespace gles2_conform_support {
-namespace egl {
+namespace gles2_conform_support::egl {
 
 class Config;
 class Context;
@@ -105,7 +104,6 @@
   EGLint window_surface_pbuffer_height_;
 };
 
-}  // namespace egl
-}  // namespace gles2_conform_support
+}  // namespace gles2_conform_support::egl
 
 #endif  // GPU_GLES2_CONFORM_SUPPORT_EGL_DISPLAY_H_
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index b74e7f2..e4d74b4 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -64,6 +64,8 @@
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_share_group.h"
+#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gl_version_info.h"
 #include "ui/gl/init/create_gr_gl_interface.h"
 #include "ui/gl/init/gl_factory.h"
@@ -286,7 +288,8 @@
   } else {
     // TODO(crbug.com/1247756): Is creating an offscreen GL surface needed
     // still?
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                  gfx::Size());
     if (!surface_.get()) {
       DestroyOnGpuThread();
       LOG(ERROR) << "ContextResult::kFatalFailure: Failed to create surface.";
diff --git a/gpu/ipc/in_process_gpu_thread_holder.cc b/gpu/ipc/in_process_gpu_thread_holder.cc
index 02167032..ab4e5f0 100644
--- a/gpu/ipc/in_process_gpu_thread_holder.cc
+++ b/gpu/ipc/in_process_gpu_thread_holder.cc
@@ -17,6 +17,7 @@
 #include "gpu/command_buffer/service/sync_point_manager.h"
 #include "gpu/config/gpu_info_collector.h"
 #include "gpu/config/gpu_util.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace gpu {
@@ -78,7 +79,8 @@
       gles2::PassthroughCommandDecoderSupported();
 
   share_group_ = new gl::GLShareGroup();
-  surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  surface_ =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   gl::GLContextAttribs attribs = gles2::GenerateGLContextAttribs(
       ContextCreationAttribs(), use_passthrough_cmd_decoder);
   context_ =
diff --git a/gpu/ipc/service/gles2_command_buffer_stub.cc b/gpu/ipc/service/gles2_command_buffer_stub.cc
index ac6e3f5..5c01966 100644
--- a/gpu/ipc/service/gles2_command_buffer_stub.cc
+++ b/gpu/ipc/service/gles2_command_buffer_stub.cc
@@ -46,7 +46,9 @@
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_implementation.h"
+#include "ui/gl/gl_surface_egl.h"
 #include "ui/gl/gl_switches.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gl_workarounds.h"
 #include "ui/gl/init/gl_factory.h"
 
@@ -169,6 +171,26 @@
       channel_->sync_point_manager()->CreateSyncPointClientState(
           CommandBufferNamespace::GPU_IO, command_buffer_id_, sequence_id_);
 
+  gl::GpuPreference gpu_preference = init_params.attribs.gpu_preference;
+  // If the user queries a low-power context, it's better to use whatever the
+  // default GPU used by Chrome is, which may be different than the low-power
+  // GPU determined by GLDisplayManager.
+  if (gpu_preference == gl::GpuPreference::kLowPower ||
+      gpu_preference == gl::GpuPreference::kNone) {
+    gpu_preference = gl::GpuPreference::kDefault;
+  }
+  gl::GLDisplay* display = gl::GetDisplay(gpu_preference);
+  DCHECK(display);
+
+  if (!display->IsInitialized()) {
+    gl::GLDisplay* initialized_display =
+        gl::init::InitializeGLOneOffPlatformImplementation(
+            /*fallback_to_software_gl=*/false, /*disable_gl_drawing=*/false,
+            /*init_extensions=*/true,
+            /*system_device_id=*/display->system_device_id());
+    DCHECK_EQ(initialized_display, display);
+  }
+
   if (offscreen) {
     // Do we want to create an offscreen rendering context suitable
     // for directly drawing to a separately supplied surface? In that
@@ -196,8 +218,8 @@
     if (!surface_format.IsCompatible(default_surface->GetFormat())) {
       DVLOG(1) << __FUNCTION__ << ": Hit the OwnOffscreenSurface path";
       use_virtualized_gl_context_ = false;
-      surface_ = gl::init::CreateOffscreenGLSurfaceWithFormat(gfx::Size(),
-                                                              surface_format);
+      surface_ = gl::init::CreateOffscreenGLSurfaceWithFormat(
+          display, gfx::Size(), surface_format);
       if (!surface_) {
         LOG(ERROR)
             << "ContextResult::kSurfaceFailure: Failed to create surface.";
@@ -221,7 +243,8 @@
         break;
     }
     surface_ = ImageTransportSurface::CreateNativeSurface(
-        weak_ptr_factory_.GetWeakPtr(), surface_handle_, surface_format);
+        display, weak_ptr_factory_.GetWeakPtr(), surface_handle_,
+        surface_format);
     if (!surface_ || !surface_->Initialize(surface_format)) {
       surface_ = nullptr;
       LOG(ERROR) << "ContextResult::kSurfaceFailure: Failed to create surface.";
diff --git a/gpu/ipc/service/gpu_channel_test_common.cc b/gpu/ipc/service/gpu_channel_test_common.cc
index c6fc68c..c26e6e97 100644
--- a/gpu/ipc/service/gpu_channel_test_common.cc
+++ b/gpu/ipc/service/gpu_channel_test_common.cc
@@ -102,7 +102,7 @@
       sync_point_manager_.get(), shared_image_manager_.get(),
       nullptr, /* gpu_memory_buffer_factory */
       std::move(feature_info), GpuProcessActivityFlags(),
-      gl::init::CreateOffscreenGLSurface(gfx::Size()),
+      gl::init::CreateOffscreenGLSurface(display_, gfx::Size()),
       nullptr /* image_decode_accelerator_worker */);
 }
 
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index ad88e08..7636024 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -246,7 +246,7 @@
   // supported.
   gl::SetGpuPreferenceEGL(gl::GpuPreference::kDefault,
                           system_device_id_default);
-  if (system_device_id_high_perf) {
+  if (system_device_id_high_perf && features::SupportsEGLDualGpuRendering()) {
     gl::SetGpuPreferenceEGL(gl::GpuPreference::kHighPerformance,
                             system_device_id_high_perf);
   }
@@ -664,7 +664,7 @@
       return false;
     }
     default_offscreen_surface_ =
-        gl::init::CreateOffscreenGLSurface(gfx::Size());
+        gl::init::CreateOffscreenGLSurface(gl_display, gfx::Size());
     if (!default_offscreen_surface_) {
       VLOG(1) << "gl::init::CreateOffscreenGLSurface failed";
       return false;
@@ -784,8 +784,8 @@
       command_line, gpu_feature_info_,
       gpu_preferences_.disable_software_rasterizer, false));
 
-  InitializeGLThreadSafe(command_line, gpu_preferences_, &gpu_info_,
-                         &gpu_feature_info_);
+  gl::GLDisplay* gl_display = InitializeGLThreadSafe(
+      command_line, gpu_preferences_, &gpu_info_, &gpu_feature_info_);
 
   if (command_line->HasSwitch(switches::kWebViewDrawFunctorUsesVulkan) &&
       base::FeatureList::IsEnabled(features::kWebViewVulkan)) {
@@ -795,7 +795,9 @@
   } else {
     DisableInProcessGpuVulkan(&gpu_feature_info_, &gpu_preferences_);
   }
-  default_offscreen_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+
+  default_offscreen_surface_ =
+      gl::init::CreateOffscreenGLSurface(gl_display, gfx::Size());
 
   UMA_HISTOGRAM_ENUMERATION("GPU.GLImplementation", gl::GetGLImplementation());
 }
@@ -899,7 +901,7 @@
       VLOG(1) << "gl::init::InitializeExtensionSettingsOneOffPlatform failed";
     }
     default_offscreen_surface_ =
-        gl::init::CreateOffscreenGLSurface(gfx::Size());
+        gl::init::CreateOffscreenGLSurface(gl_display, gfx::Size());
     if (!default_offscreen_surface_) {
       VLOG(1) << "gl::init::CreateOffscreenGLSurface failed";
     }
diff --git a/gpu/ipc/service/image_transport_surface.h b/gpu/ipc/service/image_transport_surface.h
index a978a061..17adff55 100644
--- a/gpu/ipc/service/image_transport_surface.h
+++ b/gpu/ipc/service/image_transport_surface.h
@@ -28,6 +28,7 @@
   // This will be implemented separately by each platform. On failure, a null
   // scoped_refptr should be returned.
   static scoped_refptr<gl::GLSurface> CreateNativeSurface(
+      gl::GLDisplay* display,
       base::WeakPtr<ImageTransportSurfaceDelegate> stub,
       SurfaceHandle surface_handle,
       gl::GLSurfaceFormat format);
diff --git a/gpu/ipc/service/image_transport_surface_android.cc b/gpu/ipc/service/image_transport_surface_android.cc
index e49b821..a475b18 100644
--- a/gpu/ipc/service/image_transport_surface_android.cc
+++ b/gpu/ipc/service/image_transport_surface_android.cc
@@ -19,6 +19,7 @@
 
 // static
 scoped_refptr<gl::GLSurface> ImageTransportSurface::CreateNativeSurface(
+    gl::GLDisplay* display,
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
@@ -42,11 +43,11 @@
       delegate->GetFeatureInfo()->feature_flags().android_surface_control &&
       can_be_used_with_surface_control) {
     surface = new gl::GLSurfaceEGLSurfaceControl(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), window,
+        display->GetAs<gl::GLDisplayEGL>(), window,
         base::ThreadTaskRunnerHandle::Get());
   } else {
-    surface = new gl::NativeViewGLSurfaceEGL(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), window, nullptr);
+    surface = new gl::NativeViewGLSurfaceEGL(display->GetAs<gl::GLDisplayEGL>(),
+                                             window, nullptr);
   }
 
   bool initialize_success = surface->Initialize(format);
diff --git a/gpu/ipc/service/image_transport_surface_fuchsia.cc b/gpu/ipc/service/image_transport_surface_fuchsia.cc
index 8103537..9238bcb 100644
--- a/gpu/ipc/service/image_transport_surface_fuchsia.cc
+++ b/gpu/ipc/service/image_transport_surface_fuchsia.cc
@@ -13,6 +13,7 @@
 
 // static
 scoped_refptr<gl::GLSurface> ImageTransportSurface::CreateNativeSurface(
+    gl::GLDisplay* display,
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
@@ -22,7 +23,7 @@
   }
 
   scoped_refptr<gl::GLSurface> surface =
-      gl::init::CreateViewGLSurface(surface_handle);
+      gl::init::CreateViewGLSurface(display, surface_handle);
 
   if (!surface)
     return surface;
diff --git a/gpu/ipc/service/image_transport_surface_linux.cc b/gpu/ipc/service/image_transport_surface_linux.cc
index 05ce217a..29a93f3 100644
--- a/gpu/ipc/service/image_transport_surface_linux.cc
+++ b/gpu/ipc/service/image_transport_surface_linux.cc
@@ -11,6 +11,7 @@
 
 // static
 scoped_refptr<gl::GLSurface> ImageTransportSurface::CreateNativeSurface(
+    gl::GLDisplay* display,
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
@@ -18,10 +19,10 @@
   scoped_refptr<gl::GLSurface> surface;
   bool override_vsync_for_multi_window_swap = false;
 #if defined(USE_OZONE)
-  surface = gl::init::CreateSurfacelessViewGLSurface(surface_handle);
+  surface = gl::init::CreateSurfacelessViewGLSurface(display, surface_handle);
 #endif
   if (!surface) {
-    surface = gl::init::CreateViewGLSurface(surface_handle);
+    surface = gl::init::CreateViewGLSurface(display, surface_handle);
     if (gl::GetGLImplementation() == gl::kGLImplementationDesktopGL ||
         gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE) {
       override_vsync_for_multi_window_swap = true;
diff --git a/gpu/ipc/service/image_transport_surface_mac.mm b/gpu/ipc/service/image_transport_surface_mac.mm
index 1071b3b..068eb98 100644
--- a/gpu/ipc/service/image_transport_surface_mac.mm
+++ b/gpu/ipc/service/image_transport_surface_mac.mm
@@ -15,6 +15,7 @@
 
 // static
 scoped_refptr<gl::GLSurface> ImageTransportSurface::CreateNativeSurface(
+    gl::GLDisplay* display,
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
@@ -30,7 +31,7 @@
     case gl::kGLImplementationEGLANGLE:
       return base::WrapRefCounted<gl::GLSurface>(
           new ImageTransportSurfaceOverlayMacEGL(
-              gl::GLSurfaceEGL::GetGLDisplayEGL(), delegate));
+              display->GetAs<gl::GLDisplayEGL>(), delegate));
 #endif
     case gl::kGLImplementationMockGL:
     case gl::kGLImplementationStubGL:
diff --git a/gpu/ipc/service/image_transport_surface_win.cc b/gpu/ipc/service/image_transport_surface_win.cc
index d8f9e42..f5f93c9a 100644
--- a/gpu/ipc/service/image_transport_surface_win.cc
+++ b/gpu/ipc/service/image_transport_surface_win.cc
@@ -45,6 +45,7 @@
 
 // static
 scoped_refptr<gl::GLSurface> ImageTransportSurface::CreateNativeSurface(
+    gl::GLDisplay* display,
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
@@ -57,7 +58,7 @@
       auto settings = CreateDirectCompositionSurfaceSettings(
           delegate->GetFeatureInfo()->workarounds());
       auto dc_surface = base::MakeRefCounted<gl::DirectCompositionSurfaceWin>(
-          gl::GLSurfaceEGL::GetGLDisplayEGL(), surface_handle,
+          display->GetAs<gl::GLDisplayEGL>(), surface_handle,
           std::move(vsync_callback), settings);
       if (!dc_surface->Initialize(gl::GLSurfaceFormat()))
         return nullptr;
@@ -67,13 +68,13 @@
     } else {
       surface = gl::InitializeGLSurface(
           base::MakeRefCounted<gl::NativeViewGLSurfaceEGL>(
-              gl::GLSurfaceEGL::GetGLDisplayEGL(), surface_handle,
+              display->GetAs<gl::GLDisplayEGL>(), surface_handle,
               std::make_unique<gl::VSyncProviderWin>(surface_handle)));
       if (!surface)
         return nullptr;
     }
   } else {
-    surface = gl::init::CreateViewGLSurface(surface_handle);
+    surface = gl::init::CreateViewGLSurface(display, surface_handle);
     if (!surface)
       return nullptr;
   }
diff --git a/gpu/perftests/texture_upload_perftest.cc b/gpu/perftests/texture_upload_perftest.cc
index e472b1d..9fc9aff 100644
--- a/gpu/perftests/texture_upload_perftest.cc
+++ b/gpu/perftests/texture_upload_perftest.cc
@@ -23,6 +23,7 @@
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gl_version_info.h"
 #include "ui/gl/gpu_timing.h"
 #include "ui/gl/init/gl_factory.h"
@@ -179,7 +180,8 @@
   // Overridden from testing::Test
   void SetUp() override {
     // Initialize an offscreen surface and a gl context.
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                  gfx::Size());
     gl_context_ =
         gl::init::CreateGLContext(nullptr,  // share_group
                                   surface_.get(), gl::GLContextAttribs());
diff --git a/gpu/swiftshader_tests_main.cc b/gpu/swiftshader_tests_main.cc
index 3198197..8af170d9 100644
--- a/gpu/swiftshader_tests_main.cc
+++ b/gpu/swiftshader_tests_main.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/test/launcher/unit_test_launcher.h"
@@ -32,9 +33,10 @@
   base::TestSuite test_suite(argc, argv);
   int rt = base::LaunchUnitTestsWithOptions(
       argc, argv,
-      1,     // Run tests serially.
-      0,     // Disable batching.
-      true,  // Use job objects.
+      1,                  // Run tests serially.
+      0,                  // Disable batching.
+      true,               // Use job objects.
+      base::DoNothing(),  // No special action on test timeout.
       base::BindOnce(&RunHelper, base::Unretained(&test_suite)));
   return rt;
 }
diff --git a/infra/config/generated/builders/ci/win32-updater-builder-dbg/properties.json b/infra/config/generated/builders/ci/win32-updater-builder-dbg/properties.json
index 78c43f5..ef2b07a 100644
--- a/infra/config/generated/builders/ci/win32-updater-builder-dbg/properties.json
+++ b/infra/config/generated/builders/ci/win32-updater-builder-dbg/properties.json
@@ -1,4 +1,42 @@
 {
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win32-updater-builder-dbg",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.updater",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "win"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "win32-updater-builder-dbg",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
   "$build/reclient": {
     "instance": "rbe-chromium-trusted",
     "jobs": 80,
diff --git a/infra/config/generated/builders/ci/win32-updater-builder-rel/properties.json b/infra/config/generated/builders/ci/win32-updater-builder-rel/properties.json
index 78c43f5..328ea7f6 100644
--- a/infra/config/generated/builders/ci/win32-updater-builder-rel/properties.json
+++ b/infra/config/generated/builders/ci/win32-updater-builder-rel/properties.json
@@ -1,4 +1,77 @@
 {
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win32-updater-builder-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.updater",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "win"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win7(32)-updater-tester-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.updater",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "win"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "win32-updater-builder-rel",
+                "project": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "win32-updater-builder-rel",
+          "project": "chromium"
+        }
+      ],
+      "builder_ids_in_scope_for_testing": [
+        {
+          "bucket": "ci",
+          "builder": "win7(32)-updater-tester-rel",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
   "$build/reclient": {
     "instance": "rbe-chromium-trusted",
     "jobs": 80,
diff --git "a/infra/config/generated/builders/ci/win7\05032\051-updater-tester-rel/properties.json" "b/infra/config/generated/builders/ci/win7\05032\051-updater-tester-rel/properties.json"
index 0d6f98c..ab01470 100644
--- "a/infra/config/generated/builders/ci/win7\05032\051-updater-tester-rel/properties.json"
+++ "b/infra/config/generated/builders/ci/win7\05032\051-updater-tester-rel/properties.json"
@@ -1,4 +1,70 @@
 {
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win32-updater-builder-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.updater",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "win"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win7(32)-updater-tester-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.updater",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "win"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "win32-updater-builder-rel",
+                "project": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "win7(32)-updater-tester-rel",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
     "grouping_keys": [
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 1db1c55..f50fff7 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -498,7 +498,7 @@
   * Experiment percentage: 3.0
 
 * [fuchsia-binary-size](https://ci.chromium.org/p/chromium/builders/try/fuchsia-binary-size) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-binary-size"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""fuchsia-binary-size""))
-  * Experiment percentage: 50.0
+  * Experiment percentage: 75.0
 
 * [linux-1mbu-compile-fyi-rel](https://ci.chromium.org/p/chromium/builders/try/linux-1mbu-compile-fyi-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-1mbu-compile-fyi-rel"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""linux-1mbu-compile-fyi-rel""))
   * Experiment percentage: 5.0
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index d49c8a9..f6c494d 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1871,7 +1871,7 @@
       }
       builders {
         name: "chromium/try/fuchsia-binary-size"
-        experiment_percentage: 50
+        experiment_percentage: 75
         location_regexp: ".*"
         location_regexp_exclude: ".+/[+]/docs/.+"
         location_regexp_exclude: ".+/[+]/infra/config/.+"
diff --git a/infra/config/generated/luci/project.cfg b/infra/config/generated/luci/project.cfg
index 4c731471..c23bdfa 100644
--- a/infra/config/generated/luci/project.cfg
+++ b/infra/config/generated/luci/project.cfg
@@ -7,7 +7,7 @@
 name: "chromium"
 access: "group:all"
 lucicfg {
-  version: "1.31.5"
+  version: "1.32.1"
   package_dir: "../.."
   config_dir: "generated/luci"
   entry_point: "main.star"
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 0c1bee7..cc387dc 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -189,6 +189,8 @@
     x14main = xcode_enum("14a5284g"),
     # A newer Xcode 14 version used on beta bots.
     x14betabots = xcode_enum("14a5284g"),
+    # Xcode14 beta 5 used on beta bots.
+    x14beta5bots = xcode_enum("14a5294e"),
     # in use by ios-webkit-tot
     x13wk = xcode_enum("13a1030dwk"),
 )
diff --git a/infra/config/subprojects/chromium/ci/chromium.updater.star b/infra/config/subprojects/chromium/ci/chromium.updater.star
index b5e5a54a..4f2a01f 100644
--- a/infra/config/subprojects/chromium/ci/chromium.updater.star
+++ b/infra/config/subprojects/chromium/ci/chromium.updater.star
@@ -409,6 +409,20 @@
 
 ci.builder(
     name = "win32-updater-builder-dbg",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.WIN,
+        ),
+    ),
     builderless = True,
     console_view_entry = consoles.console_view_entry(
         category = "debug|win (32)",
@@ -451,6 +465,20 @@
 
 ci.builder(
     name = "win32-updater-builder-rel",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.WIN,
+        ),
+    ),
     builderless = True,
     console_view_entry = consoles.console_view_entry(
         category = "release|win (32)",
@@ -488,6 +516,21 @@
 
 ci.thin_tester(
     name = "win7(32)-updater-tester-rel",
+    builder_spec = builder_config.builder_spec(
+        execution_mode = builder_config.execution_mode.TEST,
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.WIN,
+        ),
+    ),
     console_view_entry = consoles.console_view_entry(
         category = "release|win (32)",
         short_name = "7",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 18be405..f73d48f 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -61,7 +61,7 @@
         },
     },
     tryjob = try_.job(
-        experiment_percentage = 50,
+        experiment_percentage = 75,
     ),
 )
 
diff --git a/ios/chrome/browser/crash_report/crash_helper.mm b/ios/chrome/browser/crash_report/crash_helper.mm
index 1accf075..9ff4bd4 100644
--- a/ios/chrome/browser/crash_report/crash_helper.mm
+++ b/ios/chrome/browser/crash_report/crash_helper.mm
@@ -80,43 +80,6 @@
   crash_reporter::StartProcessingPendingReports();
 }
 
-// Callback for logging::SetLogMessageHandler
-bool FatalMessageHandler(int severity,
-                         const char* file,
-                         int line,
-                         size_t message_start,
-                         const std::string& str) {
-  // Do not handle non-FATAL.
-  if (severity != logging::LOG_FATAL)
-    return false;
-
-  // In case of OOM condition, this code could be reentered when
-  // constructing and storing the key.  Using a static is not
-  // thread-safe, but if multiple threads are in the process of a
-  // fatal crash at the same time, this should work.
-  static bool guarded = false;
-  if (guarded)
-    return false;
-
-  base::AutoReset<bool> guard(&guarded, true);
-
-  // Only log last path component.  This matches logging.cc.
-  if (file) {
-    const char* slash = strrchr(file, '/');
-    if (slash)
-      file = slash + 1;
-  }
-
-  NSString* fatal_value = [NSString
-      stringWithFormat:@"%s:%d: %s", file, line, str.c_str() + message_start];
-  static crash_reporter::CrashKeyString<2550> key("LOG_FATAL");
-  key.Set(base::SysNSStringToUTF8(fatal_value));
-
-  // Rather than including the code to force the crash here, allow the
-  // caller to do it.
-  return false;
-}
-
 // Called after Breakpad finishes uploading each report.
 void UploadResultHandler(NSString* report_id, NSError* error) {
   base::UmaHistogramSparse("CrashReport.BreakpadIOSUploadOutcome", error.code);
@@ -153,7 +116,6 @@
   // Notifying the PathService on the location of the crashes so that crashes
   // can be displayed to the user on the about:crashes page.  Use the app group
   // so crashes can be shared by plugins.
-  logging::SetLogMessageHandler(&FatalMessageHandler);
   if (common::CanUseCrashpad()) {
     base::PathService::Override(ios::DIR_CRASH_DUMPS,
                                 common::CrashpadDumpLocation());
diff --git a/ios/chrome/browser/promos_manager/promos_manager_scene_agent.mm b/ios/chrome/browser/promos_manager/promos_manager_scene_agent.mm
index 724e3ac5..d72b835 100644
--- a/ios/chrome/browser/promos_manager/promos_manager_scene_agent.mm
+++ b/ios/chrome/browser/promos_manager/promos_manager_scene_agent.mm
@@ -31,4 +31,64 @@
   [self.sceneState.appState addObserver:self];
 }
 
+#pragma mark - AppStateObserver
+
+- (void)appState:(AppState*)appState
+    didTransitionFromInitStage:(InitStage)previousInitStage {
+  // Monitor the app intialization stages to consider showing a promo at a point
+  // in the initialization of the app that allows it.
+  [self handlePromoDisplayIfUIAvailable];
+}
+
+#pragma mark - Private
+
+// Handle the display of a promo if the scene UI is available to display one.
+- (void)handlePromoDisplayIfUIAvailable {
+  if (![self isUIAvailableForPromo])
+    return;
+}
+
+// Returns YES if a promo can be displayed.
+- (BOOL)isUIAvailableForPromo {
+  // The following app & scene conditions need to be met to enable a promo's
+  // display (please note the Promos Manager may still decide *not* to display a
+  // promo, based on its own internal criteria):
+
+  // (1) The app initialization is over (the stage InitStageFinal is reached).
+  if (self.sceneState.appState.initStage < InitStageFinal)
+    return NO;
+
+  // (2) The scene is in the foreground.
+  if (self.sceneState.activationLevel < SceneActivationLevelForegroundActive)
+    return NO;
+
+  // (3) There is no UI blocker.
+  if (self.sceneState.appState.currentUIBlocker)
+    return NO;
+
+  // (4) The app isn't shutting down.
+  if (self.sceneState.appState.appIsTerminating)
+    return NO;
+
+  // (5) There are no launch intents (external intents).
+  if (self.sceneState.startupHadExternalIntent)
+    return NO;
+
+  // (6) The app isn't launching after a crash.
+  if (self.sceneState.appState.postCrashLaunch)
+    return NO;
+
+  // Additional, sensible checks to add to minimize user annoyance:
+
+  // (7) The user isn't currently signing in.
+  if (self.sceneState.signinInProgress)
+    return NO;
+
+  // (8) The user isn't currently looking at a modal overlay.
+  if (self.sceneState.presentingModalOverlay)
+    return NO;
+
+  return YES;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
index 657e381b..b6c296ae 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
@@ -239,7 +239,16 @@
   [self.formInputAccessoryViewController reset];
 
   self.formInputViewController = nil;
-  [GetFirstResponder() reloadInputViews];
+  if (@available(iOS 16, *)) {
+    @try {
+      [GetFirstResponder() reloadInputViews];
+    } @catch (NSException* e) {
+      // TODO(crbug.com/1334530) iOS 16 beta 5 is still throwing an
+      // NSInternalInconsistencyException.
+    }
+  } else {
+    [GetFirstResponder() reloadInputViews];
+  }
 }
 
 #pragma mark - Presenting Children
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/query_suggestion_view.mm b/ios/chrome/browser/ui/content_suggestions/cells/query_suggestion_view.mm
index 4df2e415..617d951 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/query_suggestion_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/query_suggestion_view.mm
@@ -28,11 +28,20 @@
 // Spacing between search ImageView and label.
 const CGFloat kQueryImageToLabelHorizontalSpacing = 9.5f;
 
+// Touchdown alpha for `queryLabel` when the user presses on this view.
+const CGFloat kTouchDownAlpha = 0.25f;
+
 }  // namespace
 
 @implementation QuerySuggestionConfig
 @end
 
+@interface QuerySuggestionView ()
+
+@property(nonatomic, strong) UILabel* queryLabel;
+
+@end
+
 @implementation QuerySuggestionView
 
 - (instancetype)initWithConfiguration:(QuerySuggestionConfig*)config {
@@ -44,26 +53,27 @@
         [[UIImageView alloc] initWithImage:searchImage];
     searchImageView.tintColor = [UIColor colorNamed:kGrey500Color];
     searchImageView.translatesAutoresizingMaskIntoConstraints = NO;
-    UILabel* queryLabel = [[UILabel alloc] init];
-    queryLabel.font =
+    self.queryLabel = [[UILabel alloc] init];
+    self.queryLabel.font =
         [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
-    queryLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
-    queryLabel.translatesAutoresizingMaskIntoConstraints = NO;
-    queryLabel.numberOfLines = 2;
-    queryLabel.adjustsFontForContentSizeCategory = YES;
+    self.queryLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
+    self.queryLabel.translatesAutoresizingMaskIntoConstraints = NO;
+    self.queryLabel.numberOfLines = 2;
+    self.queryLabel.adjustsFontForContentSizeCategory = YES;
+    //      self.userInteractionEnabled = YES;
 
     if (!config) {
       // If there is no config, then this is a placeholder tile.
-      queryLabel.backgroundColor = [UIColor colorNamed:kGrey100Color];
+      self.queryLabel.backgroundColor = [UIColor colorNamed:kGrey100Color];
     } else {
       _config = config;
-      queryLabel.text = config.query;
+      self.queryLabel.text = config.query;
       self.accessibilityLabel = config.query;
       self.isAccessibilityElement = YES;
     }
 
     [self addSubview:searchImageView];
-    [self addSubview:queryLabel];
+    [self addSubview:self.queryLabel];
 
     [NSLayoutConstraint activateConstraints:@[
       [searchImageView.widthAnchor
@@ -74,26 +84,29 @@
           constraintEqualToAnchor:self.centerYAnchor],
       [searchImageView.leadingAnchor
           constraintEqualToAnchor:self.leadingAnchor],
-      [queryLabel.leadingAnchor
+      [self.queryLabel.leadingAnchor
           constraintEqualToAnchor:searchImageView.trailingAnchor
                          constant:kQueryImageToLabelHorizontalSpacing],
-      [queryLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
+      [self.queryLabel.trailingAnchor
+          constraintEqualToAnchor:self.trailingAnchor],
       [self.heightAnchor constraintEqualToConstant:kViewHeightAnchor],
       [self.widthAnchor constraintEqualToConstant:kViewWidthAnchor]
     ]];
     if (config) {
       // Let label take up all vertical space for two-line query text.
       [NSLayoutConstraint activateConstraints:@[
-        [queryLabel.topAnchor constraintEqualToAnchor:self.topAnchor],
-        [queryLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]
+        [self.queryLabel.topAnchor constraintEqualToAnchor:self.topAnchor],
+        [self.queryLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]
       ]];
     } else {
       // Vertically center and set thin height to visualize text placeholder.
       [NSLayoutConstraint activateConstraints:@[
-        [queryLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
-        [queryLabel.heightAnchor
+        [self.queryLabel.centerYAnchor
+            constraintEqualToAnchor:self.centerYAnchor],
+        [self.queryLabel.heightAnchor
             constraintEqualToConstant:kQueryLabelHeightAnchor],
-        [queryLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
+        [self.queryLabel.trailingAnchor
+            constraintEqualToAnchor:self.trailingAnchor]
       ]];
     }
   }
@@ -117,4 +130,26 @@
   ]];
 }
 
+- (void)setHighlighted:(BOOL)highlighted {
+  if (highlighted) {
+    self.queryLabel.alpha = kTouchDownAlpha;
+  } else {
+    self.queryLabel.alpha = 1.0;
+  }
+}
+
+#pragma mark - UIResponder
+
+- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
+  [self setHighlighted:YES];
+}
+
+- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
+  [self setHighlighted:NO];
+}
+
+- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
+  [self setHighlighted:NO];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
index a19a5ac..59d12d8 100644
--- a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
@@ -7,10 +7,11 @@
 #import <UIKit/UIKit.h>
 
 #import "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
-#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
-#include "ios/chrome/browser/first_run/first_run_metrics.h"
-#include "ios/chrome/browser/main/browser.h"
+#import "base/notreached.h"
+#import "base/time/time.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/first_run/first_run_metrics.h"
+#import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/authentication/signin_sync/signin_sync_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.h"
 #import "ios/chrome/browser/ui/first_run/first_run_screen_delegate.h"
@@ -31,6 +32,7 @@
 @property(nonatomic, strong) ScreenProvider* screenProvider;
 @property(nonatomic, strong) ChromeCoordinator* childCoordinator;
 @property(nonatomic, strong) UINavigationController* navigationController;
+@property(nonatomic, strong) NSDate* firstScreenStartTime;
 
 // YES if First Run was completed.
 @property(nonatomic, assign) BOOL completed;
@@ -56,8 +58,10 @@
 
 - (void)start {
   [self presentScreen:[self.screenProvider nextScreenType]];
+  __weak FirstRunCoordinator* weakSelf = self;
   void (^completion)(void) = ^{
     base::UmaHistogramEnumeration("FirstRun.Stage", first_run::kStart);
+    weakSelf.firstScreenStartTime = [NSDate now];
   };
   [self.navigationController setNavigationBarHidden:YES animated:NO];
   [self.baseViewController presentViewController:self.navigationController
@@ -89,6 +93,16 @@
 - (void)willFinishPresenting {
   [self.childCoordinator stop];
   self.childCoordinator = nil;
+  // Usually, finishing presenting the first FRE screen signifies that the user
+  // has accepted Terms of Services. Therefore, we can use the time it takes the
+  // first screen to be visible as the time it takes a user to accept Terms of
+  // Services.
+  if (self.firstScreenStartTime) {
+    base::TimeDelta delta =
+        base::Time::Now() - base::Time::FromNSDate(self.firstScreenStartTime);
+    base::UmaHistogramTimes("FirstRun.TermsOfServicesPromoDisplayTime", delta);
+    self.firstScreenStartTime = nil;
+  }
   [self presentScreen:[self.screenProvider nextScreenType]];
 }
 
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 89659c6..bd450cec 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-5cf1d75a2b03ebb9a06b6e76159d419718996ed0
\ No newline at end of file
+d038ee2a702acdcb03af944fc3855f0baf0b4bfb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 7f87d175..acb386e 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-17d93acd84fb63dbf55cb62d4e80eef7d183b5d5
\ No newline at end of file
+2d882c96e7373339b3c443830dbf7afc14a27c02
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 2b5392f..a12422e 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-b34178ae86de8afead560cca5f6d7da70e8dc7bc
\ No newline at end of file
+274f2237441e67350cd3645cdc806700da82e2d3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index dc56049..404e844 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-4b9b6b84813c4432bf2cf05e32cb1b596d90cabd
\ No newline at end of file
+f74b5a21eb76e450f12ac5389f32886b03adba71
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index d63f24a..c5e6e03 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-4a44c24384ba8f05527e8f3e322d582ab849fe8f
\ No newline at end of file
+a05b2b224949b78985eb0e509af454e4891d91a7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 23e5dbd7..3d595693 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-1070c31a24a9f8fbc310f6389b934bb63211943c
\ No newline at end of file
+59e61b8a329049031108726014d7ebb90f7ae053
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 6d862aa..ec47853 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ef4a30fc78f0cdc66b419e314dc4a46a14cd3b94
\ No newline at end of file
+638aa4d3a18c64d0ce63fd2ad876eb6176d4abef
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 26e0f04..4d8e5bf7 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-07ccf0b6f2c46446284a7992209ec454f795492b
\ No newline at end of file
+38a1312be66b2d75fe1d5e5f20914bc381318475
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 06c4a59..caca5b34 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-308ecac6c58f15b3e9361e6df8496da6f15efd1a
\ No newline at end of file
+86e3bc4c89b56883a0f64c5cc8995202b1e949a9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 06c7f53..4a532263 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-c7a35e0e1d93f97fee869c59a67440aad36be4b5
\ No newline at end of file
+86aba0a111e6abd7981dd5fc24ba1be3a5ca9dfb
\ No newline at end of file
diff --git a/media/gpu/chromeos/gl_image_processor_backend.cc b/media/gpu/chromeos/gl_image_processor_backend.cc
index 9e8a6f0..bfc6816 100644
--- a/media/gpu/chromeos/gl_image_processor_backend.cc
+++ b/media/gpu/chromeos/gl_image_processor_backend.cc
@@ -19,6 +19,7 @@
 #include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_image_native_pixmap.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/init/gl_factory.h"
 
 namespace media {
@@ -213,7 +214,8 @@
   // Create a driver-level GL context just for us. This is questionable because
   // work in this context will be competing with the context(s) used for
   // rasterization and compositing. However, it's a simple starting point.
-  gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+  gl_surface_ =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
   if (!gl_surface_) {
     LOG(ERROR) << "Could not create the offscreen EGL surface";
     return false;
diff --git a/media/gpu/v4l2/test/av1_decoder.cc b/media/gpu/v4l2/test/av1_decoder.cc
index cb3a1063..2bef8ce 100644
--- a/media/gpu/v4l2/test/av1_decoder.cc
+++ b/media/gpu/v4l2/test/av1_decoder.cc
@@ -116,172 +116,6 @@
   v4l2_seq_params->max_frame_height_minus_1 = seq_header->max_frame_height - 1;
 }
 
-// 5.9.2. Uncompressed header syntax
-void FillFrameParams(struct v4l2_ctrl_av1_frame_header* v4l2_frame_params,
-                     const libgav1::ObuFrameHeader& frm_header) {
-  conditionally_set_u32_flags(&v4l2_frame_params->flags, frm_header.show_frame,
-                              V4L2_AV1_FRAME_HEADER_FLAG_SHOW_FRAME);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.showable_frame,
-                              V4L2_AV1_FRAME_HEADER_FLAG_SHOWABLE_FRAME);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.error_resilient_mode,
-                              V4L2_AV1_FRAME_HEADER_FLAG_ERROR_RESILIENT_MODE);
-  // libgav1 header has |enable_cdf_update| instead of |disable_cdf_update|.
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              !frm_header.enable_cdf_update,
-                              V4L2_AV1_FRAME_HEADER_FLAG_DISABLE_CDF_UPDATE);
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, frm_header.allow_screen_content_tools,
-      V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_SCREEN_CONTENT_TOOLS);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.force_integer_mv,
-                              V4L2_AV1_FRAME_HEADER_FLAG_FORCE_INTEGER_MV);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.allow_intrabc,
-                              V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_INTRABC);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.use_superres,
-                              V4L2_AV1_FRAME_HEADER_FLAG_USE_SUPERRES);
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, frm_header.allow_high_precision_mv,
-      V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_HIGH_PRECISION_MV);
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, frm_header.is_motion_mode_switchable,
-      V4L2_AV1_FRAME_HEADER_FLAG_IS_MOTION_MODE_SWITCHABLE);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.use_ref_frame_mvs,
-                              V4L2_AV1_FRAME_HEADER_FLAG_USE_REF_FRAME_MVS);
-  // libgav1 header has |enable_frame_end_update_cdf| instead.
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, !frm_header.enable_frame_end_update_cdf,
-      V4L2_AV1_FRAME_HEADER_FLAG_DISABLE_FRAME_END_UPDATE_CDF);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.tile_info.uniform_spacing,
-                              V4L2_AV1_FRAME_HEADER_FLAG_UNIFORM_TILE_SPACING);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.allow_warped_motion,
-                              V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_WARPED_MOTION);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.reference_mode_select,
-                              V4L2_AV1_FRAME_HEADER_FLAG_REFERENCE_SELECT);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.reduced_tx_set,
-                              V4L2_AV1_FRAME_HEADER_FLAG_REDUCED_TX_SET);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.skip_mode_frame[0] > 0,
-                              V4L2_AV1_FRAME_HEADER_FLAG_SKIP_MODE_ALLOWED);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.skip_mode_present,
-                              V4L2_AV1_FRAME_HEADER_FLAG_SKIP_MODE_PRESENT);
-  conditionally_set_u32_flags(&v4l2_frame_params->flags,
-                              frm_header.frame_size_override_flag,
-                              V4L2_AV1_FRAME_HEADER_FLAG_FRAME_SIZE_OVERRIDE);
-  // libgav1 header doesn't have |buffer_removal_time_present_flag|.
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, frm_header.buffer_removal_time[0] > 0,
-      V4L2_AV1_FRAME_HEADER_FLAG_BUFFER_REMOVAL_TIME_PRESENT);
-  conditionally_set_u32_flags(
-      &v4l2_frame_params->flags, frm_header.frame_refs_short_signaling,
-      V4L2_AV1_FRAME_HEADER_FLAG_FRAME_REFS_SHORT_SIGNALING);
-
-  switch (frm_header.frame_type) {
-    case libgav1::kFrameKey:
-      v4l2_frame_params->frame_type = V4L2_AV1_KEY_FRAME;
-      break;
-    case libgav1::kFrameInter:
-      v4l2_frame_params->frame_type = V4L2_AV1_INTER_FRAME;
-      break;
-    case libgav1::kFrameIntraOnly:
-      v4l2_frame_params->frame_type = V4L2_AV1_INTRA_ONLY_FRAME;
-      break;
-    case libgav1::kFrameSwitch:
-      v4l2_frame_params->frame_type = V4L2_AV1_SWITCH_FRAME;
-      break;
-    default:
-      NOTREACHED() << "Invalid frame type, " << frm_header.frame_type;
-  }
-
-  v4l2_frame_params->order_hint = frm_header.order_hint;
-  v4l2_frame_params->superres_denom = frm_header.superres_scale_denominator;
-  v4l2_frame_params->upscaled_width = frm_header.upscaled_width;
-
-  switch (frm_header.interpolation_filter) {
-    case libgav1::kInterpolationFilterEightTap:
-      v4l2_frame_params->interpolation_filter =
-          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP;
-      break;
-    case libgav1::kInterpolationFilterEightTapSmooth:
-      v4l2_frame_params->interpolation_filter =
-          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH;
-      break;
-    case libgav1::kInterpolationFilterEightTapSharp:
-      v4l2_frame_params->interpolation_filter =
-          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP;
-      break;
-    case libgav1::kInterpolationFilterBilinear:
-      v4l2_frame_params->interpolation_filter =
-          V4L2_AV1_INTERPOLATION_FILTER_BILINEAR;
-      break;
-    case libgav1::kInterpolationFilterSwitchable:
-      v4l2_frame_params->interpolation_filter =
-          V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE;
-      break;
-    default:
-      NOTREACHED() << "Invalid interpolation filter, "
-                   << frm_header.interpolation_filter;
-  }
-
-  switch (frm_header.tx_mode) {
-    case libgav1::kTxModeOnly4x4:
-      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_ONLY_4X4;
-      break;
-    case libgav1::kTxModeLargest:
-      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_LARGEST;
-      break;
-    case libgav1::kTxModeSelect:
-      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_SELECT;
-      break;
-    default:
-      NOTREACHED() << "Invalid tx mode, " << frm_header.tx_mode;
-  }
-
-  v4l2_frame_params->frame_width_minus_1 = frm_header.width - 1;
-  v4l2_frame_params->frame_height_minus_1 = frm_header.height - 1;
-  v4l2_frame_params->render_width_minus_1 = frm_header.render_width - 1;
-  v4l2_frame_params->render_height_minus_1 = frm_header.render_height - 1;
-
-  v4l2_frame_params->current_frame_id = frm_header.current_frame_id;
-  v4l2_frame_params->primary_ref_frame = frm_header.primary_reference_frame;
-  SafeArrayMemcpy(v4l2_frame_params->buffer_removal_time,
-                  frm_header.buffer_removal_time);
-  v4l2_frame_params->refresh_frame_flags = frm_header.refresh_frame_flags;
-
-  static_assert(std::size(decltype(v4l2_frame_params->order_hints){}) ==
-                    libgav1::kNumReferenceFrameTypes,
-                "Invalid size of |order_hints| array");
-  for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; i++)
-    v4l2_frame_params->order_hints[i] =
-        base::checked_cast<__u32>(frm_header.reference_order_hint[i]);
-
-  v4l2_frame_params->last_frame_idx =
-      frm_header.reference_frame_index[libgav1::kReferenceFrameLast];
-  v4l2_frame_params->gold_frame_idx =
-      frm_header.reference_frame_index[libgav1::kReferenceFrameGolden];
-
-  static_assert(std::size(decltype(v4l2_frame_params->ref_frame_idx){}) ==
-                    libgav1::kNumInterReferenceFrameTypes,
-                "Invalid size of |ref_frame_idx| array");
-  for (size_t i = 0; i < libgav1::kNumInterReferenceFrameTypes; i++)
-    v4l2_frame_params->ref_frame_idx[i] =
-        base::checked_cast<__u64>(frm_header.reference_frame_index[i]);
-
-  v4l2_frame_params->skip_mode_frame[0] =
-      base::checked_cast<__u8>(frm_header.skip_mode_frame[0]);
-  v4l2_frame_params->skip_mode_frame[1] =
-      base::checked_cast<__u8>(frm_header.skip_mode_frame[1]);
-}
-
 // Section 5.9.11. Loop filter params syntax.
 // Note that |update_ref_delta| and |update_mode_delta| flags in the spec
 // are not needed for V4L2 AV1 API.
@@ -603,6 +437,200 @@
   }
 }
 
+// 5.9.2. Uncompressed header syntax
+void FillFrameParams(
+    struct v4l2_ctrl_av1_frame_header* v4l2_frame_params,
+    const absl::optional<libgav1::ObuSequenceHeader>& seq_header,
+    const libgav1::ObuFrameHeader& frm_header) {
+  FillLoopFilterParams(&v4l2_frame_params->loop_filter, frm_header.loop_filter);
+
+  FillLoopFilterDeltaParams(&v4l2_frame_params->loop_filter,
+                            frm_header.delta_lf);
+
+  FillQuantizationParams(&v4l2_frame_params->quantization,
+                         frm_header.quantizer);
+
+  FillQuantizerIndexDeltaParams(&v4l2_frame_params->quantization, seq_header,
+                                frm_header);
+
+  FillSegmentationParams(&v4l2_frame_params->segmentation,
+                         frm_header.segmentation);
+
+  const auto color_bitdepth = seq_header->color_config.bitdepth;
+  FillCdefParams(&v4l2_frame_params->cdef, frm_header.cdef,
+                 base::strict_cast<int8_t>(color_bitdepth));
+
+  FillLoopRestorationParams(&v4l2_frame_params->loop_restoration,
+                            frm_header.loop_restoration);
+
+  FillTileInfo(&v4l2_frame_params->tile_info, frm_header.tile_info);
+
+  FillGlobalMotionParams(&v4l2_frame_params->global_motion,
+                         frm_header.global_motion);
+
+  conditionally_set_u32_flags(&v4l2_frame_params->flags, frm_header.show_frame,
+                              V4L2_AV1_FRAME_HEADER_FLAG_SHOW_FRAME);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.showable_frame,
+                              V4L2_AV1_FRAME_HEADER_FLAG_SHOWABLE_FRAME);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.error_resilient_mode,
+                              V4L2_AV1_FRAME_HEADER_FLAG_ERROR_RESILIENT_MODE);
+  // libgav1 header has |enable_cdf_update| instead of |disable_cdf_update|.
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              !frm_header.enable_cdf_update,
+                              V4L2_AV1_FRAME_HEADER_FLAG_DISABLE_CDF_UPDATE);
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, frm_header.allow_screen_content_tools,
+      V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_SCREEN_CONTENT_TOOLS);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.force_integer_mv,
+                              V4L2_AV1_FRAME_HEADER_FLAG_FORCE_INTEGER_MV);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.allow_intrabc,
+                              V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_INTRABC);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.use_superres,
+                              V4L2_AV1_FRAME_HEADER_FLAG_USE_SUPERRES);
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, frm_header.allow_high_precision_mv,
+      V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_HIGH_PRECISION_MV);
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, frm_header.is_motion_mode_switchable,
+      V4L2_AV1_FRAME_HEADER_FLAG_IS_MOTION_MODE_SWITCHABLE);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.use_ref_frame_mvs,
+                              V4L2_AV1_FRAME_HEADER_FLAG_USE_REF_FRAME_MVS);
+  // libgav1 header has |enable_frame_end_update_cdf| instead.
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, !frm_header.enable_frame_end_update_cdf,
+      V4L2_AV1_FRAME_HEADER_FLAG_DISABLE_FRAME_END_UPDATE_CDF);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.tile_info.uniform_spacing,
+                              V4L2_AV1_FRAME_HEADER_FLAG_UNIFORM_TILE_SPACING);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.allow_warped_motion,
+                              V4L2_AV1_FRAME_HEADER_FLAG_ALLOW_WARPED_MOTION);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.reference_mode_select,
+                              V4L2_AV1_FRAME_HEADER_FLAG_REFERENCE_SELECT);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.reduced_tx_set,
+                              V4L2_AV1_FRAME_HEADER_FLAG_REDUCED_TX_SET);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.skip_mode_frame[0] > 0,
+                              V4L2_AV1_FRAME_HEADER_FLAG_SKIP_MODE_ALLOWED);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.skip_mode_present,
+                              V4L2_AV1_FRAME_HEADER_FLAG_SKIP_MODE_PRESENT);
+  conditionally_set_u32_flags(&v4l2_frame_params->flags,
+                              frm_header.frame_size_override_flag,
+                              V4L2_AV1_FRAME_HEADER_FLAG_FRAME_SIZE_OVERRIDE);
+  // libgav1 header doesn't have |buffer_removal_time_present_flag|.
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, frm_header.buffer_removal_time[0] > 0,
+      V4L2_AV1_FRAME_HEADER_FLAG_BUFFER_REMOVAL_TIME_PRESENT);
+  conditionally_set_u32_flags(
+      &v4l2_frame_params->flags, frm_header.frame_refs_short_signaling,
+      V4L2_AV1_FRAME_HEADER_FLAG_FRAME_REFS_SHORT_SIGNALING);
+
+  switch (frm_header.frame_type) {
+    case libgav1::kFrameKey:
+      v4l2_frame_params->frame_type = V4L2_AV1_KEY_FRAME;
+      break;
+    case libgav1::kFrameInter:
+      v4l2_frame_params->frame_type = V4L2_AV1_INTER_FRAME;
+      break;
+    case libgav1::kFrameIntraOnly:
+      v4l2_frame_params->frame_type = V4L2_AV1_INTRA_ONLY_FRAME;
+      break;
+    case libgav1::kFrameSwitch:
+      v4l2_frame_params->frame_type = V4L2_AV1_SWITCH_FRAME;
+      break;
+    default:
+      NOTREACHED() << "Invalid frame type, " << frm_header.frame_type;
+  }
+
+  v4l2_frame_params->order_hint = frm_header.order_hint;
+  v4l2_frame_params->superres_denom = frm_header.superres_scale_denominator;
+  v4l2_frame_params->upscaled_width = frm_header.upscaled_width;
+
+  switch (frm_header.interpolation_filter) {
+    case libgav1::kInterpolationFilterEightTap:
+      v4l2_frame_params->interpolation_filter =
+          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP;
+      break;
+    case libgav1::kInterpolationFilterEightTapSmooth:
+      v4l2_frame_params->interpolation_filter =
+          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH;
+      break;
+    case libgav1::kInterpolationFilterEightTapSharp:
+      v4l2_frame_params->interpolation_filter =
+          V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP;
+      break;
+    case libgav1::kInterpolationFilterBilinear:
+      v4l2_frame_params->interpolation_filter =
+          V4L2_AV1_INTERPOLATION_FILTER_BILINEAR;
+      break;
+    case libgav1::kInterpolationFilterSwitchable:
+      v4l2_frame_params->interpolation_filter =
+          V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE;
+      break;
+    default:
+      NOTREACHED() << "Invalid interpolation filter, "
+                   << frm_header.interpolation_filter;
+  }
+
+  switch (frm_header.tx_mode) {
+    case libgav1::kTxModeOnly4x4:
+      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_ONLY_4X4;
+      break;
+    case libgav1::kTxModeLargest:
+      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_LARGEST;
+      break;
+    case libgav1::kTxModeSelect:
+      v4l2_frame_params->tx_mode = V4L2_AV1_TX_MODE_SELECT;
+      break;
+    default:
+      NOTREACHED() << "Invalid tx mode, " << frm_header.tx_mode;
+  }
+
+  v4l2_frame_params->frame_width_minus_1 = frm_header.width - 1;
+  v4l2_frame_params->frame_height_minus_1 = frm_header.height - 1;
+  v4l2_frame_params->render_width_minus_1 = frm_header.render_width - 1;
+  v4l2_frame_params->render_height_minus_1 = frm_header.render_height - 1;
+
+  v4l2_frame_params->current_frame_id = frm_header.current_frame_id;
+  v4l2_frame_params->primary_ref_frame = frm_header.primary_reference_frame;
+  SafeArrayMemcpy(v4l2_frame_params->buffer_removal_time,
+                  frm_header.buffer_removal_time);
+  v4l2_frame_params->refresh_frame_flags = frm_header.refresh_frame_flags;
+
+  static_assert(std::size(decltype(v4l2_frame_params->order_hints){}) ==
+                    libgav1::kNumReferenceFrameTypes,
+                "Invalid size of |order_hints| array");
+  for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; i++)
+    v4l2_frame_params->order_hints[i] =
+        base::checked_cast<__u32>(frm_header.reference_order_hint[i]);
+
+  v4l2_frame_params->last_frame_idx =
+      frm_header.reference_frame_index[libgav1::kReferenceFrameLast];
+  v4l2_frame_params->gold_frame_idx =
+      frm_header.reference_frame_index[libgav1::kReferenceFrameGolden];
+
+  static_assert(std::size(decltype(v4l2_frame_params->ref_frame_idx){}) ==
+                    libgav1::kNumInterReferenceFrameTypes,
+                "Invalid size of |ref_frame_idx| array");
+  for (size_t i = 0; i < libgav1::kNumInterReferenceFrameTypes; i++)
+    v4l2_frame_params->ref_frame_idx[i] =
+        base::checked_cast<__u64>(frm_header.reference_frame_index[i]);
+
+  v4l2_frame_params->skip_mode_frame[0] =
+      base::checked_cast<__u8>(frm_header.skip_mode_frame[0]);
+  v4l2_frame_params->skip_mode_frame[1] =
+      base::checked_cast<__u8>(frm_header.skip_mode_frame[1]);
+}
+
 // Section 5.11. Tile Group OBU syntax
 void FillTileGroupParams(
     v4l2_ctrl_av1_tile_group* tile_group_params,
@@ -921,34 +949,8 @@
 
   struct v4l2_ctrl_av1_frame_header v4l2_frame_params = {};
 
-  FillLoopFilterParams(&v4l2_frame_params.loop_filter,
-                       current_frame_header.loop_filter);
-
-  FillLoopFilterDeltaParams(&v4l2_frame_params.loop_filter,
-                            current_frame_header.delta_lf);
-
-  FillQuantizationParams(&v4l2_frame_params.quantization,
-                         current_frame_header.quantizer);
-
-  FillQuantizerIndexDeltaParams(&v4l2_frame_params.quantization,
-                                current_sequence_header_, current_frame_header);
-
-  FillSegmentationParams(&v4l2_frame_params.segmentation,
-                         current_frame_header.segmentation);
-
-  const auto color_bitdepth = current_sequence_header_->color_config.bitdepth;
-  FillCdefParams(&v4l2_frame_params.cdef, current_frame_header.cdef,
-                 base::strict_cast<int8_t>(color_bitdepth));
-
-  FillLoopRestorationParams(&v4l2_frame_params.loop_restoration,
-                            current_frame_header.loop_restoration);
-
-  FillTileInfo(&v4l2_frame_params.tile_info, current_frame_header.tile_info);
-
-  FillGlobalMotionParams(&v4l2_frame_params.global_motion,
-                         current_frame_header.global_motion);
-
-  FillFrameParams(&v4l2_frame_params, current_frame_header);
+  FillFrameParams(&v4l2_frame_params, current_sequence_header_,
+                  current_frame_header);
 
   // TODO(stevecho): V4L2_CID_STATELESS_AV1_FRAME_HEADER is trending to be
   // changed to V4L2_CID_STATELESS_AV1_FRAME
diff --git a/media/gpu/windows/d3d11_texture_wrapper_unittest.cc b/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
index 93c4b0b..4a7cfb1 100644
--- a/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
+++ b/media/gpu/windows/d3d11_texture_wrapper_unittest.cc
@@ -46,7 +46,7 @@
 
     display_ = gl::GLSurfaceTestSupport::InitializeOneOffImplementation(
         gl::GLImplementationParts(gl::ANGLEImplementation::kD3D11), false);
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(display_, gfx::Size());
     share_group_ = new gl::GLShareGroup();
     context_ = gl::init::CreateGLContext(share_group_.get(), surface_.get(),
                                          gl::GLContextAttribs());
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index abcdff84..485515a 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -145,6 +145,9 @@
   // from |impl_| will be cancelled by |weak_factory_| when we return.
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Log whatever usage we measured, if any.
+  LogPictureBufferUsage();
+
   impl_.Reset();
 
   // Explicitly destroy the decoder, since it can reference picture buffers.
@@ -544,6 +547,17 @@
     return;
   }
 
+  // Periodically measure picture buffer usage.  We could do this on every free,
+  // but it's not that important that we should run it so often.
+  if (picture_buffers_.size() > 0) {
+    if (!decode_count_until_picture_buffer_measurement_) {
+      MeasurePictureBufferUsage();
+      decode_count_until_picture_buffer_measurement_ = picture_buffers_.size();
+    } else {
+      decode_count_until_picture_buffer_measurement_--;
+    }
+  }
+
   if (!current_buffer_) {
     if (input_buffer_queue_.empty()) {
       return;
@@ -737,6 +751,10 @@
     display_metadata = hdr_metadata_helper.GetDisplayMetadata();
   }
 
+  // Since we are about to allocate new picture buffers, record whatever usage
+  // we had for the outgoing ones, if any.
+  LogPictureBufferUsage();
+
   // Drop any old pictures.
   for (auto& buffer : picture_buffers_)
     DCHECK(!buffer->in_picture_use());
@@ -936,6 +954,43 @@
   input_buffer_queue_.clear();
 }
 
+void D3D11VideoDecoder::MeasurePictureBufferUsage() {
+  // Count the total number of buffers that are currently unused by either the
+  // client or the decoder.  These are buffers that we didn't need to allocate.
+  int unused_buffers = 0;
+  for (const auto& buffer : picture_buffers_) {
+    if (!buffer->in_client_use() && !buffer->in_picture_use())
+      unused_buffers++;
+  }
+
+  if (!min_unused_buffers_ || unused_buffers < *min_unused_buffers_) {
+    min_unused_buffers_ = unused_buffers;
+  }
+}
+
+void D3D11VideoDecoder::LogPictureBufferUsage() {
+  if (!min_unused_buffers_)
+    return;
+
+  // Record these separately because (a) we could potentially fix the
+  // MultiTexture case pretty easily, and (b) we have no idea how often we're in
+  // one mode vs the other.  This will let us know if there is enough usage of
+  // MultiTexture and also enough unused textures that it's worth fixing.  Note
+  // that this assumes that we would lazily allocate buffers but not free them,
+  // and is a lower bound on savings.
+  if (use_single_video_decoder_texture_) {
+    UMA_HISTOGRAM_COUNTS_100(
+        "Media.D3D11VideoDecoder.UnusedPictureBufferCount.SingleTexture",
+        *min_unused_buffers_);
+  } else {
+    UMA_HISTOGRAM_COUNTS_100(
+        "Media.D3D11VideoDecoder.UnusedPictureBufferCount.MultiTexture",
+        *min_unused_buffers_);
+  }
+
+  min_unused_buffers_.reset();
+}
+
 // static
 bool D3D11VideoDecoder::GetD3D11FeatureLevel(
     ComD3D11Device dev,
diff --git a/media/gpu/windows/d3d11_video_decoder.h b/media/gpu/windows/d3d11_video_decoder.h
index c838c9a..f774971 100644
--- a/media/gpu/windows/d3d11_video_decoder.h
+++ b/media/gpu/windows/d3d11_video_decoder.h
@@ -150,6 +150,15 @@
   // gpu main thread.
   void CreatePictureBuffers();
 
+  // Measure the number of picture buffers that are currently unused, and see if
+  // it's smaller than the minimum we've seen since we've allocated them.
+  void MeasurePictureBufferUsage();
+
+  // Log the current measurement of picture buffer usage, as measured by
+  // `MeasurePictureBufferUsage()`, and clear the measurement.  Do nothing,
+  // successfully, if no measurement has been made.
+  void LogPictureBufferUsage();
+
   // Create a D3D11VideoDecoder, if possible, based on the current config.
   D3D11Status::Or<ComD3D11VideoDecoder> CreateD3D11Decoder();
 
@@ -317,6 +326,17 @@
   // this changes we need to recreate the decoder.
   VideoChromaSampling chroma_sampling_ = VideoChromaSampling::k420;
 
+  // If set, this is the minimum number of picture buffers that we've seen
+  // since the last time it was logged to UMA that are unused by both the
+  // client and the decoder.  If unset, then no measurement has been made.
+  absl::optional<int> min_unused_buffers_;
+
+  // Picture buffer usage is measured periodically after some number of decodes.
+  // This tracks how many until the next measurement.  It's used strictly to
+  // rate limit the measurements, so we don't spent too much time counting
+  // unused picture buffers.
+  int decode_count_until_picture_buffer_measurement_ = 0;
+
   base::WeakPtrFactory<D3D11VideoDecoder> weak_factory_{this};
 };
 
diff --git a/net/data/ssl/root_stores/update_root_stores.py b/net/data/ssl/root_stores/update_root_stores.py
index 8aca424..f9f58d9 100755
--- a/net/data/ssl/root_stores/update_root_stores.py
+++ b/net/data/ssl/root_stores/update_root_stores.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -22,7 +22,7 @@
 ROOT_CERT_LIST_PATH = 'net/cert/root_cert_list_generated.h'
 ROOT_STORE_FILE_PATH = 'net/data/ssl/root_stores/root_stores.json'
 
-LICENSE_AND_HEADER = """\
+LICENSE_AND_HEADER = b"""\
 // Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -57,7 +57,7 @@
 } kRootCerts[] = {
 """
 
-FOOTER = """\
+FOOTER = b"""\
 
 };
 
@@ -93,8 +93,9 @@
       cpp_str = ''.join('0x{:02X}, '.format(x) for x in bytearray.fromhex(spki))
       log_id = int(data['id'])
       legacy = 'legacy' in data and data['legacy']
-      header_file.write('{ { %s },\n%d, %s }, ' %
-                        (cpp_str, log_id, "true" if legacy else "false"))
+      header_file.write(
+          ('{ { %s },\n%d, %s }, ' %
+           (cpp_str, log_id, "true" if legacy else "false")).encode('utf-8'))
 
     header_file.write(FOOTER)
 
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index 93e944c..e13d8bb 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -312,7 +312,7 @@
   virtual void RotateCounterclockwise() = 0;
   virtual bool IsReadOnly() const = 0;
   virtual void SetReadOnly(bool enable) = 0;
-  virtual void SetTwoUpView(bool enable) = 0;
+  virtual void SetDocumentLayout(DocumentLayout::PageSpread page_spread) = 0;
   virtual void DisplayAnnotations(bool display) = 0;
 
   // Applies the document layout options proposed by a call to
diff --git a/pdf/pdf_view_plugin_base.cc b/pdf/pdf_view_plugin_base.cc
index 5b5e106..dd4c5f5 100644
--- a/pdf/pdf_view_plugin_base.cc
+++ b/pdf/pdf_view_plugin_base.cc
@@ -47,7 +47,6 @@
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/input/web_mouse_event.h"
 #include "third_party/blink/public/common/input/web_touch_event.h"
-#include "third_party/blink/public/web/web_print_preset_options.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
@@ -176,19 +175,6 @@
   SendMessage(std::move(message));
 }
 
-void PdfViewPluginBase::Print() {
-  if (!engine())
-    return;
-
-  const bool can_print =
-      engine()->HasPermission(DocumentPermission::kPrintLowQuality) ||
-      engine()->HasPermission(DocumentPermission::kPrintHighQuality);
-  if (!can_print)
-    return;
-
-  InvokePrintDialog();
-}
-
 void PdfViewPluginBase::DocumentLoadComplete() {
   DCHECK_EQ(DocumentLoadState::kLoading, document_load_state_);
   document_load_state_ = DocumentLoadState::kComplete;
@@ -203,9 +189,6 @@
 
   OnDocumentLoadComplete();
 
-  if (accessibility_state_ == AccessibilityState::kPending)
-    LoadAccessibility();
-
   if (!full_frame())
     return;
 
@@ -329,12 +312,6 @@
   SendMessage(std::move(message));
 }
 
-void PdfViewPluginBase::SendPrintPreviewLoadedNotification() {
-  base::Value::Dict message;
-  message.Set("type", "printPreviewLoaded");
-  SendMessage(std::move(message));
-}
-
 void PdfViewPluginBase::OnPaint(const std::vector<gfx::Rect>& paint_rects,
                                 std::vector<PaintReadyRect>& ready,
                                 std::vector<gfx::Rect>& pending) {
@@ -342,22 +319,6 @@
   DoPaint(paint_rects, ready, pending);
 }
 
-void PdfViewPluginBase::EnableAccessibility() {
-  if (accessibility_state_ == AccessibilityState::kLoaded)
-    return;
-
-  if (accessibility_state_ == AccessibilityState::kOff)
-    accessibility_state_ = AccessibilityState::kPending;
-
-  if (document_load_state_ == DocumentLoadState::kComplete)
-    LoadAccessibility();
-}
-
-void PdfViewPluginBase::HandleAccessibilityAction(
-    const AccessibilityActionData& action_data) {
-  engine()->HandleAccessibilityAction(action_data);
-}
-
 int PdfViewPluginBase::GetContentRestrictions() const {
   int content_restrictions = kContentRestrictionCut | kContentRestrictionPaste;
   if (!engine()->HasPermission(DocumentPermission::kCopy))
@@ -397,47 +358,6 @@
     PrepareAndSetAccessibilityViewportInfo();
 }
 
-blink::WebPrintPresetOptions PdfViewPluginBase::GetPrintPresetOptions() {
-  blink::WebPrintPresetOptions options;
-  options.is_scaling_disabled = !engine()->GetPrintScaling();
-  options.copies = engine()->GetCopiesToPrint();
-  options.duplex_mode = engine()->GetDuplexMode();
-  options.uniform_page_size = engine()->GetUniformPageSizePoints();
-  return options;
-}
-
-int PdfViewPluginBase::PrintBegin(const blink::WebPrintParams& print_params) {
-  // The returned value is always equal to the number of pages in the PDF
-  // document irrespective of the printable area.
-  int32_t ret = engine()->GetNumberOfPages();
-  if (!ret)
-    return 0;
-
-  if (!engine()->HasPermission(DocumentPermission::kPrintLowQuality))
-    return 0;
-
-  print_params_ = print_params;
-  if (!engine()->HasPermission(DocumentPermission::kPrintHighQuality))
-    print_params_->rasterize_pdf = true;
-
-  engine()->PrintBegin();
-  return ret;
-}
-
-std::vector<uint8_t> PdfViewPluginBase::PrintPages(
-    const std::vector<int>& page_numbers) {
-  print_pages_called_ = true;
-  return engine()->PrintPages(page_numbers, print_params_.value());
-}
-
-void PdfViewPluginBase::PrintEnd() {
-  if (print_pages_called_)
-    UserMetricsRecordAction("PDF.PrintPage");
-  print_pages_called_ = false;
-  print_params_.reset();
-  engine()->PrintEnd();
-}
-
 void PdfViewPluginBase::RecalculateAreas(double old_zoom,
                                          float old_device_scale) {
   if (zoom_ != old_zoom || device_scale() != old_device_scale)
@@ -515,20 +435,6 @@
       std::ceil(document_size_.height() * zoom() * device_scale()));
 }
 
-void PdfViewPluginBase::SetCaretPosition(const gfx::PointF& position) {
-  engine()->SetCaretPosition(FrameToPdfCoordinates(position));
-}
-
-void PdfViewPluginBase::MoveRangeSelectionExtent(const gfx::PointF& extent) {
-  engine()->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
-}
-
-void PdfViewPluginBase::SetSelectionBounds(const gfx::PointF& base,
-                                           const gfx::PointF& extent) {
-  engine()->SetSelectionBounds(FrameToPdfCoordinates(base),
-                               FrameToPdfCoordinates(extent));
-}
-
 void PdfViewPluginBase::PrepareAndSetAccessibilityPageInfo(int32_t page_index) {
   // Outdated calls are ignored.
   if (page_index != next_accessibility_page_index_)
diff --git a/pdf/pdf_view_plugin_base.h b/pdf/pdf_view_plugin_base.h
index 15ae067..62f3314 100644
--- a/pdf/pdf_view_plugin_base.h
+++ b/pdf/pdf_view_plugin_base.h
@@ -28,7 +28,6 @@
 
 namespace blink {
 class WebInputEvent;
-struct WebPrintPresetOptions;
 }  // namespace blink
 
 namespace gfx {
@@ -41,7 +40,6 @@
 
 class PDFiumEngine;
 class PaintReadyRect;
-struct AccessibilityActionData;
 struct AccessibilityCharInfo;
 struct AccessibilityDocInfo;
 struct AccessibilityPageInfo;
@@ -91,7 +89,6 @@
              const std::string& bcc,
              const std::string& subject,
              const std::string& body) override;
-  void Print() override;
   void DocumentLoadComplete() override;
   void DocumentLoadFailed() override;
   void DocumentLoadProgress(uint32_t available, uint32_t doc_size) override;
@@ -105,12 +102,6 @@
                std::vector<PaintReadyRect>& ready,
                std::vector<gfx::Rect>& pending) override;
 
-  // Enable accessibility for PDF plugin.
-  void EnableAccessibility();
-
-  // Handle invoked accessibility actions.
-  void HandleAccessibilityAction(const AccessibilityActionData& action_data);
-
   // Gets the content restrictions based on the permissions which `engine_` has.
   int GetContentRestrictions() const;
 
@@ -159,9 +150,6 @@
   // -1 for loading error.
   void SendLoadingProgress(double percentage);
 
-  // Send a notification that the print preview has loaded.
-  void SendPrintPreviewLoadedNotification();
-
   // Schedules invalidation tasks after painting finishes.
   void InvalidateAfterPaintDone();
 
@@ -182,11 +170,6 @@
   int GetDocumentPixelWidth() const;
   int GetDocumentPixelHeight() const;
 
-  // Common `pdf::mojom::PdfListener` implementations.
-  void SetCaretPosition(const gfx::PointF& position);
-  void MoveRangeSelectionExtent(const gfx::PointF& extent);
-  void SetSelectionBounds(const gfx::PointF& base, const gfx::PointF& extent);
-
   // Sets the text input type for this plugin based on `in_focus`.
   virtual void SetFormTextFieldInFocus(bool in_focus) = 0;
 
@@ -215,23 +198,11 @@
   virtual void SetAccessibilityViewportInfo(
       AccessibilityViewportInfo viewport_info) = 0;
 
-  // Returns the print preset options for the document.
-  blink::WebPrintPresetOptions GetPrintPresetOptions();
-
-  // Begins a print session with the given `print_params`. A call to
-  // `PrintPages()` can only be made after after a successful call to
-  // `PrintBegin()`. Returns the number of pages required for the print output.
-  // A returned value of 0 indicates failure.
-  int PrintBegin(const blink::WebPrintParams& print_params);
-
   // Prints the pages specified by `page_numbers` using the parameters passed to
   // `PrintBegin()` Returns a vector of bytes containing the printed output. An
   // empty returned value indicates failure.
   std::vector<uint8_t> PrintPages(const std::vector<int>& page_numbers);
 
-  // Ends the print session. Further calls to `PrintPages()` will fail.
-  void PrintEnd();
-
   // Disables browser commands because of restrictions on how the data is to be
   // used (i.e. can't copy/print). `content_restrictions` should have its bits
   // set by `chrome_pdf::ContentRestriction` enum values.
@@ -241,10 +212,6 @@
   virtual void DidStartLoading() = 0;
   virtual void DidStopLoading() = 0;
 
-  // Requests the plugin's render frame to set up a print dialog for the
-  // document.
-  virtual void InvokePrintDialog() = 0;
-
   // Notifies the embedder of the top-left and bottom-right coordinates of the
   // current selection.
   virtual void NotifySelectionChanged(const gfx::PointF& left,
@@ -299,6 +266,10 @@
     return accessibility_state_;
   }
 
+  void set_accessibility_state(AccessibilityState state) {
+    accessibility_state_ = state;
+  }
+
   static constexpr bool IsSaveDataSizeValid(size_t size) {
     return size > 0 && size <= kMaximumSavedFileSize;
   }
@@ -368,12 +339,6 @@
   // The next accessibility page index, used to track interprocess calls when
   // reconstructing the tree for new document layouts.
   int32_t next_accessibility_page_index_ = 0;
-
-  // Assigned a value only between `PrintBegin()` and `PrintEnd()` calls.
-  absl::optional<blink::WebPrintParams> print_params_;
-
-  // For identifying actual print operations to avoid double logging of UMA.
-  bool print_pages_called_;
 };
 
 }  // namespace chrome_pdf
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 35ab7794..edb53d5 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -82,6 +82,7 @@
 #include "third_party/blink/public/web/web_frame.h"
 #include "third_party/blink/public/web/web_plugin_container.h"
 #include "third_party/blink/public/web/web_plugin_params.h"
+#include "third_party/blink/public/web/web_print_params.h"
 #include "third_party/blink/public/web/web_print_preset_options.h"
 #include "third_party/blink/public/web/web_view.h"
 #include "third_party/blink/public/web/web_widget.h"
@@ -584,12 +585,29 @@
 
 bool PdfViewWebPlugin::GetPrintPresetOptionsFromDocument(
     blink::WebPrintPresetOptions* print_preset_options) {
-  *print_preset_options = GetPrintPresetOptions();
+  print_preset_options->is_scaling_disabled = !engine_->GetPrintScaling();
+  print_preset_options->copies = engine_->GetCopiesToPrint();
+  print_preset_options->duplex_mode = engine_->GetDuplexMode();
+  print_preset_options->uniform_page_size = engine_->GetUniformPageSizePoints();
   return true;
 }
 
 int PdfViewWebPlugin::PrintBegin(const blink::WebPrintParams& print_params) {
-  return PdfViewPluginBase::PrintBegin(print_params);
+  // The returned value is always equal to the number of pages in the PDF
+  // document irrespective of the printable area.
+  int32_t ret = engine_->GetNumberOfPages();
+  if (!ret)
+    return 0;
+
+  if (!engine_->HasPermission(DocumentPermission::kPrintLowQuality))
+    return 0;
+
+  print_params_ = print_params;
+  if (!engine_->HasPermission(DocumentPermission::kPrintHighQuality))
+    print_params_->rasterize_pdf = true;
+
+  engine_->PrintBegin();
+  return ret;
 }
 
 void PdfViewWebPlugin::PrintPage(int page_number, cc::PaintCanvas* canvas) {
@@ -618,9 +636,16 @@
   if (pages_to_print_.empty())
     return;
 
-  printing_metafile_->InitFromData(PrintPages(pages_to_print_));
+  print_pages_called_ = true;
+  printing_metafile_->InitFromData(
+      engine_->PrintPages(pages_to_print_, print_params_.value()));
 
-  PdfViewPluginBase::PrintEnd();
+  if (print_pages_called_)
+    UserMetricsRecordAction("PDF.PrintPage");
+  print_pages_called_ = false;
+  print_params_.reset();
+  engine_->PrintEnd();
+
   printing_metafile_ = nullptr;
   pages_to_print_.clear();
 }
@@ -878,6 +903,21 @@
                    base::BindOnce(std::move(callback), std::move(loader)));
 }
 
+void PdfViewWebPlugin::Print() {
+  if (!engine_)
+    return;
+
+  const bool can_print =
+      engine_->HasPermission(DocumentPermission::kPrintLowQuality) ||
+      engine_->HasPermission(DocumentPermission::kPrintHighQuality);
+  if (!can_print)
+    return;
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
+                                weak_factory_.GetWeakPtr()));
+}
+
 void PdfViewWebPlugin::SubmitForm(const std::string& url,
                                   const void* data,
                                   int length) {
@@ -977,16 +1017,17 @@
 }
 
 void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
-  PdfViewPluginBase::SetCaretPosition(position);
+  engine_->SetCaretPosition(FrameToPdfCoordinates(position));
 }
 
 void PdfViewWebPlugin::MoveRangeSelectionExtent(const gfx::PointF& extent) {
-  PdfViewPluginBase::MoveRangeSelectionExtent(extent);
+  engine_->MoveRangeSelectionExtent(FrameToPdfCoordinates(extent));
 }
 
 void PdfViewWebPlugin::SetSelectionBounds(const gfx::PointF& base,
                                           const gfx::PointF& extent) {
-  PdfViewPluginBase::SetSelectionBounds(base, extent);
+  engine_->SetSelectionBounds(FrameToPdfCoordinates(base),
+                              FrameToPdfCoordinates(extent));
 }
 
 bool PdfViewWebPlugin::IsValid() const {
@@ -1201,7 +1242,9 @@
 
 void PdfViewWebPlugin::HandleSetTwoUpViewMessage(
     const base::Value::Dict& message) {
-  engine_->SetTwoUpView(message.FindBool("enableTwoUpView").value());
+  engine_->SetDocumentLayout(message.FindBool("enableTwoUpView").value()
+                                 ? DocumentLayout::PageSpread::kTwoUpOdd
+                                 : DocumentLayout::PageSpread::kOneUp);
 }
 
 void PdfViewWebPlugin::HandleStopScrollingMessage(
@@ -1418,12 +1461,19 @@
 }
 
 void PdfViewWebPlugin::EnableAccessibility() {
-  PdfViewPluginBase::EnableAccessibility();
+  if (accessibility_state() == AccessibilityState::kLoaded)
+    return;
+
+  if (accessibility_state() == AccessibilityState::kOff)
+    set_accessibility_state(AccessibilityState::kPending);
+
+  if (document_load_state() == DocumentLoadState::kComplete)
+    LoadAccessibility();
 }
 
 void PdfViewWebPlugin::HandleAccessibilityAction(
     const AccessibilityActionData& action_data) {
-  PdfViewPluginBase::HandleAccessibilityAction(action_data);
+  engine_->HandleAccessibilityAction(action_data);
 }
 
 base::WeakPtr<PdfViewPluginBase> PdfViewWebPlugin::GetWeakPtr() {
@@ -1453,6 +1503,9 @@
   SendAttachments();
   SendBookmarks();
   SendMetadata();
+
+  if (accessibility_state() == AccessibilityState::kPending)
+    LoadAccessibility();
 }
 
 void PdfViewWebPlugin::SendMessage(base::Value::Dict message) {
@@ -1505,12 +1558,6 @@
   did_call_start_loading_ = false;
 }
 
-void PdfViewWebPlugin::InvokePrintDialog() {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&PdfViewWebPlugin::OnInvokePrintDialog,
-                                weak_factory_.GetWeakPtr()));
-}
-
 void PdfViewWebPlugin::NotifySelectionChanged(const gfx::PointF& left,
                                               int left_height,
                                               const gfx::PointF& right,
@@ -1906,6 +1953,12 @@
     SendPrintPreviewLoadedNotification();
 }
 
+void PdfViewWebPlugin::SendPrintPreviewLoadedNotification() {
+  base::Value::Dict message;
+  message.Set("type", "printPreviewLoaded");
+  SendMessage(std::move(message));
+}
+
 void PdfViewWebPlugin::SendThumbnail(base::Value::Dict reply,
                                      Thumbnail thumbnail) {
   DCHECK_EQ(*reply.FindString("type"), "getThumbnailReply");
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index e5099896..2cc4bbf 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -36,6 +36,7 @@
 #include "third_party/blink/public/web/web_plugin.h"
 #include "third_party/blink/public/web/web_plugin_container.h"
 #include "third_party/blink/public/web/web_plugin_params.h"
+#include "third_party/blink/public/web/web_print_params.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/gfx/geometry/rect.h"
@@ -48,6 +49,7 @@
 class WebURL;
 class WebURLRequest;
 struct WebAssociatedURLLoaderOptions;
+struct WebPrintPresetOptions;
 }  // namespace blink
 
 namespace gfx {
@@ -284,6 +286,7 @@
   std::string Prompt(const std::string& question,
                      const std::string& default_answer) override;
   std::string GetURL() override;
+  void Print() override;
   void SubmitForm(const std::string& url,
                   const void* data,
                   int length) override;
@@ -363,7 +366,6 @@
   void SetContentRestrictions(int content_restrictions) override;
   void DidStartLoading() override;
   void DidStopLoading() override;
-  void InvokePrintDialog() override;
   void NotifySelectionChanged(const gfx::PointF& left,
                               int left_height,
                               const gfx::PointF& right,
@@ -490,6 +492,9 @@
   // Continues loading the next preview page.
   void LoadNextPreviewPage();
 
+  // Sends a notification that the print preview has loaded.
+  void SendPrintPreviewLoadedNotification();
+
   // Sends the thumbnail image data.
   void SendThumbnail(base::Value::Dict reply, Thumbnail thumbnail);
 
@@ -638,6 +643,12 @@
   // The indices of pages to print.
   std::vector<int> pages_to_print_;
 
+  // Assigned a value only between `PrintBegin()` and `PrintEnd()` calls.
+  absl::optional<blink::WebPrintParams> print_params_;
+
+  // For identifying actual print operations to avoid double logging of UMA.
+  bool print_pages_called_;
+
   // Whether the plugin is loaded in Print Preview.
   bool is_print_preview_ = false;
 
diff --git a/pdf/pdfium/findtext_unittest.cc b/pdf/pdfium/findtext_unittest.cc
index 84e83614..242b1d58 100644
--- a/pdf/pdfium/findtext_unittest.cc
+++ b/pdf/pdfium/findtext_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/check_op.h"
 #include "base/strings/utf_string_conversions.h"
+#include "pdf/document_layout.h"
 #include "pdf/pdfium/pdfium_engine.h"
 #include "pdf/pdfium/pdfium_test_base.h"
 #include "pdf/test/test_client.h"
@@ -223,7 +224,7 @@
     EXPECT_CALL(client,
                 NotifyNumberOfFindResultsChanged(4, /*final_result=*/true));
   }
-  engine->SetTwoUpView(true);
+  engine->SetDocumentLayout(DocumentLayout::PageSpread::kTwoUpOdd);
 
   {
     InSequence sequence;
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index b2ce295..ddb8d4a5 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -2153,10 +2153,8 @@
   selection_.clear();
 }
 
-void PDFiumEngine::SetTwoUpView(bool enable) {
-  desired_layout_options_.set_page_spread(
-      enable ? DocumentLayout::PageSpread::kTwoUpOdd
-             : DocumentLayout::PageSpread::kOneUp);
+void PDFiumEngine::SetDocumentLayout(DocumentLayout::PageSpread page_spread) {
+  desired_layout_options_.set_page_spread(page_spread);
   ProposeNextDocumentLayout();
 }
 
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 61fd67a..c0f83d9 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -114,7 +114,7 @@
   void RotateCounterclockwise() override;
   bool IsReadOnly() const override;
   void SetReadOnly(bool enable) override;
-  void SetTwoUpView(bool enable) override;
+  void SetDocumentLayout(DocumentLayout::PageSpread page_spread) override;
   void DisplayAnnotations(bool display) override;
   gfx::Size ApplyDocumentLayout(
       const DocumentLayout::Options& options) override;
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc
index c412480..9a9c4c6 100644
--- a/pdf/pdfium/pdfium_engine_unittest.cc
+++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -193,7 +193,7 @@
   options.set_page_spread(DocumentLayout::PageSpread::kTwoUpOdd);
   EXPECT_CALL(client, ProposeDocumentLayout(LayoutWithOptions(options)))
       .WillOnce(Return());
-  engine->SetTwoUpView(true);
+  engine->SetDocumentLayout(DocumentLayout::PageSpread::kTwoUpOdd);
 
   engine->ApplyDocumentLayout(options);
 
diff --git a/pdf/pdfium/pdfium_permissions.cc b/pdf/pdfium/pdfium_permissions.cc
index 6f0f24d1..dc2d128a 100644
--- a/pdf/pdfium/pdfium_permissions.cc
+++ b/pdf/pdfium/pdfium_permissions.cc
@@ -45,25 +45,25 @@
       case DocumentPermission::kCopy:
       case DocumentPermission::kCopyAccessible:
         // Check the same copy bit for all copying permissions.
-        return HasPermissionBits(kPDFPermissionCopyMask);
+        return HasPermissionBits(kPDFPermissionBit05CopyMask);
       case DocumentPermission::kPrintLowQuality:
       case DocumentPermission::kPrintHighQuality:
         // Check the same printing bit for all printing permissions.
-        return HasPermissionBits(kPDFPermissionPrintMask);
+        return HasPermissionBits(kPDFPermissionBit03PrintMask);
     }
   } else {
     // Security handler revision 3+ have different rules for interpreting the
     // bits in `permission_bits_`.
     switch (permission) {
       case DocumentPermission::kCopy:
-        return HasPermissionBits(kPDFPermissionCopyMask);
+        return HasPermissionBits(kPDFPermissionBit05CopyMask);
       case DocumentPermission::kCopyAccessible:
-        return HasPermissionBits(kPDFPermissionCopyAccessibleMask);
+        return HasPermissionBits(kPDFPermissionBit10CopyAccessibleMask);
       case DocumentPermission::kPrintLowQuality:
-        return HasPermissionBits(kPDFPermissionPrintMask);
+        return HasPermissionBits(kPDFPermissionBit03PrintMask);
       case DocumentPermission::kPrintHighQuality:
-        return HasPermissionBits(kPDFPermissionPrintMask |
-                                 kPDFPermissionPrintHighQualityMask);
+        return HasPermissionBits(kPDFPermissionBit03PrintMask |
+                                 kPDFPermissionBit12PrintHighQualityMask);
     }
   }
 }
diff --git a/pdf/pdfium/pdfium_permissions.h b/pdf/pdfium/pdfium_permissions.h
index c4c0ce7..8dc784b 100644
--- a/pdf/pdfium/pdfium_permissions.h
+++ b/pdf/pdfium/pdfium_permissions.h
@@ -14,10 +14,10 @@
 
 // See Table 3.20 in the PDF 1.7 spec for details on how to interpret permission
 // bits. Exposed for use in testing.
-constexpr uint32_t kPDFPermissionPrintMask = 1 << 2;
-constexpr uint32_t kPDFPermissionPrintHighQualityMask = 1 << 11;
-constexpr uint32_t kPDFPermissionCopyMask = 1 << 4;
-constexpr uint32_t kPDFPermissionCopyAccessibleMask = 1 << 9;
+constexpr uint32_t kPDFPermissionBit03PrintMask = 1 << 2;
+constexpr uint32_t kPDFPermissionBit05CopyMask = 1 << 4;
+constexpr uint32_t kPDFPermissionBit10CopyAccessibleMask = 1 << 9;
+constexpr uint32_t kPDFPermissionBit12PrintHighQualityMask = 1 << 11;
 
 // The permissions for a given FPDF_DOCUMENT.
 class PDFiumPermissions final {
diff --git a/pdf/pdfium/pdfium_permissions_unittest.cc b/pdf/pdfium/pdfium_permissions_unittest.cc
index da61bc7..35fff42 100644
--- a/pdf/pdfium/pdfium_permissions_unittest.cc
+++ b/pdf/pdfium/pdfium_permissions_unittest.cc
@@ -21,21 +21,24 @@
 }
 
 // Sanity check the permission constants are correct.
-static_assert(kPDFPermissionCopyAccessibleMask == 0x200, "Wrong permission");
-static_assert(kPDFPermissionCopyMask == 0x10, "Wrong permission");
-static_assert(kPDFPermissionPrintHighQualityMask == 0x800, "Wrong permission");
-static_assert(kPDFPermissionPrintMask == 0x4, "Wrong permission");
+static_assert(kPDFPermissionBit03PrintMask == 0x4, "Wrong permission");
+static_assert(kPDFPermissionBit05CopyMask == 0x10, "Wrong permission");
+static_assert(kPDFPermissionBit10CopyAccessibleMask == 0x200,
+              "Wrong permission");
+static_assert(kPDFPermissionBit12PrintHighQualityMask == 0x800,
+              "Wrong permission");
 
 // Sanity check the permission generation functions above do the right thing.
 static_assert(GeneratePermissions2(0) == 0xffffffc0, "Wrong permission");
-static_assert(GeneratePermissions2(kPDFPermissionCopyMask |
-                                   kPDFPermissionPrintMask) == 0xffffffd4,
+static_assert(GeneratePermissions2(kPDFPermissionBit03PrintMask |
+                                   kPDFPermissionBit05CopyMask) == 0xffffffd4,
               "Wrong permission");
 static_assert(GeneratePermissions3(0) == 0xfffff0c0, "Wrong permission");
-static_assert(GeneratePermissions3(kPDFPermissionCopyAccessibleMask |
-                                   kPDFPermissionCopyMask |
-                                   kPDFPermissionPrintHighQualityMask |
-                                   kPDFPermissionPrintMask) == 0xfffffad4,
+static_assert(GeneratePermissions3(kPDFPermissionBit03PrintMask |
+                                   kPDFPermissionBit05CopyMask |
+                                   kPDFPermissionBit10CopyAccessibleMask |
+                                   kPDFPermissionBit12PrintHighQualityMask) ==
+                  0xfffffad4,
               "Wrong permission");
 
 TEST(PDFiumPermissionTest, InvalidSecurityHandler) {
@@ -62,16 +65,18 @@
       obsolete_perms.HasPermission(DocumentPermission::kPrintHighQuality));
 }
 
-TEST(PDFiumPermissionTest, Revision2SecurityHandler) {
+TEST(PDFiumPermissionTest, Revision2SecurityHandlerNone) {
   uint32_t permissions = GeneratePermissions2(0);
   auto no_perms = PDFiumPermissions::CreateForTesting(2, permissions);
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kCopyAccessible));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kPrintLowQuality));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kPrintHighQuality));
+}
 
-  permissions =
-      GeneratePermissions2(kPDFPermissionCopyMask | kPDFPermissionPrintMask);
+TEST(PDFiumPermissionTest, Revision2SecurityHandlerAll) {
+  uint32_t permissions = GeneratePermissions2(kPDFPermissionBit03PrintMask |
+                                              kPDFPermissionBit05CopyMask);
   auto all_known_perms = PDFiumPermissions::CreateForTesting(2, permissions);
   EXPECT_TRUE(all_known_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_TRUE(
@@ -80,8 +85,10 @@
       all_known_perms.HasPermission(DocumentPermission::kPrintLowQuality));
   EXPECT_TRUE(
       all_known_perms.HasPermission(DocumentPermission::kPrintHighQuality));
+}
 
-  permissions = GeneratePermissions2(kPDFPermissionCopyMask);
+TEST(PDFiumPermissionTest, Revision2SecurityHandlerCopyPrint) {
+  uint32_t permissions = GeneratePermissions2(kPDFPermissionBit05CopyMask);
   auto no_print_perms = PDFiumPermissions::CreateForTesting(2, permissions);
   EXPECT_TRUE(no_print_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_TRUE(
@@ -91,7 +98,7 @@
   EXPECT_FALSE(
       no_print_perms.HasPermission(DocumentPermission::kPrintHighQuality));
 
-  permissions = GeneratePermissions2(kPDFPermissionPrintMask);
+  permissions = GeneratePermissions2(kPDFPermissionBit03PrintMask);
   auto no_copy_perms = PDFiumPermissions::CreateForTesting(2, permissions);
   EXPECT_FALSE(no_copy_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_FALSE(
@@ -102,17 +109,20 @@
       no_copy_perms.HasPermission(DocumentPermission::kPrintHighQuality));
 }
 
-TEST(PDFiumPermissionTest, Revision3SecurityHandler) {
+TEST(PDFiumPermissionTest, Revision3SecurityHandlerNone) {
   uint32_t permissions = GeneratePermissions3(0);
   auto no_perms = PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kCopyAccessible));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kPrintLowQuality));
   EXPECT_FALSE(no_perms.HasPermission(DocumentPermission::kPrintHighQuality));
+}
 
-  permissions = GeneratePermissions3(
-      kPDFPermissionCopyAccessibleMask | kPDFPermissionCopyMask |
-      kPDFPermissionPrintHighQualityMask | kPDFPermissionPrintMask);
+TEST(PDFiumPermissionTest, Revision3SecurityHandlerAll) {
+  uint32_t permissions = GeneratePermissions3(
+      kPDFPermissionBit03PrintMask | kPDFPermissionBit05CopyMask |
+      kPDFPermissionBit10CopyAccessibleMask |
+      kPDFPermissionBit12PrintHighQualityMask);
   auto all_known_perms = PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_TRUE(all_known_perms.HasPermission(DocumentPermission::kCopy));
   EXPECT_TRUE(
@@ -121,9 +131,11 @@
       all_known_perms.HasPermission(DocumentPermission::kPrintLowQuality));
   EXPECT_TRUE(
       all_known_perms.HasPermission(DocumentPermission::kPrintHighQuality));
+}
 
-  permissions = GeneratePermissions3(kPDFPermissionCopyAccessibleMask |
-                                     kPDFPermissionCopyMask);
+TEST(PDFiumPermissionTest, Revision3SecurityHandlerCopyPrint) {
+  uint32_t permissions = GeneratePermissions3(
+      kPDFPermissionBit05CopyMask | kPDFPermissionBit10CopyAccessibleMask);
   auto copy_no_print_perms =
       PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_TRUE(copy_no_print_perms.HasPermission(DocumentPermission::kCopy));
@@ -134,9 +146,9 @@
   EXPECT_FALSE(
       copy_no_print_perms.HasPermission(DocumentPermission::kPrintHighQuality));
 
-  permissions =
-      GeneratePermissions3(kPDFPermissionCopyAccessibleMask |
-                           kPDFPermissionCopyMask | kPDFPermissionPrintMask);
+  permissions = GeneratePermissions3(kPDFPermissionBit03PrintMask |
+                                     kPDFPermissionBit05CopyMask |
+                                     kPDFPermissionBit10CopyAccessibleMask);
   auto copy_low_print_perms =
       PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_TRUE(copy_low_print_perms.HasPermission(DocumentPermission::kCopy));
@@ -147,8 +159,8 @@
   EXPECT_FALSE(copy_low_print_perms.HasPermission(
       DocumentPermission::kPrintHighQuality));
 
-  permissions = GeneratePermissions3(kPDFPermissionPrintHighQualityMask |
-                                     kPDFPermissionPrintMask);
+  permissions = GeneratePermissions3(kPDFPermissionBit03PrintMask |
+                                     kPDFPermissionBit12PrintHighQualityMask);
   auto print_no_copy_perms =
       PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_FALSE(print_no_copy_perms.HasPermission(DocumentPermission::kCopy));
@@ -159,9 +171,9 @@
   EXPECT_TRUE(
       print_no_copy_perms.HasPermission(DocumentPermission::kPrintHighQuality));
 
-  permissions = GeneratePermissions3(kPDFPermissionCopyAccessibleMask |
-                                     kPDFPermissionPrintHighQualityMask |
-                                     kPDFPermissionPrintMask);
+  permissions = GeneratePermissions3(kPDFPermissionBit03PrintMask |
+                                     kPDFPermissionBit10CopyAccessibleMask |
+                                     kPDFPermissionBit12PrintHighQualityMask);
   auto print_a11y_copy_perms =
       PDFiumPermissions::CreateForTesting(3, permissions);
   EXPECT_FALSE(print_a11y_copy_perms.HasPermission(DocumentPermission::kCopy));
diff --git a/remoting/base/result.h b/remoting/base/result.h
index 6ca5e1c..6d1313c 100644
--- a/remoting/base/result.h
+++ b/remoting/base/result.h
@@ -124,14 +124,16 @@
 
 namespace remoting {
 
+// TODO(joedow): Migrate instances of remoting::Result to base::expected.
+
 // SuccessTag and ErrorTag are used for constructing a Result in the success
 // state or error state, respectively.
 class SuccessTag {};
 class ErrorTag {};
-// absl::Monostate can be used for SuccessType or ErrorType to indicate that
-// there is no data for that state. Thus, Result<SomeType, Monostate> is
-// somewhat analogous to absl::optional<SomeType>, and Result<Monostate,
-// Monostate> is effectively a (2-byte) boolean. Result<Monostate, ErrorType>
+// absl::monostate can be used for SuccessType or ErrorType to indicate that
+// there is no data for that state. Thus, Result<SomeType, monostate> is
+// somewhat analogous to absl::optional<SomeType>, and Result<monostate,
+// monostate> is effectively a (2-byte) boolean. Result<monostate, ErrorType>
 // can be useful for cases where an operation can fail, but there is no return
 // value in the success case.
 
diff --git a/remoting/codec/webrtc_video_encoder_av1.cc b/remoting/codec/webrtc_video_encoder_av1.cc
index 83c4fbc..cc1bceb 100644
--- a/remoting/codec/webrtc_video_encoder_av1.cc
+++ b/remoting/codec/webrtc_video_encoder_av1.cc
@@ -54,7 +54,13 @@
   // Set the width, height, and thread count now that the frame size is known.
   config_.g_w = size.width();
   config_.g_h = size.height();
-  config_.g_threads = GetEncoderThreadCount(config_.g_w);
+  // Determine the number of threads to use for encoding by choosing the larger
+  // of the two dimensions. If we only checked the width, the performance for
+  // displays in portrait mode will be degraded due to a lower thread count.
+  // Since AV1 supports dividing the image into both rows and cols, we set the
+  // maximum number of threads here and then set the tile_row and tile_column
+  // values based on the frame dimensions later on.
+  config_.g_threads = GetEncoderThreadCount(std::max(config_.g_w, config_.g_h));
 
   // Initialize an encoder instance.
   scoped_aom_codec codec(new aom_codec_ctx_t, DestroyAomCodecContext);
@@ -79,13 +85,21 @@
     DCHECK_EQ(error, AOM_CODEC_OK) << "Failed to set AV1E_SET_ROW_MT";
   }
 
-  // The param for the tile column control is a log2 value so 0 is ok.
-  error = aom_codec_control(codec.get(), AV1E_SET_TILE_COLUMNS,
-                            static_cast<int>(std::log2(config_.g_threads)));
+  // The param used to set the tile columns and tile rows is in log2 so 0 is ok.
+  int log2_threads = static_cast<int>(std::log2(config_.g_threads));
+  // Split the cols/rows as evenly as possible, favoring columns for widescreen.
+  int log2_cols = (log2_threads + 1) / 2;
+  int log2_rows = log2_threads - log2_cols;
+  if (size.height() > size.width()) {
+    // Swap for portrait mode since we want more rows than columns.
+    std::swap(log2_cols, log2_rows);
+  }
+
+  error = aom_codec_control(codec.get(), AV1E_SET_TILE_COLUMNS, log2_cols);
   DCHECK_EQ(error, AOM_CODEC_OK) << "Failed to set AV1E_SET_TILE_COLUMNS";
 
-  // TODO(joedow): Experiment with AV1E_SET_TILE_ROWS. Note that the total
-  // number of COLUMNS and ROWS should add up to, at most, config_.g_threads.
+  error = aom_codec_control(codec.get(), AV1E_SET_TILE_ROWS, log2_rows);
+  DCHECK_EQ(error, AOM_CODEC_OK) << "Failed to set AV1E_SET_TILE_ROWS";
 
   // These make realtime encoding faster.
   error =
diff --git a/remoting/codec/webrtc_video_encoder_vpx.cc b/remoting/codec/webrtc_video_encoder_vpx.cc
index 65c42679..1c4be417 100644
--- a/remoting/codec/webrtc_video_encoder_vpx.cc
+++ b/remoting/codec/webrtc_video_encoder_vpx.cc
@@ -66,6 +66,10 @@
   config->g_w = size.width();
   config->g_h = size.height();
   config->g_pass = VPX_RC_ONE_PASS;
+  // TODO(joedow): Determine whether it is possible to support portrait mode.
+  // VP9 only supports breaking an image up into columns for parallel encoding,
+  // this means that a display in portrait mode cannot be broken up into as many
+  // columns as a display in landscape mode can so performance will be degraded.
   config->g_threads = WebrtcVideoEncoder::GetEncoderThreadCount(config->g_w);
 
   // Start emitting packets immediately.
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index d371b41..0cfad5f4f 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -96,7 +96,6 @@
     "audio_volume_filter.h",
     "basic_desktop_environment.h",
     "chromoting_messages.h",
-    "chromoting_param_traits.h",
     "desktop_and_cursor_conditional_composer.h",
     "desktop_environment.h",
     "evaluate_capability.h",
@@ -246,8 +245,6 @@
     "chromoting_host_context.cc",
     "chromoting_host_context.h",
     "chromoting_messages.cc",
-    "chromoting_param_traits.cc",
-    "chromoting_param_traits_impl.h",
     "client_session.cc",
     "client_session.h",
     "client_session_events.h",
diff --git a/remoting/host/OWNERS b/remoting/host/OWNERS
index d8a041f..2316a02 100644
--- a/remoting/host/OWNERS
+++ b/remoting/host/OWNERS
@@ -4,8 +4,5 @@
 per-file *_messages.cc=set noparent
 per-file *_messages.cc=file://ipc/SECURITY_OWNERS
 
-per-file *_param_traits*.*=set noparent
-per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
-
 per-file *event_reporter*=file://components/reporting/OWNERS
 
diff --git a/remoting/host/chromoting_messages.cc b/remoting/host/chromoting_messages.cc
index 2367eb0..f4406af 100644
--- a/remoting/host/chromoting_messages.cc
+++ b/remoting/host/chromoting_messages.cc
@@ -2,31 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/host/chromoting_param_traits_impl.h"
-
 // Get basic type definitions.
 #define IPC_MESSAGE_IMPL
 #include "remoting/host/chromoting_messages.h"
-
-// Generate constructors.
-#include "ipc/struct_constructor_macros.h"
-#include "remoting/host/chromoting_messages.h"
-
-// Generate param traits write methods.
-#include "ipc/param_traits_write_macros.h"
-namespace IPC {
-#include "remoting/host/chromoting_messages.h"
-}  // namespace IPC
-
-// Generate param traits read methods.
-#include "ipc/param_traits_read_macros.h"
-namespace IPC {
-#include "remoting/host/chromoting_messages.h"
-}  // namespace IPC
-
-// Generate param traits log methods.
-#include "ipc/param_traits_log_macros.h"
-namespace IPC {
-#include "remoting/host/chromoting_messages.h"
-}  // namespace IPC
-
diff --git a/remoting/host/chromoting_messages.h b/remoting/host/chromoting_messages.h
index 205d36a..553b18e 100644
--- a/remoting/host/chromoting_messages.h
+++ b/remoting/host/chromoting_messages.h
@@ -5,13 +5,7 @@
 #ifndef REMOTING_HOST_CHROMOTING_MESSAGES_H_
 #define REMOTING_HOST_CHROMOTING_MESSAGES_H_
 
-#include <stdint.h>
-
-#include "base/files/file_path.h"
 #include "ipc/ipc_message_start.h"
-#include "ipc/ipc_platform_file.h"
-#include "remoting/host/chromoting_param_traits.h"
-#include "remoting/protocol/file_transfer_helpers.h"
 
 #endif  // REMOTING_HOST_CHROMOTING_MESSAGES_H_
 
@@ -31,72 +25,3 @@
                     std::string /* function_name */,
                     std::string /* file_name */,
                     int /* line_number */)
-
-//-----------------------------------------------------------------------------
-// Chromoting messages sent from the desktop to the network process.
-
-// Informs the network process of the result of a file operation on the file
-// identified by |file_id|. If |result| is an error, the file ID is no longer
-// valid.
-IPC_MESSAGE_CONTROL(
-    ChromotingDesktopNetworkMsg_FileResult,
-    uint64_t /* file_id */,
-    remoting::protocol::FileTransferResult<absl::monostate> /* result */)
-
-// Carries the result of a read-file operation on the file identified by
-// |file_id|. |result| is the filename and size of the selected file. If
-// |result| is an error, the file ID is no longer valid.
-IPC_MESSAGE_CONTROL(ChromotingDesktopNetworkMsg_FileInfoResult,
-                    uint64_t /* file_id */,
-                    remoting::protocol::FileTransferResult<
-                        std::tuple<base::FilePath, uint64_t>> /* result */)
-
-// Carries the result of a file read-chunk operation on the file identified by
-// |file_id|. |result| holds the read data. If |result| is an error, the file ID
-// is no longer valid.
-IPC_MESSAGE_CONTROL(ChromotingDesktopNetworkMsg_FileDataResult,
-                    uint64_t /* file_id */,
-                    remoting::protocol::FileTransferResult<
-                        std::vector<std::uint8_t>> /* result */)
-
-//-----------------------------------------------------------------------------
-// Chromoting messages sent from the network to the desktop process.
-
-// Requests that the desktop process create a new file for writing with the
-// provided file name, which will be identified by |file_id|. The desktop
-// process will respond with a FileResult message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_WriteFile,
-                    uint64_t /* file_id */,
-                    base::FilePath /* filename */)
-
-// Requests that the desktop process append the provided data chunk to the
-// previously created file identified by |file_id|. The desktop process will
-// respond with a FileResult message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_WriteFileChunk,
-                    uint64_t /* file_id */,
-                    std::vector<std::uint8_t> /* data */)
-
-// Prompt the user to select a file for reading, which will be identified by
-// |file_id|. The desktop process will respond with a FileInfoResult message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_ReadFile,
-                    uint64_t /* file_id */)
-
-// Requests that the desktop process read a data chunk from the file identified
-// by |file_id|. The desktop process will respond with a FileDataResult message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_ReadFileChunk,
-                    uint64_t /* file_id */,
-                    uint64_t /* size */)
-
-// Requests that the desktop process close the file identified by |file_id|.
-// If the file is being written, it will be finalized, and the desktop process
-// will respond with a FileResult message. If the file is being read, there is
-// no response message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_CloseFile,
-                    uint64_t /* file_id */)
-
-// Requests that the desktop process cancel the file identified by |file_id|.
-// If the file is being written, the partial file will be deleted. If the file
-// is being read, it will be closed. In either case, there is no response
-// message.
-IPC_MESSAGE_CONTROL(ChromotingNetworkDesktopMsg_CancelFile,
-                    uint64_t /* file_id */)
diff --git a/remoting/host/chromoting_param_traits.cc b/remoting/host/chromoting_param_traits.cc
deleted file mode 100644
index f9ae7a7..0000000
--- a/remoting/host/chromoting_param_traits.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "remoting/host/chromoting_param_traits.h"
-
-#include <stdint.h>
-#include <sstream>
-
-#include "base/strings/stringprintf.h"
-#include "ipc/ipc_message_protobuf_utils.h"
-#include "ipc/ipc_message_utils.h"
-#include "remoting/protocol/file_transfer_helpers.h"
-
-namespace IPC {
-
-// remoting::protocol::FileTransfer_Error
-
-// static
-void IPC::ParamTraits<remoting::protocol::FileTransfer_Error>::Write(
-    base::Pickle* m,
-    const param_type& p) {
-  std::string serialized_file_transfer_error;
-  bool result = p.SerializeToString(&serialized_file_transfer_error);
-  DCHECK(result);
-  m->WriteString(serialized_file_transfer_error);
-}
-
-// static
-bool ParamTraits<remoting::protocol::FileTransfer_Error>::Read(
-    const base::Pickle* m,
-    base::PickleIterator* iter,
-    param_type* p) {
-  std::string serialized_file_transfer_error;
-  if (!iter->ReadString(&serialized_file_transfer_error))
-    return false;
-
-  return p->ParseFromString(serialized_file_transfer_error);
-}
-
-// static
-void ParamTraits<remoting::protocol::FileTransfer_Error>::Log(
-    const param_type& p,
-    std::string* l) {
-  std::ostringstream formatted;
-  formatted << p;
-  l->append(
-      base::StringPrintf("FileTransfer Error: %s", formatted.str().c_str()));
-}
-
-}  // namespace IPC
diff --git a/remoting/host/chromoting_param_traits.h b/remoting/host/chromoting_param_traits.h
deleted file mode 100644
index 6ee451d2..0000000
--- a/remoting/host/chromoting_param_traits.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_HOST_CHROMOTING_PARAM_TRAITS_H_
-#define REMOTING_HOST_CHROMOTING_PARAM_TRAITS_H_
-
-#include "ipc/ipc_message.h"
-#include "ipc/ipc_param_traits.h"
-#include "remoting/base/result.h"
-#include "remoting/proto/file_transfer.pb.h"
-
-namespace IPC {
-
-template <>
-struct ParamTraits<remoting::protocol::FileTransfer_Error> {
-  typedef remoting::protocol::FileTransfer_Error param_type;
-  static void Write(base::Pickle* m, const param_type& p);
-  static bool Read(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   param_type* p);
-  static void Log(const param_type& p, std::string* l);
-};
-
-template <typename SuccessType, typename ErrorType>
-struct ParamTraits<remoting::Result<SuccessType, ErrorType>> {
-  typedef remoting::Result<SuccessType, ErrorType> param_type;
-  static void Write(base::Pickle* m, const param_type& p);
-  static bool Read(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   param_type* p);
-  static void Log(const param_type& p, std::string* l);
-};
-
-}  // namespace IPC
-
-#endif  // REMOTING_HOST_CHROMOTING_PARAM_TRAITS_H_
diff --git a/remoting/host/chromoting_param_traits_impl.h b/remoting/host/chromoting_param_traits_impl.h
deleted file mode 100644
index 15c82ab..0000000
--- a/remoting/host/chromoting_param_traits_impl.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_HOST_CHROMOTING_PARAM_TRAITS_IMPL_H_
-#define REMOTING_HOST_CHROMOTING_PARAM_TRAITS_IMPL_H_
-
-#include "remoting/host/chromoting_param_traits.h"
-
-#include "ipc/ipc_message_utils.h"
-
-template <typename SuccessType, typename ErrorType>
-void IPC::ParamTraits<remoting::Result<SuccessType, ErrorType>>::Log(
-    const param_type& p,
-    std::string* l) {
-  if (p) {
-    l->append("success: ");
-    LogParam(p.success(), l);
-  } else {
-    l->append("error: ");
-    LogParam(p.error(), l);
-  }
-}
-
-template <typename SuccessType, typename ErrorType>
-bool IPC::ParamTraits<remoting::Result<SuccessType, ErrorType>>::Read(
-    const base::Pickle* m,
-    base::PickleIterator* iter,
-    param_type* p) {
-  bool is_success = false;
-  if (!ReadParam(m, iter, &is_success)) {
-    return false;
-  }
-  if (is_success) {
-    p->EmplaceSuccess();
-    if (!ReadParam(m, iter, &p->success())) {
-      return false;
-    }
-  } else {
-    p->EmplaceError();
-    if (!ReadParam(m, iter, &p->error())) {
-      return false;
-    }
-  }
-  return true;
-}
-
-template <typename SuccessType, typename ErrorType>
-void IPC::ParamTraits<remoting::Result<SuccessType, ErrorType>>::Write(
-    base::Pickle* m,
-    const param_type& p) {
-  WriteParam(m, p.is_success());
-  if (p) {
-    WriteParam(m, p.success());
-  } else {
-    WriteParam(m, p.error());
-  }
-}
-
-#endif  // REMOTING_HOST_CHROMOTING_PARAM_TRAITS_IMPL_H_
diff --git a/remoting/host/desktop_session_agent.cc b/remoting/host/desktop_session_agent.cc
index ca8a029..d673173 100644
--- a/remoting/host/desktop_session_agent.cc
+++ b/remoting/host/desktop_session_agent.cc
@@ -21,7 +21,6 @@
 #include "build/build_config.h"
 #include "ipc/ipc_channel_proxy.h"
 #include "ipc/ipc_message.h"
-#include "ipc/ipc_message_macros.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "remoting/base/auto_thread_task_runner.h"
 #include "remoting/base/constants.h"
@@ -29,7 +28,6 @@
 #include "remoting/host/audio_capturer.h"
 #include "remoting/host/base/screen_controls.h"
 #include "remoting/host/base/screen_resolution.h"
-#include "remoting/host/chromoting_messages.h"
 #include "remoting/host/crash_process.h"
 #include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/desktop_environment.h"
@@ -240,33 +238,8 @@
 
 bool DesktopSessionAgent::OnMessageReceived(const IPC::Message& message) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
-  CHECK(started_);
-
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_ReadFile,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::ReadFile)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_ReadFileChunk,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::ReadChunk)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_WriteFile,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::WriteFile)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_WriteFileChunk,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::WriteChunk)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_CloseFile,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::Close)
-    IPC_MESSAGE_FORWARD(ChromotingNetworkDesktopMsg_CancelFile,
-                        &*session_file_operations_handler_,
-                        SessionFileOperationsHandler::Cancel)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-
-  CHECK(handled) << "Received unexpected IPC type: " << message.type();
-  return handled;
+  NOTREACHED() << "Received unexpected IPC type: " << message.type();
+  return false;
 }
 
 void DesktopSessionAgent::OnChannelConnected(int32_t peer_pid) {
@@ -462,7 +435,7 @@
 
   // Set up the message handler for file transfers.
   session_file_operations_handler_.emplace(
-      this, desktop_environment_->CreateFileOperations());
+      desktop_environment_->CreateFileOperations());
 
   url_forwarder_configurator_ =
       desktop_environment_->CreateUrlForwarderConfigurator();
@@ -554,30 +527,6 @@
   }
 }
 
-void DesktopSessionAgent::OnResult(uint64_t file_id,
-                                   ResultHandler::Result result) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToNetwork(std::make_unique<ChromotingDesktopNetworkMsg_FileResult>(
-      file_id, std::move(result)));
-}
-
-void DesktopSessionAgent::OnInfoResult(std::uint64_t file_id,
-                                       ResultHandler::InfoResult result) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToNetwork(std::make_unique<ChromotingDesktopNetworkMsg_FileInfoResult>(
-      file_id, std::move(result)));
-}
-
-void DesktopSessionAgent::OnDataResult(std::uint64_t file_id,
-                                       ResultHandler::DataResult result) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToNetwork(std::make_unique<ChromotingDesktopNetworkMsg_FileDataResult>(
-      file_id, std::move(result)));
-}
-
 mojo::ScopedMessagePipeHandle DesktopSessionAgent::Initialize(
     const base::WeakPtr<Delegate>& delegate) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
@@ -773,19 +722,6 @@
     screen_controls_->SetScreenResolution(resolution, absl::nullopt);
 }
 
-void DesktopSessionAgent::SendToNetwork(std::unique_ptr<IPC::Message> message) {
-  if (!caller_task_runner_->BelongsToCurrentThread()) {
-    caller_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&DesktopSessionAgent::SendToNetwork, this,
-                                  std::move(message)));
-    return;
-  }
-
-  if (network_channel_) {
-    network_channel_->Send(message.release());
-  }
-}
-
 void DesktopSessionAgent::StartAudioCapturer() {
   DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
 
@@ -816,6 +752,22 @@
   webauthn_state_change_notifier_->NotifyStateChange();
 }
 
+void DesktopSessionAgent::BeginFileRead(BeginFileReadCallback callback) {
+  DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  CHECK(started_);
+
+  session_file_operations_handler_->BeginFileRead(std::move(callback));
+}
+
+void DesktopSessionAgent::BeginFileWrite(const base::FilePath& file_path,
+                                         BeginFileWriteCallback callback) {
+  DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  CHECK(started_);
+
+  session_file_operations_handler_->BeginFileWrite(file_path,
+                                                   std::move(callback));
+}
+
 void DesktopSessionAgent::OnCheckUrlForwarderSetUpResult(bool is_set_up) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
   if (!desktop_session_event_handler_) {
diff --git a/remoting/host/desktop_session_agent.h b/remoting/host/desktop_session_agent.h
index b159db2b..7cfa429 100644
--- a/remoting/host/desktop_session_agent.h
+++ b/remoting/host/desktop_session_agent.h
@@ -31,6 +31,7 @@
 #include "remoting/host/mojom/remoting_mojom_traits.h"
 #include "remoting/proto/url_forwarder_control.pb.h"
 #include "remoting/protocol/clipboard_stub.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
@@ -73,7 +74,6 @@
       public webrtc::DesktopCapturer::Callback,
       public webrtc::MouseCursorMonitor::Callback,
       public ClientSessionControl,
-      public IpcFileOperations::ResultHandler,
       public mojom::DesktopSessionAgent,
       public mojom::DesktopSessionControl {
  public:
@@ -126,13 +126,6 @@
   // Forwards an audio packet though the IPC channel to the network process.
   void ProcessAudioPacket(std::unique_ptr<AudioPacket> packet);
 
-  // IpcFileOperations::ResultHandler implementation.
-  void OnResult(std::uint64_t file_id, ResultHandler::Result result) override;
-  void OnInfoResult(std::uint64_t file_id,
-                    ResultHandler::InfoResult result) override;
-  void OnDataResult(std::uint64_t file_id,
-                    ResultHandler::DataResult result) override;
-
   // mojom::DesktopSessionAgent implementation.
   void Start(const std::string& authenticated_jid,
              const ScreenResolution& resolution,
@@ -152,6 +145,9 @@
   void InjectTouchEvent(const protocol::TouchEvent& event) override;
   void SetUpUrlForwarder() override;
   void SignalWebAuthnExtension() override;
+  void BeginFileRead(BeginFileReadCallback callback) override;
+  void BeginFileWrite(const base::FilePath& file_path,
+                      BeginFileWriteCallback callback) override;
 
   // Creates desktop integration components and a connected IPC channel to be
   // used to access them. The client end of the channel is returned.
@@ -187,9 +183,6 @@
   // Notifies the network process when a shared memory region is released.
   void OnSharedMemoryRegionReleased(int id);
 
-  // Sends a message to the network process.
-  void SendToNetwork(std::unique_ptr<IPC::Message> message);
-
   // Posted to |audio_capture_task_runner_| to start the audio capturer.
   void StartAudioCapturer();
 
diff --git a/remoting/host/desktop_session_proxy.cc b/remoting/host/desktop_session_proxy.cc
index 64830da..934e340 100644
--- a/remoting/host/desktop_session_proxy.cc
+++ b/remoting/host/desktop_session_proxy.cc
@@ -18,9 +18,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "build/build_config.h"
 #include "ipc/ipc_channel_proxy.h"
-#include "ipc/ipc_message_macros.h"
 #include "remoting/base/capabilities.h"
-#include "remoting/host/chromoting_messages.h"
 #include "remoting/host/client_session.h"
 #include "remoting/host/client_session_control.h"
 #include "remoting/host/crash_process.h"
@@ -33,6 +31,7 @@
 #include "remoting/host/ipc_screen_controls.h"
 #include "remoting/host/ipc_url_forwarder_configurator.h"
 #include "remoting/host/ipc_video_frame_capturer.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/host/remote_open_url/remote_open_url_util.h"
 #include "remoting/host/webauthn/remote_webauthn_delegated_state_change_notifier.h"
 #include "remoting/proto/audio.pb.h"
@@ -237,22 +236,8 @@
 
 bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message)
-    IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileResult,
-                        &ipc_file_operations_factory_,
-                        IpcFileOperations::ResultHandler::OnResult)
-    IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileInfoResult,
-                        &ipc_file_operations_factory_,
-                        IpcFileOperations::ResultHandler::OnInfoResult)
-    IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileDataResult,
-                        &ipc_file_operations_factory_,
-                        IpcFileOperations::ResultHandler::OnDataResult)
-  IPC_END_MESSAGE_MAP()
-
-  CHECK(handled) << "Received unexpected IPC type: " << message.type();
-  return handled;
+  NOTREACHED() << "Received unexpected IPC type: " << message.type();
+  return false;
 }
 
 void DesktopSessionProxy::OnChannelConnected(int32_t peer_pid) {
@@ -347,6 +332,9 @@
 
   shared_buffers_.clear();
 
+  // Notify interested folks that the IPC has been disconnected.
+  disconnect_handlers_.Notify();
+
   // Generate fake responses to keep the video capturer in sync.
   while (pending_capture_frame_requests_) {
     OnCaptureResult(mojom::CaptureResult::NewCaptureError(
@@ -537,42 +525,46 @@
   }
 }
 
-void DesktopSessionProxy::ReadFile(std::uint64_t file_id) {
+void DesktopSessionProxy::BeginFileRead(
+    IpcFileOperations::BeginFileReadCallback callback,
+    base::OnceClosure on_disconnect) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  if (!desktop_session_control_) {
+    std::move(callback).Run(
+        mojom::BeginFileReadResult::NewError(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)));
+    return;
+  }
 
-  SendToDesktop(new ChromotingNetworkDesktopMsg_ReadFile(file_id));
+  auto disconnect_handler_subscription =
+      disconnect_handlers_.Add(std::move(on_disconnect));
+  // Unretained is sound as DesktopSessionProxy owns |desktop_session_control_|
+  // and the callback won't be invoked after the remote is destroyed.
+  desktop_session_control_->BeginFileRead(base::BindOnce(
+      &DesktopSessionProxy::OnBeginFileReadResult, base::Unretained(this),
+      std::move(callback), std::move(disconnect_handler_subscription)));
 }
 
-void DesktopSessionProxy::ReadChunk(std::uint64_t file_id, std::uint64_t size) {
+void DesktopSessionProxy::BeginFileWrite(
+    const base::FilePath& file_path,
+    IpcFileOperations::BeginFileWriteCallback callback,
+    base::OnceClosure on_disconnect) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  if (!desktop_session_control_) {
+    std::move(callback).Run(
+        mojom::BeginFileWriteResult::NewError(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR)));
+    return;
+  }
 
-  SendToDesktop(new ChromotingNetworkDesktopMsg_ReadFileChunk(file_id, size));
-}
-
-void DesktopSessionProxy::WriteFile(uint64_t file_id,
-                                    const base::FilePath& filename) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToDesktop(new ChromotingNetworkDesktopMsg_WriteFile(file_id, filename));
-}
-
-void DesktopSessionProxy::WriteChunk(uint64_t file_id,
-                                     std::vector<std::uint8_t> data) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToDesktop(new ChromotingNetworkDesktopMsg_WriteFileChunk(file_id, data));
-}
-
-void DesktopSessionProxy::Close(uint64_t file_id) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToDesktop(new ChromotingNetworkDesktopMsg_CloseFile(file_id));
-}
-
-void DesktopSessionProxy::Cancel(uint64_t file_id) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  SendToDesktop(new ChromotingNetworkDesktopMsg_CancelFile(file_id));
+  auto disconnect_handler_subscription =
+      disconnect_handlers_.Add(std::move(on_disconnect));
+  // Unretained is sound as DesktopSessionProxy owns |desktop_session_control_|
+  // and the callback won't be invoked after the remote is destroyed.
+  desktop_session_control_->BeginFileWrite(
+      file_path, base::BindOnce(&DesktopSessionProxy::OnBeginFileWriteResult,
+                                base::Unretained(this), std::move(callback),
+                                std::move(disconnect_handler_subscription)));
 }
 
 void DesktopSessionProxy::IsUrlForwarderSetUp(
@@ -755,6 +747,28 @@
                                    std::move(frame));
 }
 
+void DesktopSessionProxy::OnBeginFileReadResult(
+    IpcFileOperations::BeginFileReadCallback callback,
+    base::CallbackListSubscription disconnect_handler_subscription,
+    mojom::BeginFileReadResultPtr result) {
+  // This handler is only needed until the pending_remote is returned from the
+  // DesktopSessionAgent as the IpcFileReader will then use the new channel and
+  // hook into its disconnect handler.
+  disconnect_handler_subscription = {};
+  std::move(callback).Run(std::move(result));
+}
+
+void DesktopSessionProxy::OnBeginFileWriteResult(
+    IpcFileOperations::BeginFileWriteCallback callback,
+    base::CallbackListSubscription disconnect_handler_subscription,
+    mojom::BeginFileWriteResultPtr result) {
+  // This handler is only needed until the pending_remote is returned from the
+  // DesktopSessionAgent as the IpcFileWriter will then use the new channel and
+  // hook into its disconnect handler.
+  disconnect_handler_subscription = {};
+  std::move(callback).Run(std::move(result));
+}
+
 void DesktopSessionProxy::OnMouseCursorChanged(
     const webrtc::MouseCursor& mouse_cursor) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
@@ -792,16 +806,6 @@
   }
 }
 
-void DesktopSessionProxy::SendToDesktop(IPC::Message* message) {
-  DCHECK(caller_task_runner_->BelongsToCurrentThread());
-
-  if (desktop_channel_) {
-    desktop_channel_->Send(message);
-  } else {
-    delete message;
-  }
-}
-
 // static
 void DesktopSessionProxyTraits::Destruct(
     const DesktopSessionProxy* desktop_session_proxy) {
diff --git a/remoting/host/desktop_session_proxy.h b/remoting/host/desktop_session_proxy.h
index ab3f4d3..306f0c7 100644
--- a/remoting/host/desktop_session_proxy.h
+++ b/remoting/host/desktop_session_proxy.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/callback_list.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -173,14 +174,11 @@
   void ExecuteAction(const protocol::ActionRequest& request);
 
   // IpcFileOperations::RequestHandler implementation.
-  void ReadFile(std::uint64_t file_id) override;
-  void ReadChunk(std::uint64_t file_id, std::uint64_t size) override;
-  void WriteFile(std::uint64_t file_id,
-                 const base::FilePath& filename) override;
-  void WriteChunk(std::uint64_t file_id,
-                  std::vector<std::uint8_t> data) override;
-  void Close(std::uint64_t file_id) override;
-  void Cancel(std::uint64_t file_id) override;
+  void BeginFileRead(IpcFileOperations::BeginFileReadCallback callback,
+                     base::OnceClosure on_disconnect) override;
+  void BeginFileWrite(const base::FilePath& file_path,
+                      IpcFileOperations::BeginFileWriteCallback callback,
+                      base::OnceClosure on_disconnect) override;
 
   // mojom::DesktopSessionEventHandler implementation.
   void OnClipboardEvent(const protocol::ClipboardEvent& event) override;
@@ -229,11 +227,19 @@
   void OnCaptureResult(webrtc::DesktopCapturer::Result result,
                        const SerializedDesktopFrame& serialized_frame);
 
-  void SignalWebAuthnExtension();
+  // Handles the BeginFileReadResult returned from the DesktopSessionAgent.
+  void OnBeginFileReadResult(
+      IpcFileOperations::BeginFileReadCallback callback,
+      base::CallbackListSubscription disconnect_handler_subscription,
+      mojom::BeginFileReadResultPtr result);
 
-  // Sends a message to the desktop session agent. The message is silently
-  // deleted if the channel is broken.
-  void SendToDesktop(IPC::Message* message);
+  // Handles the BeginFileWriteResult returned from the DesktopSessionAgent.
+  void OnBeginFileWriteResult(
+      IpcFileOperations::BeginFileWriteCallback callback,
+      base::CallbackListSubscription disconnect_handler_subscription,
+      mojom::BeginFileWriteResultPtr result);
+
+  void SignalWebAuthnExtension();
 
   // Task runners:
   //   - |audio_capturer_| is called back on |audio_capture_task_runner_|.
@@ -298,6 +304,9 @@
   // is called on IpcKeyboardLayoutMonitor.
   absl::optional<protocol::KeyboardLayout> keyboard_layout_;
 
+  // Used to notify registered handlers when the IPC channel is disconnected.
+  base::OnceClosureList disconnect_handlers_;
+
   // |desktop_session_agent_| is only valid when |desktop_channel_| is
   // connected.
   mojo::AssociatedRemote<mojom::DesktopSessionAgent> desktop_session_agent_;
diff --git a/remoting/host/file_transfer/BUILD.gn b/remoting/host/file_transfer/BUILD.gn
index b899af4..7f53fb8 100644
--- a/remoting/host/file_transfer/BUILD.gn
+++ b/remoting/host/file_transfer/BUILD.gn
@@ -41,6 +41,7 @@
       "//ipc:ipc",
       "//remoting/host:common",
       "//remoting/host/base",
+      "//remoting/host/mojom",
       "//remoting/host/win",
     ]
   } else {
@@ -85,6 +86,7 @@
     "//base",
     "//net:net",
     "//remoting/base:base",
+    "//remoting/host/mojom",
     "//remoting/protocol",
     "//ui/base:base",
     "//url:url",
@@ -128,6 +130,7 @@
     "//base/test:test_support",
     "//net:net",
     "//remoting/base:base",
+    "//remoting/host/mojom",
     "//remoting/protocol",
     "//remoting/protocol:test_support",
     "//testing/gtest",
diff --git a/remoting/host/file_transfer/file_chooser_main_win.cc b/remoting/host/file_transfer/file_chooser_main_win.cc
index fe8c453..3427725 100644
--- a/remoting/host/file_transfer/file_chooser_main_win.cc
+++ b/remoting/host/file_transfer/file_chooser_main_win.cc
@@ -13,17 +13,15 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/pickle.h"
 #include "base/run_loop.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/timer/timer.h"
 #include "base/win/scoped_co_mem.h"
 #include "base/win/scoped_com_initializer.h"
-#include "ipc/ipc_message_utils.h"
-#include "remoting/host/chromoting_param_traits.h"
-#include "remoting/host/chromoting_param_traits_impl.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "remoting/host/file_transfer/file_chooser.h"
 #include "remoting/host/file_transfer/file_chooser_common_win.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/host/win/core_resource.h"
 #include "remoting/protocol/file_transfer_helpers.h"
 
@@ -138,16 +136,15 @@
 
   FileChooser::Result result = ShowFileChooser();
 
-  base::Pickle pickle;
-  IPC::WriteParam(&pickle, result);
+  mojo::Message serialized_message =
+      mojom::FileChooserResult::SerializeAsMessage(&result);
 
   // Highly unlikely, but we want to know if it happens.
-  if (pickle.size() > kFileChooserPipeBufferSize) {
-    pickle = base::Pickle();
-    IPC::WriteParam(
-        &pickle,
-        protocol::MakeFileTransferError(
-            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  if (serialized_message.data_num_bytes() > kFileChooserPipeBufferSize) {
+    FileChooser::Result error_result(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+    serialized_message =
+        mojom::FileChooserResult::SerializeAsMessage(&error_result);
   }
 
   HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
@@ -157,7 +154,8 @@
   }
 
   DWORD bytes_written;
-  if (!WriteFile(stdout_handle, pickle.data(), pickle.size(), &bytes_written,
+  if (!WriteFile(stdout_handle, serialized_message.data(),
+                 serialized_message.data_num_bytes(), &bytes_written,
                  nullptr)) {
     PLOG(ERROR) << "Failed to write file chooser result";
   }
@@ -167,9 +165,10 @@
   // in case. Check that all bytes were written successfully, and return an
   // error code if not to signal the parent that it shouldn't try to parse the
   // output.
-  if (bytes_written != pickle.size()) {
+  if (bytes_written != serialized_message.data_num_bytes()) {
     LOG(ERROR) << "Failed to write all bytes to pipe. (Buffer full?) Expected: "
-               << pickle.size() << " Actual: " << bytes_written;
+               << serialized_message.data_num_bytes()
+               << " Actual: " << bytes_written;
     return EXIT_FAILURE;
   }
 
diff --git a/remoting/host/file_transfer/file_chooser_win.cc b/remoting/host/file_transfer/file_chooser_win.cc
index b1c2b131..578d7cf 100644
--- a/remoting/host/file_transfer/file_chooser_win.cc
+++ b/remoting/host/file_transfer/file_chooser_win.cc
@@ -9,6 +9,7 @@
 
 #include <cstdlib>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -18,12 +19,11 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/win/object_watcher.h"
 #include "base/win/scoped_handle.h"
-#include "ipc/ipc_message_utils.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/base/switches.h"
-#include "remoting/host/chromoting_param_traits.h"
-#include "remoting/host/chromoting_param_traits_impl.h"
 #include "remoting/host/file_transfer/file_chooser_common_win.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 
 namespace remoting {
 
@@ -156,10 +156,10 @@
   }
   process_.Close();
 
-  char raw_response[kFileChooserPipeBufferSize];
+  std::vector<uint8_t> response_bytes(kFileChooserPipeBufferSize);
   DWORD bytes_read;
-  if (!PeekNamedPipe(pipe_read_.Get(), raw_response, sizeof(raw_response),
-                     &bytes_read, nullptr, nullptr)) {
+  if (!PeekNamedPipe(pipe_read_.Get(), response_bytes.data(),
+                     response_bytes.size(), &bytes_read, nullptr, nullptr)) {
     PLOG(ERROR) << "Failed to read response from pipe";
     std::move(callback_).Run(MakeFileTransferError(
         FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR,
@@ -167,10 +167,13 @@
     return;
   }
 
+  mojo::Message serialized_message(
+      base::span<uint8_t>(response_bytes.begin(), bytes_read),
+      base::span<mojo::ScopedHandle>());
+
   FileChooser::Result result;
-  base::Pickle pickle(raw_response, bytes_read);
-  base::PickleIterator iterator(pickle);
-  if (!IPC::ReadParam(&pickle, &iterator, &result)) {
+  if (!mojom::FileChooserResult::DeserializeFromMessage(
+          std::move(serialized_message), &result)) {
     LOG(ERROR) << "Failed to deserialize response.";
     std::move(callback_).Run(MakeFileTransferError(
         FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
diff --git a/remoting/host/file_transfer/file_operations.h b/remoting/host/file_transfer/file_operations.h
index 6b9e94ec..d0fbacb 100644
--- a/remoting/host/file_transfer/file_operations.h
+++ b/remoting/host/file_transfer/file_operations.h
@@ -86,7 +86,7 @@
     // complete.
     virtual void Open(const base::FilePath& filename, Callback callback) = 0;
 
-    // Writes a chuck to the file. Chunks cannot be queued; the caller must
+    // Writes a chunk to the file. Chunks cannot be queued; the caller must
     // wait until callback is called before calling WriteChunk again or calling
     // Close.
     virtual void WriteChunk(std::vector<std::uint8_t> data,
diff --git a/remoting/host/file_transfer/file_transfer_message_handler.cc b/remoting/host/file_transfer/file_transfer_message_handler.cc
index 96c3b89..a8a0279f 100644
--- a/remoting/host/file_transfer/file_transfer_message_handler.cc
+++ b/remoting/host/file_transfer/file_transfer_message_handler.cc
@@ -169,6 +169,9 @@
 void FileTransferMessageHandler::OnSuccess() {
   DCHECK_EQ(kEof, state_);
   SetState(kClosed);
+
+  // Ensure any resources tied to the reader's lifetime are released.
+  file_reader_.reset();
 }
 
 void FileTransferMessageHandler::OnError(protocol::FileTransfer_Error error) {
diff --git a/remoting/host/file_transfer/ipc_file_operations.cc b/remoting/host/file_transfer/ipc_file_operations.cc
index 7fc55336..c2e7ab2 100644
--- a/remoting/host/file_transfer/ipc_file_operations.cc
+++ b/remoting/host/file_transfer/ipc_file_operations.cc
@@ -5,19 +5,80 @@
 #include "remoting/host/file_transfer/ipc_file_operations.h"
 
 #include <cstdint>
+#include <memory>
 #include <utility>
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/protocol/file_transfer_helpers.h"
 
 namespace remoting {
 
+// This is an overview of how IpcFileOperations is integrated and used in the
+// multi-process host architecture. Reasoning about the lifetime and ownership
+// of the various pieces currently requires digging through the code so this
+// comment block describes the relationships and pieces involved at a high-level
+// to help those looking to understand the code.
+//
+// The IpcFileOperations and related classes are all used in the low-privilege
+// network process. They handle network communication with the website client
+// over a WebRTC data channel and proxy those requests using Mojo to the
+// SessionFileOperationsHandler (and friends) which lives in the high-privilege
+// desktop process and handles the actual file reading and writing.
+//
+// When a new file transfer data channel is opened by the client, the
+// ClientSession instance on the host (running in the network process) will
+// create a FileTransferMessageHandler (FTMH) instance to service it. As part of
+// the FTMH creation, ClientSession will ask the IpcDesktopEnvironment to create
+// a new IpcFileOperations instance. This instance will be provided with a
+// WeakPtr<IpcFileOperations::RequestHandler> which is used to start a file read
+// or write operation in the desktop process over an existing IPC channel owned
+// by the DesktopSessionProxy.
+//
+// After the FTMH receives the initial message indicating the type of operation
+// to perform, it creates an IpcFileReader or an IpcFileWriter instance. The
+// IpcFile{Reader|Writer} begins an operation by calling the appropriate method
+// on the IpcFileOperations::RequestHandler interface. This interface is
+// implemented by the DesktopSessionProxy (DSP) which in turn calls the
+// DesktopSessionAgent (DSA) via its mojom::DesktopSessionControl remote. The
+// DSA passes the request to its SessionFileOperationsHandler instance which, if
+// successful, will create a new IPC channel for the transfer and return a
+// remote to the IpcFile{Reader|Writer} to allow it to proceed with the file
+// operation. The receiver is owned by a MojoFileReader or MojoFileWriter
+// instance whose lifetime is tied to the Mojo channel meaning the
+// MojoFile{Reader|Writer} will be destroyed when the channel is disconnected.
+//
+// The lifetime of an FTMH instance is tied to the WebRTC file transfer data
+// channel that it was created to service. Each data channel exists for one
+// transfer request, so once the operation completes, or encounters an error,
+// the IpcFileOperations instance and the IpcFile{Reader|Writer} it created will
+// be destroyed (this will also trigger destruction of a MojoFile{Reader|Writer}
+// in the desktop process).
+//
+// The lifetime of the DesktopSessionProxy is a bit harder to reason about as a
+// number of classes and callbacks hold a scoped_refptr reference to it. At the
+// very earliest, the DSP will be destroyed when the chromoting session is
+// terminated. When this occurs, the scoped_refptr in ClientSession is released
+// and the IpcDesktopEnvironment and IpcFileOperationsFactory are destroyed.
+//
+// Because of the objects involved, the two UaF concerns are:
+// - Calling into |request_handler_| after the DSP has been destroyed.
+//   This is unlikely given that a DSP lasts for the entire session but it
+//   could occur if the timing was just right near the end of a session.
+//   Mitigation: |request_handler_| is wrapped in a WeakPtr and provided to each
+//               IpcFile{Reader|Writer} instance.
+// - The DSP could invoke a disconnect_handler on the IpcFile{Reader|Writer} if
+//   the file transfer request was canceled just after the operation started.
+//   Mitigation: The disconnect_handler callback provided to the BeginFileRead
+//               BeginFileWrite method is bound with a WeakPtr.
 class IpcFileOperations::IpcReader : public FileOperations::Reader {
  public:
-  IpcReader(std::uint64_t file_id, base::WeakPtr<SharedState> shared_state);
+  explicit IpcReader(base::WeakPtr<RequestHandler> request_handler);
 
   IpcReader(const IpcReader&) = delete;
   IpcReader& operator=(const IpcReader&) = delete;
@@ -31,20 +92,30 @@
   std::uint64_t size() const override;
   State state() const override;
 
- private:
-  void OnOpenResult(OpenCallback callback, ResultHandler::InfoResult result);
-  void OnReadResult(ReadCallback callback, ResultHandler::DataResult result);
+  void OnChannelDisconnected();
 
-  State state_ = kCreated;
-  std::uint64_t file_id_;
-  base::FilePath filename_;
-  std::uint64_t size_ = 0;
-  base::WeakPtr<SharedState> shared_state_;
+  base::WeakPtr<IpcReader> GetWeakPtr() const;
+
+ private:
+  void OnOpenResult(mojom::BeginFileReadResultPtr result);
+  void OnReadResult(
+      const protocol::FileTransferResult<std::vector<std::uint8_t>>& result);
+
+  State state_ GUARDED_BY_CONTEXT(sequence_checker_) = kCreated;
+  base::FilePath filename_ GUARDED_BY_CONTEXT(sequence_checker_);
+  std::uint64_t size_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
+  OpenCallback pending_open_callback_ GUARDED_BY_CONTEXT(sequence_checker_);
+  ReadCallback pending_read_callback_ GUARDED_BY_CONTEXT(sequence_checker_);
+  base::WeakPtr<IpcFileOperations::RequestHandler> request_handler_;
+  mojo::AssociatedRemote<mojom::FileReader> remote_file_reader_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<IpcReader> weak_ptr_factory_{this};
 };
 
 class IpcFileOperations::IpcWriter : public FileOperations::Writer {
  public:
-  IpcWriter(std::uint64_t file_id, base::WeakPtr<SharedState> shared_state);
+  explicit IpcWriter(base::WeakPtr<RequestHandler> request_handler);
 
   IpcWriter(const IpcWriter&) = delete;
   IpcWriter& operator=(const IpcWriter&) = delete;
@@ -57,308 +128,309 @@
   void Close(Callback callback) override;
   State state() const override;
 
- private:
-  void OnOperationResult(Callback callback, ResultHandler::Result result);
-  void OnCloseResult(Callback callback, ResultHandler::Result result);
+  void OnChannelDisconnected();
 
-  State state_ = kCreated;
-  std::uint64_t file_id_;
-  base::WeakPtr<SharedState> shared_state_;
+  base::WeakPtr<IpcWriter> GetWeakPtr() const;
+
+ private:
+  void OnOpenResult(mojom::BeginFileWriteResultPtr result);
+  void OnOperationResult(
+      const absl::optional<::remoting::protocol::FileTransfer_Error>& error);
+  void OnCloseResult(
+      const absl::optional<::remoting::protocol::FileTransfer_Error>& error);
+
+  State state_ GUARDED_BY_CONTEXT(sequence_checker_) = kCreated;
+  Callback pending_callback_ GUARDED_BY_CONTEXT(sequence_checker_);
+  base::WeakPtr<IpcFileOperations::RequestHandler> request_handler_;
+  mojo::AssociatedRemote<mojom::FileWriter> remote_file_writer_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<IpcWriter> weak_ptr_factory_{this};
 };
 
-IpcFileOperations::IpcFileOperations(base::WeakPtr<SharedState> shared_state)
-    : shared_state_(std::move(shared_state)) {}
+IpcFileOperations::IpcFileOperations(
+    base::WeakPtr<RequestHandler> request_handler)
+    : request_handler_(std::move(request_handler)) {}
 
 IpcFileOperations::~IpcFileOperations() = default;
 
 std::unique_ptr<FileOperations::Reader> IpcFileOperations::CreateReader() {
-  return std::make_unique<IpcReader>(GetNextFileId(), shared_state_);
+  return std::make_unique<IpcReader>(request_handler_);
 }
 
 std::unique_ptr<FileOperations::Writer> IpcFileOperations::CreateWriter() {
-  return std::make_unique<IpcWriter>(GetNextFileId(), shared_state_);
+  return std::make_unique<IpcWriter>(request_handler_);
 }
 
-std::uint64_t IpcFileOperations::GetNextFileId() {
-  // If shared_state_ is invalid, it means the connection is being torn down.
-  // Using a dummy id is okay in that case, as the IpcReader/IpcWriter won't
-  // actually do anything with an invalid shared_state_, and our call should be
-  // torn down soon, as well.
-  return shared_state_ ? shared_state_->next_file_id++ : 0;
-}
-
-IpcFileOperations::SharedState::SharedState(RequestHandler* request_handler)
-    : request_handler(request_handler) {}
-
-void IpcFileOperations::SharedState::Abort(std::uint64_t file_id) {
-  request_handler->Cancel(file_id);
-
-  protocol::FileTransfer_Error error = protocol::MakeFileTransferError(
-      FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR);
-
-  // Any given file_id is expected to have at most one callback at a time, so
-  // the order in which we search the maps is arbitrary.
-
-  auto callback_iter = result_callbacks.find(file_id);
-  if (callback_iter != result_callbacks.end()) {
-    IpcFileOperations::ResultCallback callback =
-        std::move(callback_iter->second);
-    result_callbacks.erase(callback_iter);
-    std::move(callback).Run(error);
-  }
-
-  auto info_callback_iter = info_result_callbacks.find(file_id);
-  if (info_callback_iter != info_result_callbacks.end()) {
-    IpcFileOperations::InfoResultCallback info_callback =
-        std::move(info_callback_iter->second);
-    info_result_callbacks.erase(info_callback_iter);
-    std::move(info_callback).Run(error);
-  }
-
-  auto data_callback_iter = data_result_callbacks.find(file_id);
-  if (data_callback_iter != data_result_callbacks.end()) {
-    IpcFileOperations::DataResultCallback data_callback =
-        std::move(data_callback_iter->second);
-    data_result_callbacks.erase(data_callback_iter);
-    std::move(data_callback).Run(error);
-  }
-}
-
-IpcFileOperations::SharedState::~SharedState() = default;
-
 IpcFileOperationsFactory::IpcFileOperationsFactory(
     IpcFileOperations::RequestHandler* request_handler)
-    : shared_state_(request_handler) {}
+    : request_handler_weak_ptr_factory_(request_handler) {}
 
 IpcFileOperationsFactory::~IpcFileOperationsFactory() = default;
 
 std::unique_ptr<FileOperations>
 IpcFileOperationsFactory::CreateFileOperations() {
   return base::WrapUnique(
-      new IpcFileOperations(shared_state_.weak_ptr_factory.GetWeakPtr()));
+      new IpcFileOperations(request_handler_weak_ptr_factory_.GetWeakPtr()));
 }
 
-void IpcFileOperationsFactory::OnResult(uint64_t file_id, Result result) {
-  auto callback_iter = shared_state_.result_callbacks.find(file_id);
-  if (callback_iter == shared_state_.result_callbacks.end()) {
-    shared_state_.Abort(file_id);
-    return;
-  }
+IpcFileOperations::IpcReader::IpcReader(
+    base::WeakPtr<RequestHandler> request_handler)
+    : request_handler_(std::move(request_handler)) {}
 
-  IpcFileOperations::ResultCallback callback = std::move(callback_iter->second);
-  shared_state_.result_callbacks.erase(callback_iter);
-  std::move(callback).Run(std::move(result));
-}
-
-void IpcFileOperationsFactory::OnInfoResult(std::uint64_t file_id,
-                                            InfoResult result) {
-  auto callback_iter = shared_state_.info_result_callbacks.find(file_id);
-  if (callback_iter == shared_state_.info_result_callbacks.end()) {
-    shared_state_.Abort(file_id);
-    return;
-  }
-
-  IpcFileOperations::InfoResultCallback callback =
-      std::move(callback_iter->second);
-  shared_state_.info_result_callbacks.erase(callback_iter);
-  std::move(callback).Run(std::move(result));
-}
-
-void IpcFileOperationsFactory::OnDataResult(std::uint64_t file_id,
-                                            DataResult result) {
-  auto callback_iter = shared_state_.data_result_callbacks.find(file_id);
-  if (callback_iter == shared_state_.data_result_callbacks.end()) {
-    shared_state_.Abort(file_id);
-    return;
-  }
-
-  IpcFileOperations::DataResultCallback callback =
-      std::move(callback_iter->second);
-  shared_state_.data_result_callbacks.erase(callback_iter);
-  std::move(callback).Run(std::move(result));
-}
-
-IpcFileOperations::IpcReader::IpcReader(std::uint64_t file_id,
-                                        base::WeakPtr<SharedState> shared_state)
-    : file_id_(file_id), shared_state_(std::move(shared_state)) {}
-
-IpcFileOperations::IpcReader::~IpcReader() {
-  if (!shared_state_ || state_ == kCreated || state_ == kComplete ||
-      state_ == kFailed) {
-    return;
-  }
-
-  shared_state_->request_handler->Cancel(file_id_);
-
-  // Destroy any pending callbacks.
-  auto info_callback_iter = shared_state_->info_result_callbacks.find(file_id_);
-  if (info_callback_iter != shared_state_->info_result_callbacks.end()) {
-    shared_state_->info_result_callbacks.erase(info_callback_iter);
-  }
-
-  auto data_callback_iter = shared_state_->data_result_callbacks.find(file_id_);
-  if (data_callback_iter != shared_state_->data_result_callbacks.end()) {
-    shared_state_->data_result_callbacks.erase(data_callback_iter);
-  }
-}
+IpcFileOperations::IpcReader::~IpcReader() = default;
 
 void IpcFileOperations::IpcReader::Open(OpenCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(kCreated, state_);
-  if (!shared_state_) {
+
+  if (!request_handler_) {
+    state_ = kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
     return;
   }
 
   state_ = kBusy;
-  // Unretained is sound because we destroy any pending callbacks in our
-  // destructor.
-  shared_state_->info_result_callbacks.emplace(
-      file_id_, base::BindOnce(&IpcReader::OnOpenResult, base::Unretained(this),
-                               std::move(callback)));
-  shared_state_->request_handler->ReadFile(file_id_);
+  pending_open_callback_ = std::move(callback);
+  request_handler_->BeginFileRead(
+      base::BindOnce(&IpcReader::OnOpenResult, GetWeakPtr()),
+      base::BindOnce(&IpcReader::OnChannelDisconnected, GetWeakPtr()));
 }
 
-void IpcFileOperations::IpcReader::ReadChunk(
-    std::size_t size,
-    FileOperations::Reader::ReadCallback callback) {
-  DCHECK_EQ(kReady, state_);
-  if (!shared_state_) {
+void IpcFileOperations::IpcReader::ReadChunk(std::size_t size,
+                                             ReadCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (state_ != kReady || !remote_file_reader_.is_connected()) {
+    state_ = kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
     return;
   }
 
   state_ = kBusy;
-  // Unretained is sound because we destroy any pending callbacks in our
-  // destructor.
-  shared_state_->data_result_callbacks.emplace(
-      file_id_, base::BindOnce(&IpcReader::OnReadResult, base::Unretained(this),
-                               std::move(callback)));
-  shared_state_->request_handler->ReadChunk(file_id_, size);
+  pending_read_callback_ = std::move(callback);
+  // Unretained is sound because the remote is owned by this instance and will
+  // be destroyed at the same time which will clear any callbacks.
+  remote_file_reader_->ReadChunk(
+      size, base::BindOnce(&IpcReader::OnReadResult, base::Unretained(this)));
 }
 
 const base::FilePath& IpcFileOperations::IpcReader::filename() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return filename_;
 }
 
 std::uint64_t IpcFileOperations::IpcReader::size() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return size_;
 }
 
 FileOperations::State IpcFileOperations::IpcReader::state() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return state_;
 }
 
+void IpcFileOperations::IpcReader::OnChannelDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  state_ = kFailed;
+
+  if (pending_open_callback_) {
+    std::move(pending_open_callback_)
+        .Run(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  } else if (pending_read_callback_) {
+    std::move(pending_read_callback_)
+        .Run(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  }
+}
+
+base::WeakPtr<IpcFileOperations::IpcReader>
+IpcFileOperations::IpcReader::GetWeakPtr() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void IpcFileOperations::IpcReader::OnOpenResult(
-    OpenCallback callback,
-    ResultHandler::InfoResult result) {
-  if (!result) {
+    mojom::BeginFileReadResultPtr result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (result->is_error()) {
     state_ = kFailed;
-    std::move(callback).Run(result.error());
+    std::move(pending_open_callback_).Run(result->get_error());
     return;
   }
 
   state_ = kReady;
-  filename_ = std::move(std::get<0>(*result));
-  size_ = std::move(std::get<1>(*result));
-  std::move(callback).Run(kSuccessTag);
+  auto& success_ptr = result->get_success();
+  filename_ = std::move(success_ptr->filename);
+  size_ = success_ptr->size;
+
+  remote_file_reader_.Bind(std::move(success_ptr->file_reader));
+  // base::Unretained is sound because this instance owns |remote_file_reader_|
+  // and the handler will not run after it is destroyed.
+  remote_file_reader_.set_disconnect_handler(base::BindOnce(
+      &IpcReader::OnChannelDisconnected, base::Unretained(this)));
+
+  std::move(pending_open_callback_).Run(kSuccessTag);
 }
 
 void IpcFileOperations::IpcReader::OnReadResult(
-    ReadCallback callback,
-    ResultHandler::DataResult result) {
+    const protocol::FileTransferResult<std::vector<std::uint8_t>>& result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (result) {
     state_ = result->size() == 0 ? kComplete : kReady;
   } else {
     state_ = kFailed;
   }
-  std::move(callback).Run(std::move(result));
-}
 
-IpcFileOperations::IpcWriter::IpcWriter(std::uint64_t file_id,
-                                        base::WeakPtr<SharedState> shared_state)
-    : file_id_(file_id), shared_state_(std::move(shared_state)) {}
-
-IpcFileOperations::IpcWriter::~IpcWriter() {
-  if (!shared_state_ || state_ == kCreated || state_ == kComplete ||
-      state_ == kFailed) {
-    return;
+  if (state_ != kReady) {
+    // Don't need the remote if we're done or an error occurred.
+    remote_file_reader_.reset();
   }
 
-  shared_state_->request_handler->Cancel(file_id_);
-
-  // Destroy any pending callbacks.
-  auto callback_iter = shared_state_->result_callbacks.find(file_id_);
-  if (callback_iter != shared_state_->result_callbacks.end()) {
-    shared_state_->result_callbacks.erase(callback_iter);
-  }
+  std::move(pending_read_callback_).Run(std::move(result));
 }
 
+IpcFileOperations::IpcWriter::IpcWriter(
+    base::WeakPtr<RequestHandler> request_handler)
+    : request_handler_(std::move(request_handler)) {}
+
+IpcFileOperations::IpcWriter::~IpcWriter() = default;
+
 void IpcFileOperations::IpcWriter::Open(const base::FilePath& filename,
                                         Callback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(kCreated, state_);
-  if (!shared_state_) {
+
+  if (!request_handler_) {
+    state_ = kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
     return;
   }
 
   state_ = kBusy;
-  shared_state_->result_callbacks.emplace(
-      file_id_, base::BindOnce(&IpcWriter::OnOperationResult,
-                               base::Unretained(this), std::move(callback)));
-  shared_state_->request_handler->WriteFile(file_id_, filename);
+  pending_callback_ = std::move(callback);
+  request_handler_->BeginFileWrite(
+      filename, base::BindOnce(&IpcWriter::OnOpenResult, GetWeakPtr()),
+      base::BindOnce(&IpcWriter::OnChannelDisconnected, GetWeakPtr()));
 }
 
 void IpcFileOperations::IpcWriter::WriteChunk(std::vector<std::uint8_t> data,
                                               Callback callback) {
-  DCHECK_EQ(kReady, state_);
-  if (!shared_state_) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (state_ != kReady) {
+    state_ = kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
     return;
   }
 
   state_ = kBusy;
-  // Unretained is sound because IpcWriter will destroy any outstanding callback
-  // in its destructor.
-  shared_state_->result_callbacks.emplace(
-      file_id_, base::BindOnce(&IpcWriter::OnOperationResult,
-                               base::Unretained(this), std::move(callback)));
-  shared_state_->request_handler->WriteChunk(file_id_, std::move(data));
+  pending_callback_ = std::move(callback);
+  // Unretained is sound because the remote is owned by this instance and will
+  // be destroyed at the same time which will clear this callback.
+  remote_file_writer_->WriteChunk(
+      std::move(data),
+      base::BindOnce(&IpcWriter::OnOperationResult, base::Unretained(this)));
 }
 
 void IpcFileOperations::IpcWriter::Close(Callback callback) {
-  DCHECK_EQ(kReady, state_);
-  if (!shared_state_) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (state_ != kReady) {
+    state_ = kFailed;
+    std::move(callback).Run(protocol::MakeFileTransferError(
+        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
     return;
   }
 
   state_ = kBusy;
-  shared_state_->request_handler->Close(file_id_);
-  // Unretained is sound because IpcWriter will destroy any outstanding callback
-  // in its destructor.
-  shared_state_->result_callbacks.emplace(
-      file_id_, base::BindOnce(&IpcWriter::OnCloseResult,
-                               base::Unretained(this), std::move(callback)));
+  pending_callback_ = std::move(callback);
+  // Unretained is sound because the remote is owned by this instance and will
+  // be destroyed at the same time which will clear this callback.
+  remote_file_writer_->CloseFile(
+      base::BindOnce(&IpcWriter::OnCloseResult, base::Unretained(this)));
 }
 
 FileOperations::State IpcFileOperations::IpcWriter::state() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return state_;
 }
 
-void IpcFileOperations::IpcWriter::OnOperationResult(
-    Callback callback,
-    ResultHandler::Result result) {
-  if (result) {
-    state_ = kReady;
-  } else {
-    state_ = kFailed;
+void IpcFileOperations::IpcWriter::OnChannelDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  state_ = kFailed;
+
+  if (pending_callback_) {
+    std::move(pending_callback_)
+        .Run(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
   }
-  std::move(callback).Run(std::move(result));
 }
 
-void IpcFileOperations::IpcWriter::OnCloseResult(Callback callback,
-                                                 ResultHandler::Result result) {
-  if (result) {
-    state_ = kComplete;
-  } else {
+base::WeakPtr<IpcFileOperations::IpcWriter>
+IpcFileOperations::IpcWriter::GetWeakPtr() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+void IpcFileOperations::IpcWriter::OnOpenResult(
+    mojom::BeginFileWriteResultPtr result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (result->is_error()) {
     state_ = kFailed;
+    std::move(pending_callback_).Run(result->get_error());
+    return;
   }
-  std::move(callback).Run(std::move(result));
+
+  state_ = kReady;
+  auto& success_ptr = result->get_success();
+  remote_file_writer_.Bind(std::move(success_ptr->file_writer));
+  // base::Unretained is sound because this instance owns |remote_file_writer_|
+  // and the handler will not run after it is destroyed.
+  remote_file_writer_.set_disconnect_handler(base::BindOnce(
+      &IpcWriter::OnChannelDisconnected, base::Unretained(this)));
+
+  std::move(pending_callback_).Run(kSuccessTag);
+}
+
+void IpcFileOperations::IpcWriter::OnOperationResult(
+    const absl::optional<::remoting::protocol::FileTransfer_Error>& error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (error) {
+    state_ = kFailed;
+    std::move(pending_callback_).Run(std::move(*error));
+    remote_file_writer_.reset();
+    return;
+  }
+
+  state_ = kReady;
+  std::move(pending_callback_).Run({kSuccessTag});
+}
+
+void IpcFileOperations::IpcWriter::OnCloseResult(
+    const absl::optional<::remoting::protocol::FileTransfer_Error>& error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // We're done with the remote regardless of the result.
+  remote_file_writer_.reset();
+
+  if (error) {
+    state_ = kFailed;
+    std::move(pending_callback_).Run(std::move(*error));
+  } else {
+    state_ = kComplete;
+    std::move(pending_callback_).Run({kSuccessTag});
+  }
 }
 
 }  // namespace remoting
diff --git a/remoting/host/file_transfer/ipc_file_operations.h b/remoting/host/file_transfer/ipc_file_operations.h
index 09d848c..5f2c4e4 100644
--- a/remoting/host/file_transfer/ipc_file_operations.h
+++ b/remoting/host/file_transfer/ipc_file_operations.h
@@ -5,54 +5,37 @@
 #ifndef REMOTING_HOST_FILE_TRANSFER_IPC_FILE_OPERATIONS_H_
 #define REMOTING_HOST_FILE_TRANSFER_IPC_FILE_OPERATIONS_H_
 
-#include <cstdint>
-#include <tuple>
-#include <vector>
+#include <memory>
 
-#include "base/containers/flat_map.h"
-#include "base/memory/raw_ptr.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "remoting/host/file_transfer/file_operations.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/protocol/file_transfer_helpers.h"
 
 namespace remoting {
 
-// Implementation of FileOperations that translates the interface into easy-to-
-// serialize requests that can be forwarded over an IPC channel.
-// IpcFileOperationsRequestHandlerImpl can be used on the remote end to
-// perform the requested operations.
+// Implementation of FileOperations that forwards a file read or write request
+// over a Mojo channel.
 class IpcFileOperations : public FileOperations {
  public:
-  // Handles requests generated by IpcFileOperations instances by either
-  // performing the requested operations directly or forwarding them to another
-  // process.
+  using BeginFileReadCallback =
+      mojom::DesktopSessionControl::BeginFileReadCallback;
+  using BeginFileWriteCallback =
+      mojom::DesktopSessionControl::BeginFileWriteCallback;
+
+  // Handles requests from an IpcFileOperations instance by forwarding them to
+  // another process via Mojo IPC.
   class RequestHandler {
    public:
     virtual ~RequestHandler() = default;
 
-    virtual void ReadFile(std::uint64_t file_id) = 0;
-    virtual void ReadChunk(std::uint64_t file_id, std::uint64_t size) = 0;
-    virtual void WriteFile(std::uint64_t file_id,
-                           const base::FilePath& filename) = 0;
-    virtual void WriteChunk(std::uint64_t file_id,
-                            std::vector<std::uint8_t> data) = 0;
-    virtual void Close(std::uint64_t file_id) = 0;
-    virtual void Cancel(std::uint64_t file_id) = 0;
-  };
-
-  // Handles responses to file operations requests.
-  class ResultHandler {
-   public:
-    using Result = protocol::FileTransferResult<absl::monostate>;
-    using InfoResult =
-        protocol::FileTransferResult<std::tuple<base::FilePath, uint64_t>>;
-    using DataResult =
-        remoting::protocol::FileTransferResult<std::vector<std::uint8_t>>;
-
-    virtual ~ResultHandler() = default;
-    virtual void OnResult(std::uint64_t file_id, Result result) = 0;
-    virtual void OnInfoResult(std::uint64_t file_id, InfoResult result) = 0;
-    virtual void OnDataResult(std::uint64_t file_id, DataResult result) = 0;
+    virtual void BeginFileRead(BeginFileReadCallback callback,
+                               base::OnceClosure on_disconnect) = 0;
+    virtual void BeginFileWrite(const base::FilePath& file_path,
+                                BeginFileWriteCallback callback,
+                                base::OnceClosure on_disconnect) = 0;
   };
 
   IpcFileOperations(const IpcFileOperations&) = delete;
@@ -65,51 +48,12 @@
   std::unique_ptr<Writer> CreateWriter() override;
 
  private:
-  using ResultCallback = base::OnceCallback<void(ResultHandler::Result)>;
-  using InfoResultCallback =
-      base::OnceCallback<void(ResultHandler::InfoResult)>;
-  using DataResultCallback =
-      base::OnceCallback<void(ResultHandler::DataResult)>;
-
   class IpcReader;
   class IpcWriter;
 
-  struct SharedState {
-   public:
-    explicit SharedState(RequestHandler* request_handler);
+  explicit IpcFileOperations(base::WeakPtr<RequestHandler> request_handler);
 
-    SharedState(const SharedState&) = delete;
-    SharedState& operator=(const SharedState&) = delete;
-
-    ~SharedState();
-
-    // Send a Cancel request for |file_id| and provide an error response to any
-    // pending response callbacks for it. Called in the event of an unexpected
-    // message from the Desktop process.
-    void Abort(std::uint64_t file_id);
-
-    // File ID to use for the next file opened.
-    std::uint64_t next_file_id = 0;
-
-    // Pending callbacks awaiting responses from the desktop process, keyed by
-    // the file_id of the waiting Reader or Writer.
-    base::flat_map<std::uint64_t, ResultCallback> result_callbacks;
-    base::flat_map<std::uint64_t, InfoResultCallback> info_result_callbacks;
-    base::flat_map<std::uint64_t, DataResultCallback> data_result_callbacks;
-
-    // The associated RequestHandler.
-    raw_ptr<RequestHandler> request_handler;
-
-    base::WeakPtrFactory<SharedState> weak_ptr_factory{this};
-  };
-
-  explicit IpcFileOperations(base::WeakPtr<SharedState> shared_state);
-
-  std::uint64_t GetNextFileId();
-
-  // Contains shared state used by all instances tied to a given
-  // RequestHandler.
-  base::WeakPtr<SharedState> shared_state_;
+  base::WeakPtr<RequestHandler> request_handler_;
 
   friend class IpcFileOperationsFactory;
 };
@@ -117,27 +61,22 @@
 // Creates IpcFileOperations instances for a given RequestHandler. All
 // IpcFileOperations instances for the RequestHandler must be created through
 // the same IpcFileOperationsFactory.
-class IpcFileOperationsFactory : public IpcFileOperations::ResultHandler {
+class IpcFileOperationsFactory {
  public:
-  // |request_handler| must be valid for the entire lifetime of
-  // IpcFileOperationsFactory, and must only be used to construct a single
-  // IpcFileOperationsFactory to avoid file ID conflicts.
-  IpcFileOperationsFactory(IpcFileOperations::RequestHandler* request_handler);
+  // |request_handler| must outlive the IpcFileOperationsFactory instance.
+  explicit IpcFileOperationsFactory(
+      IpcFileOperations::RequestHandler* request_handler);
 
   IpcFileOperationsFactory(const IpcFileOperationsFactory&) = delete;
   IpcFileOperationsFactory& operator=(const IpcFileOperationsFactory&) = delete;
 
-  ~IpcFileOperationsFactory() override;
+  ~IpcFileOperationsFactory();
 
   std::unique_ptr<FileOperations> CreateFileOperations();
 
-  // ResultHandler implementation.
-  void OnResult(std::uint64_t file_id, Result result) override;
-  void OnInfoResult(std::uint64_t file_id, InfoResult result) override;
-  void OnDataResult(std::uint64_t file_id, DataResult result) override;
-
  private:
-  IpcFileOperations::SharedState shared_state_;
+  base::WeakPtrFactory<IpcFileOperations::RequestHandler>
+      request_handler_weak_ptr_factory_;
 };
 
 }  // namespace remoting
diff --git a/remoting/host/file_transfer/ipc_file_operations_unittest.cc b/remoting/host/file_transfer/ipc_file_operations_unittest.cc
index 4a3ec29..31df7db 100644
--- a/remoting/host/file_transfer/ipc_file_operations_unittest.cc
+++ b/remoting/host/file_transfer/ipc_file_operations_unittest.cc
@@ -12,97 +12,249 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "base/containers/queue.h"
+#include "base/callback_list.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/test/bind.h"
 #include "base/test/scoped_path_override.h"
 #include "base/test/task_environment.h"
 #include "remoting/host/file_transfer/fake_file_chooser.h"
 #include "remoting/host/file_transfer/local_file_operations.h"
 #include "remoting/host/file_transfer/session_file_operations_handler.h"
 #include "remoting/host/file_transfer/test_byte_vector_utils.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
+#include "remoting/proto/file_transfer.pb.h"
+#include "remoting/protocol/file_transfer_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace remoting {
 
+// Forward declare to allow for friending by the fake test classes.
+class IpcFileOperationsTest;
+
 namespace {
 
-// BindOnce disallows binding lambdas with captures. This is reasonable in
-// production code, as it requires one to either explicitly pass owned objects
-// or pointers using Owned, Unretained, et cetera. This helps to avoid use after
-// free bugs in async code. In test code, though, where the lambda is
-// immediately invoked in the test method using, e.g., RunUntilIdle, the ability
-// to capture can make the code much easier to read and write.
-template <typename T>
-auto BindLambda(T lambda) {
-  return base::BindOnce(&T::operator(),
-                        base::Owned(new auto(std::move(lambda))));
-}
-
-class IpcTestBridge : public IpcFileOperations::RequestHandler,
-                      public IpcFileOperations::ResultHandler,
-                      public FileOperations {
+// A simplified DesktopSessionProxy implementation for file transfer testing.
+class FakeDesktopSessionProxy : public IpcFileOperations::RequestHandler {
  public:
-  explicit IpcTestBridge(
-      scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
-      : ipc_file_operations_factory_(this),
-        session_file_operations_handler_(
-            this,
-            std::make_unique<LocalFileOperations>(std::move(ui_task_runner))),
-        file_operations_(ipc_file_operations_factory_.CreateFileOperations()) {}
+  FakeDesktopSessionProxy() = default;
 
-  IpcTestBridge(const IpcTestBridge&) = delete;
-  IpcTestBridge& operator=(const IpcTestBridge&) = delete;
+  FakeDesktopSessionProxy(const FakeDesktopSessionProxy&) = delete;
+  FakeDesktopSessionProxy& operator=(const FakeDesktopSessionProxy&) = delete;
 
-  ~IpcTestBridge() override = default;
+  ~FakeDesktopSessionProxy() override = default;
 
   // IpcFileOperations::RequestHandler implementation.
-  void ReadFile(std::uint64_t file_id) override {
-    session_file_operations_handler_.ReadFile(file_id);
-  }
-  void ReadChunk(std::uint64_t file_id, std::uint64_t size) override {
-    session_file_operations_handler_.ReadChunk(file_id, size);
-  }
-  void WriteFile(std::uint64_t file_id,
-                 const base::FilePath& filename) override {
-    session_file_operations_handler_.WriteFile(file_id, filename);
-  }
-  void WriteChunk(std::uint64_t file_id,
-                  std::vector<std::uint8_t> data) override {
-    session_file_operations_handler_.WriteChunk(file_id, std::move(data));
-  }
-  void Close(std::uint64_t file_id) override {
-    session_file_operations_handler_.Close(file_id);
-  }
-  void Cancel(std::uint64_t file_id) override {
-    session_file_operations_handler_.Cancel(file_id);
-  }
+  void BeginFileRead(IpcFileOperations::BeginFileReadCallback callback,
+                     base::OnceClosure on_disconnect) override;
+  void BeginFileWrite(const base::FilePath& file_path,
+                      IpcFileOperations::BeginFileWriteCallback callback,
+                      base::OnceClosure on_disconnect) override;
 
-  // ResultHandler implementation.
-  void OnResult(std::uint64_t file_id, Result result) override {
-    ipc_file_operations_factory_.OnResult(file_id, std::move(result));
-  }
-  void OnInfoResult(std::uint64_t file_id, InfoResult result) override {
-    ipc_file_operations_factory_.OnInfoResult(file_id, std::move(result));
-  }
-  void OnDataResult(std::uint64_t file_id, DataResult result) override {
-    ipc_file_operations_factory_.OnDataResult(file_id, std::move(result));
-  }
+  // Binds the pending DesktopSessionControl remote to |remote_|.
+  void Bind(mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> remote);
 
-  // FileOperations implementation.
-  std::unique_ptr<Reader> CreateReader() override {
-    return file_operations_->CreateReader();
-  }
-  std::unique_ptr<Writer> CreateWriter() override {
-    return file_operations_->CreateWriter();
-  }
+  // When set, this instance will return |error| on the next IPC request.
+  void SetErrorForNextRequest(protocol::FileTransfer_Error error);
+
+  // Runs any registered disconnect handlers.
+  void TriggerDisconnectHandlers();
 
  private:
-  IpcFileOperationsFactory ipc_file_operations_factory_;
-  SessionFileOperationsHandler session_file_operations_handler_;
-  std::unique_ptr<FileOperations> file_operations_;
+  // This member mirrors the handler in the real DesktopSessionProxy class.
+  base::OnceClosureList disconnect_handlers_;
+
+  // Holds disconnect handler subscriptions until they are either triggered or
+  // destroyed along with this test instance.
+  std::vector<base::CallbackListSubscription> disconnect_subscriptions_;
+
+  // If set, this will be returned on the next file transfer operation request.
+  absl::optional<protocol::FileTransfer_Error> request_error_;
+
+  // Remote end of the DesktopSessionControl channel, the receiver is owned by
+  // a FakeDesktopSessionAgent instance.
+  mojo::AssociatedRemote<mojom::DesktopSessionControl> remote_;
 };
 
+void FakeDesktopSessionProxy::BeginFileRead(
+    IpcFileOperations::BeginFileReadCallback callback,
+    base::OnceClosure on_disconnect) {
+  if (request_error_) {
+    std::move(callback).Run(
+        mojom::BeginFileReadResult::NewError(std::move(*request_error_)));
+    return;
+  }
+
+  disconnect_subscriptions_.emplace_back(
+      disconnect_handlers_.Add(std::move(on_disconnect)));
+  remote_->BeginFileRead(std::move(callback));
+}
+
+void FakeDesktopSessionProxy::BeginFileWrite(
+    const base::FilePath& file_path,
+    IpcFileOperations::BeginFileWriteCallback callback,
+    base::OnceClosure on_disconnect) {
+  if (request_error_) {
+    std::move(callback).Run(
+        mojom::BeginFileWriteResult::NewError(std::move(*request_error_)));
+    return;
+  }
+  disconnect_subscriptions_.emplace_back(
+      disconnect_handlers_.Add(std::move(on_disconnect)));
+  remote_->BeginFileWrite(file_path, std::move(callback));
+}
+
+void FakeDesktopSessionProxy::Bind(
+    mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> remote) {
+  remote_.Bind(std::move(remote));
+  remote_.set_disconnect_handler(
+      base::BindOnce(&FakeDesktopSessionProxy::TriggerDisconnectHandlers,
+                     base::Unretained(this)));
+}
+
+void FakeDesktopSessionProxy::SetErrorForNextRequest(
+    protocol::FileTransfer_Error error) {
+  request_error_ = std::move(error);
+}
+
+void FakeDesktopSessionProxy::TriggerDisconnectHandlers() {
+  disconnect_handlers_.Notify();
+}
+
+// A simplified DesktopSessionAgent implementation for file transfer testing.
+class FakeDesktopSessionAgent : public mojom::DesktopSessionControl {
+ public:
+  explicit FakeDesktopSessionAgent(
+      scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
+
+  FakeDesktopSessionAgent(const FakeDesktopSessionAgent&) = delete;
+  FakeDesktopSessionAgent& operator=(const FakeDesktopSessionAgent&) = delete;
+
+  ~FakeDesktopSessionAgent() override = default;
+
+  // mojom::DesktopSessionControl implementation.
+  void CaptureFrame() override;
+  void SelectSource(int id) override;
+  void SetScreenResolution(const ScreenResolution& resolution) override;
+  void LockWorkstation() override;
+  void InjectSendAttentionSequence() override;
+  void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;
+  void InjectKeyEvent(const protocol::KeyEvent& event) override;
+  void InjectMouseEvent(const protocol::MouseEvent& event) override;
+  void InjectTextEvent(const protocol::TextEvent& event) override;
+  void InjectTouchEvent(const protocol::TouchEvent& event) override;
+  void SetUpUrlForwarder() override;
+  void SignalWebAuthnExtension() override;
+  void BeginFileRead(BeginFileReadCallback callback) override;
+  void BeginFileWrite(const base::FilePath& file_path,
+                      BeginFileWriteCallback callback) override;
+
+  // Binds the pending DesktopSessionControl receiver to |receiver_|.
+  void Bind(
+      mojo::PendingAssociatedReceiver<mojom::DesktopSessionControl> receiver);
+
+  // When set, this instance will return |error| for its next IPC response.
+  void SetErrorForNextResponse(protocol::FileTransfer_Error error);
+
+  // Disconnect |receiver_| after the next request is received. This is used to
+  // simulate an error while the FakeDesktopSessionProxy is waiting for a reply.
+  void DisconnectReceiverOnNextRequest();
+
+ private:
+  friend class ::remoting::IpcFileOperationsTest;
+
+  // If true, the agent will disconnect |receiver_| on the next IPC request.
+  bool disconnect_on_next_request_ = false;
+
+  // If set, |response_error_| will be returned in the next IPC response.
+  absl::optional<protocol::FileTransfer_Error> response_error_;
+
+  // Handles file transfer requests over Mojo and manages receiver lifetimes.
+  SessionFileOperationsHandler session_file_operations_handler_;
+
+  // Receiver end of the DesktopSessionControl channel, the remote is owned by a
+  // FakeDesktopSessionProxy instance.
+  mojo::AssociatedReceiver<mojom::DesktopSessionControl> receiver_{this};
+};
+
+FakeDesktopSessionAgent::FakeDesktopSessionAgent(
+    scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
+    : session_file_operations_handler_(
+          std::make_unique<LocalFileOperations>(std::move(ui_task_runner))) {}
+
+void FakeDesktopSessionAgent::CaptureFrame() {}
+
+void FakeDesktopSessionAgent::SelectSource(int id) {}
+
+void FakeDesktopSessionAgent::SetScreenResolution(
+    const ScreenResolution& resolution) {}
+
+void FakeDesktopSessionAgent::LockWorkstation() {}
+
+void FakeDesktopSessionAgent::InjectSendAttentionSequence() {}
+
+void FakeDesktopSessionAgent::InjectClipboardEvent(
+    const protocol::ClipboardEvent& event) {}
+
+void FakeDesktopSessionAgent::InjectKeyEvent(const protocol::KeyEvent& event) {}
+
+void FakeDesktopSessionAgent::InjectMouseEvent(
+    const protocol::MouseEvent& event) {}
+
+void FakeDesktopSessionAgent::InjectTextEvent(
+    const protocol::TextEvent& event) {}
+
+void FakeDesktopSessionAgent::InjectTouchEvent(
+    const protocol::TouchEvent& event) {}
+
+void FakeDesktopSessionAgent::SetUpUrlForwarder() {}
+
+void FakeDesktopSessionAgent::SignalWebAuthnExtension() {}
+
+void FakeDesktopSessionAgent::BeginFileRead(BeginFileReadCallback callback) {
+  if (disconnect_on_next_request_) {
+    disconnect_on_next_request_ = false;
+    receiver_.reset();
+    return;
+  }
+  if (response_error_) {
+    std::move(callback).Run(
+        mojom::BeginFileReadResult::NewError(std::move(*response_error_)));
+    return;
+  }
+  session_file_operations_handler_.BeginFileRead(std::move(callback));
+}
+
+void FakeDesktopSessionAgent::BeginFileWrite(const base::FilePath& file_path,
+                                             BeginFileWriteCallback callback) {
+  if (disconnect_on_next_request_) {
+    disconnect_on_next_request_ = false;
+    receiver_.reset();
+    return;
+  }
+  if (response_error_) {
+    std::move(callback).Run(
+        mojom::BeginFileWriteResult::NewError(std::move(*response_error_)));
+    return;
+  }
+  session_file_operations_handler_.BeginFileWrite(file_path,
+                                                  std::move(callback));
+}
+
+void FakeDesktopSessionAgent::Bind(
+    mojo::PendingAssociatedReceiver<mojom::DesktopSessionControl> receiver) {
+  receiver_.Bind(std::move(receiver));
+}
+
+void FakeDesktopSessionAgent::SetErrorForNextResponse(
+    protocol::FileTransfer_Error error) {
+  response_error_ = std::move(error);
+}
+
+void FakeDesktopSessionAgent::DisconnectReceiverOnNextRequest() {
+  disconnect_on_next_request_ = true;
+}
+
 }  // namespace
 
 class IpcFileOperationsTest : public testing::Test {
@@ -114,6 +266,8 @@
 
   ~IpcFileOperationsTest() override;
 
+  void SetUp() override;
+
  protected:
   const base::FilePath kTestFilename =
       base::FilePath::FromUTF8Unsafe("test-file.txt");
@@ -126,22 +280,57 @@
 
   base::FilePath TestDir();
 
+  size_t session_file_reader_count() const {
+    return fake_desktop_session_agent_.session_file_operations_handler_
+        .file_readers_.size();
+  }
+
+  size_t session_file_writer_count() const {
+    return fake_desktop_session_agent_.session_file_operations_handler_
+        .file_writers_.size();
+  }
+
+  // Destroys existing MojoFileReader and MojoFileWriter instances. This will
+  // also trigger any disconnect handlers set on the Mojo remote owned by the
+  // corresponding IpcFileReader / IpcFileWriter instances.
+  void clear_session_file_receivers() {
+    fake_desktop_session_agent_.session_file_operations_handler_.file_readers_
+        .Clear();
+    fake_desktop_session_agent_.session_file_operations_handler_.file_writers_
+        .Clear();
+  }
+
   // Points DIR_USER_DESKTOP at a scoped temporary directory.
-  base::ScopedPathOverride scoped_path_override_;
-  base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<FileOperations> file_operations_;
+  base::ScopedPathOverride scoped_path_override_{base::DIR_USER_DESKTOP};
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::DEFAULT,
+      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
+
+  FakeDesktopSessionProxy fake_desktop_session_proxy_;
+  FakeDesktopSessionAgent fake_desktop_session_agent_{
+      task_environment_.GetMainThreadTaskRunner()};
+
+  IpcFileOperationsFactory ipc_file_operations_factory_{
+      &fake_desktop_session_proxy_};
+
+  std::unique_ptr<FileOperations> file_operations_{
+      ipc_file_operations_factory_.CreateFileOperations()};
 };
 
-IpcFileOperationsTest::IpcFileOperationsTest()
-    : scoped_path_override_(base::DIR_USER_DESKTOP),
-      task_environment_(
-          base::test::TaskEnvironment::MainThreadType::DEFAULT,
-          base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED),
-      file_operations_(std::make_unique<IpcTestBridge>(
-          task_environment_.GetMainThreadTaskRunner())) {}
+IpcFileOperationsTest::IpcFileOperationsTest() = default;
 
 IpcFileOperationsTest::~IpcFileOperationsTest() = default;
 
+void IpcFileOperationsTest::SetUp() {
+  // Connect the fake proxy and agent using a pair of associated endpoints. The
+  // real classes would use a pre-existing IPC channel but we don't need this
+  // for our testing.
+  mojo::AssociatedRemote<mojom::DesktopSessionControl> remote;
+  fake_desktop_session_agent_.Bind(
+      remote.BindNewEndpointAndPassDedicatedReceiver());
+  fake_desktop_session_proxy_.Bind(remote.Unbind());
+}
+
 base::FilePath IpcFileOperationsTest::TestDir() {
   base::FilePath result;
   EXPECT_TRUE(base::PathService::Get(base::DIR_USER_DESKTOP, &result));
@@ -155,22 +344,23 @@
   ASSERT_EQ(FileOperations::kCreated, writer->state());
 
   absl::optional<FileOperations::Writer::Result> open_result;
-  writer->Open(kTestFilename,
-               BindLambda([&](FileOperations::Writer::Result result) {
-                 open_result = std::move(result);
-               }));
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    open_result = std::move(result);
+                                  }));
   ASSERT_EQ(FileOperations::kBusy, writer->state());
   task_environment_.RunUntilIdle();
   ASSERT_EQ(FileOperations::kReady, writer->state());
   ASSERT_TRUE(open_result);
   ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
 
   for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) {
     absl::optional<FileOperations::Writer::Result> write_result;
-    writer->WriteChunk(chunk,
-                       BindLambda([&](FileOperations::Writer::Result result) {
-                         write_result = std::move(result);
-                       }));
+    writer->WriteChunk(chunk, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    write_result = std::move(result);
+                                  }));
     ASSERT_EQ(FileOperations::kBusy, writer->state());
     task_environment_.RunUntilIdle();
     ASSERT_EQ(FileOperations::kReady, writer->state());
@@ -179,12 +369,15 @@
   }
 
   absl::optional<FileOperations::Writer::Result> close_result;
-  writer->Close(BindLambda([&](FileOperations::Writer::Result result) {
-    close_result = std::move(result);
-  }));
+  writer->Close(
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        close_result = std::move(result);
+      }));
   ASSERT_EQ(FileOperations::kBusy, writer->state());
   task_environment_.RunUntilIdle();
   EXPECT_EQ(FileOperations::kComplete, writer->state());
+  // Verify the MojoIpcWriter instance was released.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
 
   std::string actual_file_data;
   ASSERT_TRUE(base::ReadFileToString(TestDir().Append(kTestFilename),
@@ -194,32 +387,38 @@
 }
 
 // Verifies that dropping early cancels the remote writer.
-TEST_F(IpcFileOperationsTest, DroppingCancelsRemote) {
+TEST_F(IpcFileOperationsTest, DroppingCancelsRemoteWriter) {
   std::unique_ptr<FileOperations::Writer> writer =
       file_operations_->CreateWriter();
 
   absl::optional<FileOperations::Writer::Result> open_result;
-  writer->Open(kTestFilename,
-               BindLambda([&](FileOperations::Writer::Result result) {
-                 open_result = std::move(result);
-               }));
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    open_result = std::move(result);
+                                  }));
   task_environment_.RunUntilIdle();
-  ASSERT_TRUE(open_result && *open_result);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
 
   for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) {
     absl::optional<FileOperations::Writer::Result> write_result;
-    writer->WriteChunk(chunk,
-                       BindLambda([&](FileOperations::Writer::Result result) {
-                         write_result = std::move(result);
-                       }));
+    writer->WriteChunk(chunk, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    write_result = std::move(result);
+                                  }));
     task_environment_.RunUntilIdle();
-    ASSERT_TRUE(write_result && *write_result);
+    ASSERT_TRUE(write_result);
+    ASSERT_TRUE(*write_result);
   }
 
   writer.reset();
   task_environment_.RunUntilIdle();
 
   EXPECT_TRUE(base::IsDirectoryEmpty(TestDir()));
+
+  // Verify the MojoIpcWriter instance was released.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
 }
 
 // Verifies that dropping works while an operation is pending.
@@ -228,18 +427,21 @@
       file_operations_->CreateWriter();
 
   absl::optional<FileOperations::Writer::Result> open_result;
-  writer->Open(kTestFilename,
-               BindLambda([&](FileOperations::Writer::Result result) {
-                 open_result = std::move(result);
-               }));
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    open_result = std::move(result);
+                                  }));
   task_environment_.RunUntilIdle();
-  ASSERT_TRUE(open_result && *open_result);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
 
   absl::optional<FileOperations::Writer::Result> write_result;
-  writer->WriteChunk(kTestDataOne,
-                     BindLambda([&](FileOperations::Writer::Result result) {
-                       write_result = std::move(result);
-                     }));
+  writer->WriteChunk(
+      kTestDataOne,
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        write_result = std::move(result);
+      }));
 
   EXPECT_EQ(FileOperations::kBusy, writer->state());
   writer.reset();
@@ -247,6 +449,9 @@
 
   EXPECT_FALSE(write_result);
   EXPECT_TRUE(base::IsDirectoryEmpty(TestDir()));
+
+  // Verify the MojoIpcWriter instance was released.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
 }
 
 // Verifies that a file can be successfully read in three chunks.
@@ -265,22 +470,24 @@
 
   FakeFileChooser::SetResult(path);
   absl::optional<FileOperations::Reader::OpenResult> open_result;
-  reader->Open(BindLambda([&](FileOperations::Reader::OpenResult result) {
-    open_result = std::move(result);
-  }));
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
   ASSERT_EQ(FileOperations::kBusy, reader->state());
   task_environment_.RunUntilIdle();
   EXPECT_EQ(FileOperations::kReady, reader->state());
   ASSERT_TRUE(open_result);
   ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
 
   for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) {
     absl::optional<FileOperations::Reader::ReadResult> read_result;
-    reader->ReadChunk(
-        chunk.size(),
-        BindLambda([&](FileOperations::Reader::ReadResult result) {
-          read_result = std::move(result);
-        }));
+    reader->ReadChunk(chunk.size(),
+                      base::BindLambdaForTesting(
+                          [&](FileOperations::Reader::ReadResult result) {
+                            read_result = std::move(result);
+                          }));
     ASSERT_EQ(FileOperations::kBusy, reader->state());
     task_environment_.RunUntilIdle();
     ASSERT_EQ(FileOperations::kReady, reader->state());
@@ -288,6 +495,13 @@
     ASSERT_TRUE(*read_result);
     EXPECT_EQ(chunk, **read_result);
   }
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
+
+  reader.reset();
+  task_environment_.RunUntilIdle();
+
+  // Verify the MojoIpcReader instance was released.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
 }
 
 // Verifies proper EOF handling.
@@ -306,35 +520,44 @@
 
   FakeFileChooser::SetResult(path);
   absl::optional<FileOperations::Reader::OpenResult> open_result;
-  reader->Open(BindLambda([&](FileOperations::Reader::OpenResult result) {
-    open_result = std::move(result);
-  }));
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
   task_environment_.RunUntilIdle();
-  ASSERT_TRUE(open_result && *open_result);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
 
   absl::optional<FileOperations::Reader::ReadResult> read_result;
   reader->ReadChunk(
       contents.size() +
           kOverreadAmount,  // Attempt to read more than is in file.
-      BindLambda([&](FileOperations::Reader::ReadResult result) {
-        read_result = std::move(result);
-      }));
+      base::BindLambdaForTesting(
+          [&](FileOperations::Reader::ReadResult result) {
+            read_result = std::move(result);
+          }));
   task_environment_.RunUntilIdle();
   ASSERT_EQ(FileOperations::kReady, reader->state());
   ASSERT_TRUE(read_result);
   ASSERT_TRUE(*read_result);
   EXPECT_EQ(contents, **read_result);
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
 
   read_result.reset();
   reader->ReadChunk(kOverreadAmount,
-                    BindLambda([&](FileOperations::Reader::ReadResult result) {
-                      read_result = std::move(result);
-                    }));
+                    base::BindLambdaForTesting(
+                        [&](FileOperations::Reader::ReadResult result) {
+                          read_result = std::move(result);
+                        }));
   task_environment_.RunUntilIdle();
   EXPECT_EQ(FileOperations::kComplete, reader->state());
   ASSERT_TRUE(read_result);
   ASSERT_TRUE(*read_result);
   EXPECT_EQ(std::size_t{0}, (*read_result)->size());
+
+  // Verify the MojoIpcReader instance was released.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
 }
 
 // Verifies proper handling of zero-size file
@@ -348,39 +571,640 @@
 
   FakeFileChooser::SetResult(path);
   absl::optional<FileOperations::Reader::OpenResult> open_result;
-  reader->Open(BindLambda([&](FileOperations::Reader::OpenResult result) {
-    open_result = std::move(result);
-  }));
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
   task_environment_.RunUntilIdle();
-  ASSERT_TRUE(open_result && *open_result);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
 
   absl::optional<FileOperations::Reader::ReadResult> read_result;
   reader->ReadChunk(kChunkSize,
-                    BindLambda([&](FileOperations::Reader::ReadResult result) {
-                      read_result = std::move(result);
-                    }));
+                    base::BindLambdaForTesting(
+                        [&](FileOperations::Reader::ReadResult result) {
+                          read_result = std::move(result);
+                        }));
   task_environment_.RunUntilIdle();
   EXPECT_EQ(FileOperations::kComplete, reader->state());
   ASSERT_TRUE(read_result);
   ASSERT_TRUE(*read_result);
   EXPECT_EQ(std::size_t{0}, (*read_result)->size());
+
+  // Verify the MojoIpcReader instance was released.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
 }
 
-// Verifies error is propagated.
-TEST_F(IpcFileOperationsTest, ReaderPropagatesError) {
+// Concurrent Read operations are handled and valid data is retuned.
+TEST_F(IpcFileOperationsTest, ConcurrentReadOperationsSupported) {
+  base::FilePath base_path = TestDir().Append(kTestFilename);
+  std::vector<base::FilePath> paths{
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(0)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(1)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(2)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(3)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(4)"))};
+
+  std::vector<std::uint8_t> contents =
+      ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree);
+  for (const auto& path : paths) {
+    ASSERT_EQ(
+        static_cast<int>(contents.size()),
+        base::WriteFile(path, reinterpret_cast<const char*>(contents.data()),
+                        contents.size()));
+  }
+
+  std::vector<std::unique_ptr<FileOperations::Reader>> readers;
+  for (size_t i = 0; i < paths.size(); i++) {
+    readers.emplace_back(file_operations_->CreateReader());
+  }
+
+  int reader_count = static_cast<int>(readers.size());
+  for (int i = 0; i < reader_count; i++) {
+    FakeFileChooser::SetResult(paths[i]);
+    absl::optional<FileOperations::Reader::OpenResult> open_result;
+    readers[i]->Open(base::BindLambdaForTesting(
+        [&](FileOperations::Reader::OpenResult result) {
+          open_result = std::move(result);
+        }));
+    task_environment_.RunUntilIdle();
+    ASSERT_TRUE(open_result);
+    ASSERT_TRUE(*open_result);
+    ASSERT_EQ(session_file_reader_count(), static_cast<size_t>(i + 1));
+  }
+  ASSERT_EQ(session_file_reader_count(), readers.size());
+
+  for (int i = 0; i < reader_count; i++) {
+    for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) {
+      absl::optional<FileOperations::Reader::ReadResult> read_result;
+      readers[i]->ReadChunk(chunk.size(),
+                            base::BindLambdaForTesting(
+                                [&](FileOperations::Reader::ReadResult result) {
+                                  read_result = std::move(result);
+                                }));
+      ASSERT_EQ(FileOperations::kBusy, readers[i]->state());
+      task_environment_.RunUntilIdle();
+      ASSERT_EQ(FileOperations::kReady, readers[i]->state());
+      ASSERT_TRUE(read_result);
+      ASSERT_TRUE(*read_result);
+      EXPECT_EQ(chunk, **read_result);
+    }
+  }
+  ASSERT_EQ(session_file_reader_count(), readers.size());
+
+  for (int i = reader_count - 1; i >= 0; i--) {
+    absl::optional<FileOperations::Reader::ReadResult> read_result;
+    // Simulate EOF by reading 1 additional byte.
+    readers[i]->ReadChunk(1,
+                          base::BindLambdaForTesting(
+                              [&](FileOperations::Reader::ReadResult result) {
+                                read_result = std::move(result);
+                              }));
+    ASSERT_EQ(FileOperations::kBusy, readers[i]->state());
+    task_environment_.RunUntilIdle();
+    ASSERT_EQ(FileOperations::kComplete, readers[i]->state());
+    ASSERT_TRUE(read_result);
+    // Verify each MojoIpcReader instance is released as it completes and that
+    // other instances are retained.
+    ASSERT_EQ(session_file_reader_count(), static_cast<size_t>(i));
+  }
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+}
+
+// Concurrent Write operations handled.
+TEST_F(IpcFileOperationsTest, ConcurrentWriteOperationsSupported) {
+  base::FilePath base_path = TestDir().Append(kTestFilename);
+  std::vector<base::FilePath> paths{
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(0)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(1)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(2)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(3)")),
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(4)"))};
+
+  std::vector<std::uint8_t> contents =
+      ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree);
+
+  std::vector<std::unique_ptr<FileOperations::Writer>> writers;
+  for (size_t i = 0; i < paths.size(); i++) {
+    writers.emplace_back(file_operations_->CreateWriter());
+  }
+
+  int writer_count = static_cast<int>(writers.size());
+  for (int i = 0; i < writer_count; i++) {
+    absl::optional<FileOperations::Writer::Result> open_result;
+    writers[i]->Open(paths[i], base::BindLambdaForTesting(
+                                   [&](FileOperations::Writer::Result result) {
+                                     open_result = std::move(result);
+                                   }));
+    task_environment_.RunUntilIdle();
+    ASSERT_TRUE(open_result);
+    ASSERT_TRUE(*open_result);
+    ASSERT_EQ(session_file_writer_count(), static_cast<size_t>(i + 1));
+  }
+  ASSERT_EQ(session_file_writer_count(), writers.size());
+
+  for (const auto& chunk : {kTestDataOne, kTestDataTwo, kTestDataThree}) {
+    for (int i = 0; i < writer_count; i++) {
+      absl::optional<FileOperations::Writer::Result> write_result;
+      writers[i]->WriteChunk(chunk,
+                             base::BindLambdaForTesting(
+                                 [&](FileOperations::Writer::Result result) {
+                                   write_result = std::move(result);
+                                 }));
+      EXPECT_EQ(writers[i]->state(), FileOperations::kBusy);
+      task_environment_.RunUntilIdle();
+      EXPECT_EQ(writers[i]->state(), FileOperations::kReady);
+      ASSERT_TRUE(write_result);
+      ASSERT_FALSE(write_result->is_error());
+      ASSERT_TRUE(*write_result);
+    }
+  }
+  ASSERT_EQ(session_file_writer_count(), writers.size());
+
+  for (int i = writer_count - 1; i >= 0; i--) {
+    absl::optional<FileOperations::Writer::Result> close_result;
+    writers[i]->Close(
+        base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+          close_result = std::move(result);
+        }));
+    ASSERT_EQ(writers[i]->state(), FileOperations::kBusy);
+    task_environment_.RunUntilIdle();
+    EXPECT_EQ(writers[i]->state(), FileOperations::kComplete);
+    ASSERT_TRUE(close_result);
+    ASSERT_FALSE(close_result->is_error());
+    ASSERT_TRUE(*close_result);
+    // Verify each MojoIpcWriter instance is released as it completes and that
+    // any other instances are still retained.
+    ASSERT_EQ(session_file_writer_count(), static_cast<size_t>(i));
+  }
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_FALSE(base::IsDirectoryEmpty(TestDir()));
+}
+
+// Concurrent Read and Write operations handled.
+TEST_F(IpcFileOperationsTest, ConcurrentReadAndWriteOperationsSupported) {
+  std::vector<std::uint8_t> contents =
+      ByteArrayFrom(kTestDataOne, kTestDataTwo, kTestDataThree);
+  base::FilePath base_path = TestDir().Append(kTestFilename);
+
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+
+  // Write to the read path first so that the writer can select a 'unique' file
+  // which doesn't conflict with this |read_path|.
+  base::FilePath read_path(
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(read)")));
+  ASSERT_EQ(
+      static_cast<int>(contents.size()),
+      base::WriteFile(read_path, reinterpret_cast<const char*>(contents.data()),
+                      contents.size()));
+
+  // Pending open file operations.
+  absl::optional<FileOperations::Writer::Result> open_for_write_result;
+  writer->Open(
+      base_path.InsertBeforeExtension(FILE_PATH_LITERAL("(write)")),
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        open_for_write_result = std::move(result);
+      }));
+  FakeFileChooser::SetResult(read_path);
+  absl::optional<FileOperations::Reader::OpenResult> open_for_read_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_for_read_result = std::move(result);
+      }));
+  EXPECT_EQ(writer->state(), FileOperations::kBusy);
+  EXPECT_EQ(reader->state(), FileOperations::kBusy);
+  // Complete the operations.
+  task_environment_.RunUntilIdle();
+  // Validate results.
+  EXPECT_EQ(writer->state(), FileOperations::kReady);
+  EXPECT_EQ(reader->state(), FileOperations::kReady);
+  ASSERT_TRUE(open_for_write_result);
+  ASSERT_TRUE(open_for_read_result);
+  ASSERT_TRUE(*open_for_write_result);
+  ASSERT_TRUE(*open_for_read_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
+
+  // Pending write operation.
+  absl::optional<FileOperations::Writer::Result> write_result;
+  writer->WriteChunk(contents, base::BindLambdaForTesting(
+                                   [&](FileOperations::Writer::Result result) {
+                                     write_result = std::move(result);
+                                   }));
+  // Pending read operation.
+  absl::optional<FileOperations::Reader::ReadResult> read_result;
+  reader->ReadChunk(contents.size(),
+                    base::BindLambdaForTesting(
+                        [&](FileOperations::Reader::ReadResult result) {
+                          read_result = std::move(result);
+                        }));
+  EXPECT_EQ(writer->state(), FileOperations::kBusy);
+  EXPECT_EQ(reader->state(), FileOperations::kBusy);
+  // Complete the pending operations.
+  task_environment_.RunUntilIdle();
+  // Validate the results.
+  EXPECT_EQ(writer->state(), FileOperations::kReady);
+  EXPECT_EQ(reader->state(), FileOperations::kReady);
+  ASSERT_TRUE(write_result);
+  ASSERT_TRUE(read_result);
+  ASSERT_TRUE(*write_result);
+  ASSERT_TRUE(*read_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
+
+  // Close the writer.
+  absl::optional<FileOperations::Writer::Result> close_result;
+  writer->Close(
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        close_result = std::move(result);
+      }));
+  ASSERT_EQ(writer->state(), FileOperations::kBusy);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(writer->state(), FileOperations::kComplete);
+  ASSERT_TRUE(close_result);
+  ASSERT_TRUE(*close_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
+
+  // Close the reader by reading 1 additional byte to simulate EOF.
+  reader->ReadChunk(1, base::BindLambdaForTesting(
+                           [&](FileOperations::Reader::ReadResult result) {
+                             read_result = std::move(result);
+                           }));
+  ASSERT_EQ(reader->state(), FileOperations::kBusy);
+  task_environment_.RunUntilIdle();
+  ASSERT_EQ(reader->state(), FileOperations::kComplete);
+  ASSERT_TRUE(read_result);
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+
+  ASSERT_FALSE(base::IsDirectoryEmpty(TestDir()));
+
+  // Reset the handlers, then trigger a 'disconnect' to verify it is a no-op.
+  reader.reset();
+  writer.reset();
+  fake_desktop_session_proxy_.TriggerDisconnectHandlers();
+}
+
+// Verify a file chooser error is propagated.
+TEST_F(IpcFileOperationsTest, ReaderPropagatesErrorFromFileChooser) {
   std::unique_ptr<FileOperations::Reader> reader =
       file_operations_->CreateReader();
 
   // Currently non-existent file.
   FakeFileChooser::SetResult(TestDir().Append(kTestFilename));
   absl::optional<FileOperations::Reader::OpenResult> open_result;
-  reader->Open(BindLambda([&](FileOperations::Reader::OpenResult result) {
-    open_result = std::move(result);
-  }));
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
   task_environment_.RunUntilIdle();
   EXPECT_EQ(FileOperations::kFailed, reader->state());
   ASSERT_TRUE(open_result);
   ASSERT_FALSE(*open_result);
+  // Verify the MojoIpcReader instance was not retained.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+}
+
+// Verify IpcFileReader handles an error returned from the request handler.
+TEST_F(IpcFileOperationsTest, ErrorReturnedByFileReadRequestHandler) {
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+  fake_desktop_session_proxy_.SetErrorForNextRequest(
+      protocol::MakeFileTransferError(
+          FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  absl::optional<FileOperations::Reader::OpenResult> open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
+  ASSERT_TRUE(open_result->is_error());
+  ASSERT_EQ(reader->state(), FileOperations::kFailed);
+  // Verify the MojoIpcReader instance was not created.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+}
+
+// Verify IpcFileWriter handles an error returned from the request handler.
+TEST_F(IpcFileOperationsTest, ErrorReturnedByFileWriteRequestHandler) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+  fake_desktop_session_proxy_.SetErrorForNextRequest(
+      protocol::MakeFileTransferError(
+          FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  absl::optional<FileOperations::Writer::Result> open_result;
+  writer->Open(
+      TestDir().Append(kTestFilename),
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        open_result = std::move(result);
+      }));
+  ASSERT_TRUE(open_result->is_error());
+  ASSERT_EQ(writer->state(), FileOperations::kFailed);
+  // Verify the MojoIpcWriter instance was not created.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_TRUE(base::IsDirectoryEmpty(TestDir()));
+}
+
+// Verify IpcFileReader handles an error returned from the mojo receiver.
+TEST_F(IpcFileOperationsTest, ErrorReturnedByMojoReceiverForFileRead) {
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+  fake_desktop_session_agent_.SetErrorForNextResponse(
+      protocol::MakeFileTransferError(
+          FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  absl::optional<FileOperations::Reader::OpenResult> open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  ASSERT_TRUE(open_result->is_error());
+  ASSERT_EQ(reader->state(), FileOperations::kFailed);
+  // Verify a MojoIpcReader instance was not created.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+}
+
+// Verify IpcFileWriter handles an error returned from the mojo receiver.
+TEST_F(IpcFileOperationsTest, ErrorReturnedByMojoReceiverForFileWrite) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+  fake_desktop_session_agent_.SetErrorForNextResponse(
+      protocol::MakeFileTransferError(
+          FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+  absl::optional<FileOperations::Writer::Result> open_result;
+  writer->Open(
+      TestDir().Append(kTestFilename),
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        open_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  ASSERT_TRUE(open_result->is_error());
+  ASSERT_EQ(writer->state(), FileOperations::kFailed);
+  // Verify the MojoIpcWriter instance was not retained.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_TRUE(base::IsDirectoryEmpty(TestDir()));
+}
+
+TEST_F(IpcFileOperationsTest, ReaderNotifiedOfIpcChannelDisconnect) {
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+  // This will trigger the DesktopSessionControl remote disconnect handler.
+  fake_desktop_session_agent_.DisconnectReceiverOnNextRequest();
+  absl::optional<FileOperations::Reader::OpenResult> open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  // Verify a MojoIpcReader instance was not created.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+  ASSERT_EQ(reader->state(), FileOperations::kFailed);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(open_result->is_error());
+}
+
+TEST_F(IpcFileOperationsTest, WriterNotifiedOfIpcChannelDisconnect) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+  // This will trigger the DesktopSessionControl remote disconnect handler.
+  fake_desktop_session_agent_.DisconnectReceiverOnNextRequest();
+  absl::optional<FileOperations::Writer::Result> open_result;
+  writer->Open(
+      TestDir().Append(kTestFilename),
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        open_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  // Verify a MojoIpcWriter instance was not created.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  ASSERT_EQ(writer->state(), FileOperations::kFailed);
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(open_result->is_error());
+  ASSERT_TRUE(base::IsDirectoryEmpty(TestDir()));
+}
+
+TEST_F(IpcFileOperationsTest, ErrorWhenReadChunkCalledBeforeOpen) {
+  constexpr std::size_t kChunkSize = 5;
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+  absl::optional<FileOperations::Reader::ReadResult> read_result;
+  reader->ReadChunk(kChunkSize,
+                    base::BindLambdaForTesting(
+                        [&](FileOperations::Reader::ReadResult result) {
+                          read_result = std::move(result);
+                        }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, reader->state());
+  ASSERT_TRUE(read_result);
+  ASSERT_TRUE(read_result->is_error());
+  ASSERT_FALSE(*read_result);
+  // Verify a MojoIpcReader instance was not created.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+}
+
+TEST_F(IpcFileOperationsTest, ErrorWhenWriteChunkCalledBeforeOpen) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+  absl::optional<FileOperations::Writer::Result> write_result;
+  writer->WriteChunk(
+      std::vector<uint8_t>{16, 16, 16, 16, 16},
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        write_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, writer->state());
+  ASSERT_TRUE(write_result);
+  ASSERT_TRUE(write_result->is_error());
+  ASSERT_FALSE(*write_result);
+  // Verify a MojoIpcWriter instance was not created.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+}
+
+TEST_F(IpcFileOperationsTest, ErrorWhenCloseCalledBeforeOpen) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+  absl::optional<FileOperations::Writer::Result> close_result;
+  writer->Close(
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        close_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, writer->state());
+  ASSERT_TRUE(close_result);
+  ASSERT_TRUE(close_result->is_error());
+  ASSERT_FALSE(*close_result);
+  // Verify a MojoIpcWriter instance was not created.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+}
+
+TEST_F(IpcFileOperationsTest, ErrorWhenReadChunkCalledAfterReceiverDisconnect) {
+  constexpr std::size_t kChunkSize = 5;
+  base::FilePath path = TestDir().Append(kTestFilename);
+  ASSERT_EQ(0, base::WriteFile(path, "", 0));
+
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations_->CreateReader();
+
+  FakeFileChooser::SetResult(path);
+  absl::optional<FileOperations::Reader::OpenResult> open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        open_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  ASSERT_TRUE(open_result);
+  ASSERT_TRUE(*open_result);
+  ASSERT_EQ(session_file_reader_count(), size_t{1});
+
+  clear_session_file_receivers();
+  // Verify the MojoIpcReader instance was released.
+  ASSERT_EQ(session_file_reader_count(), size_t{0});
+  task_environment_.RunUntilIdle();
+
+  absl::optional<FileOperations::Reader::ReadResult> read_result;
+  reader->ReadChunk(kChunkSize,
+                    base::BindLambdaForTesting(
+                        [&](FileOperations::Reader::ReadResult result) {
+                          read_result = std::move(result);
+                        }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, reader->state());
+  ASSERT_TRUE(read_result);
+  ASSERT_TRUE(read_result->is_error());
+  ASSERT_FALSE(*read_result);
+}
+
+TEST_F(IpcFileOperationsTest,
+       ErrorWhenWriteChunkCalledAfterReceiverDisconnect) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+
+  absl::optional<FileOperations::Writer::Result> open_result;
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    open_result = std::move(result);
+                                  }));
+  task_environment_.RunUntilIdle();
+  ASSERT_EQ(FileOperations::kReady, writer->state());
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
+
+  clear_session_file_receivers();
+  // Verify the MojoIpcWriter instance was released.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  task_environment_.RunUntilIdle();
+
+  absl::optional<FileOperations::Writer::Result> write_result;
+  writer->WriteChunk(
+      std::vector<uint8_t>{16, 16, 16, 16, 16},
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        write_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, writer->state());
+  ASSERT_TRUE(write_result);
+  ASSERT_TRUE(write_result->is_error());
+  ASSERT_FALSE(*write_result);
+  ASSERT_TRUE(base::IsDirectoryEmpty(TestDir()));
+}
+
+TEST_F(IpcFileOperationsTest, ErrorWhenCloseCalledAfterReceiverDisconnect) {
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations_->CreateWriter();
+
+  absl::optional<FileOperations::Writer::Result> open_result;
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    open_result = std::move(result);
+                                  }));
+  task_environment_.RunUntilIdle();
+  ASSERT_EQ(FileOperations::kReady, writer->state());
+  ASSERT_EQ(session_file_writer_count(), size_t{1});
+
+  clear_session_file_receivers();
+  // Verify the MojoIpcWriter instance was released.
+  ASSERT_EQ(session_file_writer_count(), size_t{0});
+  task_environment_.RunUntilIdle();
+
+  absl::optional<FileOperations::Writer::Result> close_result;
+  writer->Close(
+      base::BindLambdaForTesting([&](FileOperations::Writer::Result result) {
+        close_result = std::move(result);
+      }));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(FileOperations::kFailed, writer->state());
+  ASSERT_TRUE(close_result);
+  ASSERT_TRUE(close_result->is_error());
+  ASSERT_FALSE(*close_result);
+  ASSERT_TRUE(base::IsDirectoryEmpty(TestDir()));
+}
+
+TEST_F(IpcFileOperationsTest,
+       ErrorWhenUsingExistingFileOperationsAfterFactoryDestroyed) {
+  std::unique_ptr<IpcFileOperationsFactory> ipc_file_operations_factory =
+      std::make_unique<IpcFileOperationsFactory>(&fake_desktop_session_proxy_);
+  std::unique_ptr<FileOperations> file_operations =
+      ipc_file_operations_factory->CreateFileOperations();
+
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations->CreateWriter();
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations->CreateReader();
+
+  ipc_file_operations_factory.reset();
+
+  absl::optional<FileOperations::Writer::Result> writer_open_result;
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    writer_open_result = std::move(result);
+                                  }));
+  // We expect this to immediately fail.
+  ASSERT_EQ(writer->state(), FileOperations::kFailed);
+  ASSERT_TRUE(writer_open_result->is_error());
+
+  absl::optional<FileOperations::Reader::OpenResult> reader_open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        reader_open_result = std::move(result);
+      }));
+  // We expect this to immediately fail.
+  ASSERT_EQ(reader->state(), FileOperations::kFailed);
+  ASSERT_TRUE(reader_open_result->is_error());
+}
+
+TEST_F(IpcFileOperationsTest,
+       ErrorWhenUsingNewFileOperationsAfterFactoryDestroyed) {
+  std::unique_ptr<IpcFileOperationsFactory> ipc_file_operations_factory =
+      std::make_unique<IpcFileOperationsFactory>(&fake_desktop_session_proxy_);
+  std::unique_ptr<FileOperations> file_operations =
+      ipc_file_operations_factory->CreateFileOperations();
+
+  ipc_file_operations_factory.reset();
+
+  std::unique_ptr<FileOperations::Writer> writer =
+      file_operations->CreateWriter();
+  std::unique_ptr<FileOperations::Reader> reader =
+      file_operations->CreateReader();
+
+  absl::optional<FileOperations::Writer::Result> writer_open_result;
+  writer->Open(kTestFilename, base::BindLambdaForTesting(
+                                  [&](FileOperations::Writer::Result result) {
+                                    writer_open_result = std::move(result);
+                                  }));
+  // We expect this to immediately fail.
+  ASSERT_EQ(writer->state(), FileOperations::kFailed);
+  ASSERT_TRUE(writer_open_result->is_error());
+
+  absl::optional<FileOperations::Reader::OpenResult> reader_open_result;
+  reader->Open(base::BindLambdaForTesting(
+      [&](FileOperations::Reader::OpenResult result) {
+        reader_open_result = std::move(result);
+      }));
+  // We expect this to immediately fail.
+  ASSERT_EQ(reader->state(), FileOperations::kFailed);
+  ASSERT_TRUE(reader_open_result->is_error());
 }
 
 }  // namespace remoting
diff --git a/remoting/host/file_transfer/session_file_operations_handler.cc b/remoting/host/file_transfer/session_file_operations_handler.cc
index 6c450f4..62653b99 100644
--- a/remoting/host/file_transfer/session_file_operations_handler.cc
+++ b/remoting/host/file_transfer/session_file_operations_handler.cc
@@ -4,172 +4,224 @@
 
 #include "remoting/host/file_transfer/session_file_operations_handler.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 #include "remoting/protocol/file_transfer_helpers.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace remoting {
 
-SessionFileOperationsHandler::SessionFileOperationsHandler(
-    IpcFileOperations::ResultHandler* result_handler,
-    std::unique_ptr<FileOperations> file_operations)
-    : result_handler_(result_handler),
-      file_operations_(std::move(file_operations)) {}
+namespace {
 
-void SessionFileOperationsHandler::ReadFile(std::uint64_t file_id) {
-  auto location = readers_.emplace(file_id, file_operations_->CreateReader());
-  if (!location.second) {
-    // Strange, we've already received a ReadFile message for this ID. Cancel
-    // it and send an error to be safe.
-    readers_.erase(location.first);
-    result_handler_->OnInfoResult(
-        file_id,
-        protocol::MakeFileTransferError(
-            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
-    return;
-  }
-  // Unretained is sound because no Reader callbacks will be invoked after the
-  // Writer is destroyed.
-  location.first->second->Open(
-      base::BindOnce(&SessionFileOperationsHandler::OnReaderOpenResult,
-                     base::Unretained(this), file_id));
+using BeginFileReadCallback =
+    SessionFileOperationsHandler::BeginFileReadCallback;
+using BeginFileWriteCallback =
+    SessionFileOperationsHandler::BeginFileWriteCallback;
+
+using Reader = FileOperations::Reader;
+using Writer = FileOperations::Writer;
+
+class MojoFileReader : public mojom::FileReader {
+ public:
+  explicit MojoFileReader(std::unique_ptr<Reader> file_reader);
+
+  MojoFileReader(const MojoFileReader&) = delete;
+  MojoFileReader& operator=(const MojoFileReader&) = delete;
+
+  ~MojoFileReader() override = default;
+
+  base::WeakPtr<MojoFileReader> GetWeakPtr();
+
+  // Requests |file_reader_| to open an existing file for reading.
+  void Open(Reader::OpenCallback callback);
+  // Receives the result from Open() and returns it over the mojo channel.
+  void OnFileOpened(BeginFileReadCallback callback,
+                    mojo::PendingAssociatedRemote<mojom::FileReader> remote,
+                    Reader::OpenResult result);
+
+  // mojom::FileReader implementation.
+  void ReadChunk(uint64_t bytes_to_read, ReadChunkCallback callback) override;
+
+  const base::FilePath& filename() const { return file_reader_->filename(); }
+
+  uint64_t size() const { return file_reader_->size(); }
+
+ private:
+  const std::unique_ptr<Reader> file_reader_;
+  base::WeakPtrFactory<MojoFileReader> weak_ptr_factory{this};
+};
+
+class MojoFileWriter : public mojom::FileWriter {
+ public:
+  explicit MojoFileWriter(std::unique_ptr<Writer> file_writer);
+
+  MojoFileWriter(const MojoFileWriter&) = delete;
+  MojoFileWriter& operator=(const MojoFileWriter&) = delete;
+
+  ~MojoFileWriter() override = default;
+
+  // Requests |file_writer_| to open a new file for writing.
+  void Open(const base::FilePath& filename, Writer::Callback callback);
+  // Receives the result from Open() and returns it over the mojo channel.
+  void OnFileOpened(BeginFileWriteCallback callback,
+                    mojo::PendingAssociatedRemote<mojom::FileWriter> remote,
+                    Writer::Result result);
+
+  // mojom::FileWriter implementation.
+  void WriteChunk(const std::vector<std::uint8_t>& data,
+                  WriteChunkCallback callback) override;
+  void CloseFile(CloseFileCallback callback) override;
+
+  base::WeakPtr<MojoFileWriter> GetWeakPtr();
+
+ private:
+  const std::unique_ptr<Writer> file_writer_;
+  base::WeakPtrFactory<MojoFileWriter> weak_ptr_factory{this};
+};
+
+MojoFileReader::MojoFileReader(std::unique_ptr<Reader> file_reader)
+    : file_reader_(std::move(file_reader)) {}
+
+base::WeakPtr<MojoFileReader> MojoFileReader::GetWeakPtr() {
+  return weak_ptr_factory.GetWeakPtr();
 }
 
-void SessionFileOperationsHandler::ReadChunk(std::uint64_t file_id,
-                                             std::uint64_t size) {
-  auto reader_iter = readers_.find(file_id);
-  if (reader_iter == readers_.end()) {
-    result_handler_->OnDataResult(
-        file_id,
-        protocol::MakeFileTransferError(
-            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
-    return;
-  }
-  reader_iter->second->ReadChunk(
-      size, base::BindOnce(&SessionFileOperationsHandler::OnReaderReadResult,
-                           base::Unretained(this), file_id));
+void MojoFileReader::Open(Reader::OpenCallback callback) {
+  file_reader_->Open(std::move(callback));
 }
 
-void SessionFileOperationsHandler::WriteFile(uint64_t file_id,
-                                             const base::FilePath& filename) {
-  auto location = writers_.emplace(file_id, file_operations_->CreateWriter());
-  if (!location.second) {
-    // Strange, we've already received a WriteFile message for this ID. Cancel
-    // it and send an error to be safe.
-    writers_.erase(location.first);
-    result_handler_->OnResult(
-        file_id,
-        protocol::MakeFileTransferError(
-            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+void MojoFileReader::OnFileOpened(
+    BeginFileReadCallback callback,
+    mojo::PendingAssociatedRemote<mojom::FileReader> remote,
+    Reader::OpenResult result) {
+  if (result.is_error()) {
+    std::move(callback).Run(
+        mojom::BeginFileReadResult::NewError(result.error()));
+    // This will asynchronously trigger the destruction of this object.
+    remote.reset();
     return;
   }
-  // Unretained is sound because no Writer callbacks will be invoked after the
-  // Writer is destroyed.
-  location.first->second->Open(
-      filename,
-      base::BindOnce(&SessionFileOperationsHandler::OnWriterOperationResult,
-                     base::Unretained(this), file_id));
+
+  std::move(callback).Run(mojom::BeginFileReadResult::NewSuccess(
+      mojom::BeginFileReadSuccess::New(std::move(remote), filename(), size())));
 }
 
-void SessionFileOperationsHandler::WriteChunk(uint64_t file_id,
-                                              std::vector<std::uint8_t> data) {
-  auto writer_iter = writers_.find(file_id);
-  if (writer_iter == writers_.end()) {
-    result_handler_->OnResult(
-        file_id,
-        protocol::MakeFileTransferError(
-            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+// The Mojo-generated ReadChunkCallback has a const& param whereas the
+// FileReader::ReadCallback does not so this function wraps the Mojo callback
+// so it is compatible with the FileReader interface.
+void ReadChunkCallbackWrapper(
+    mojom::FileReader::ReadChunkCallback callback,
+    protocol::FileTransferResult<std::vector<std::uint8_t>> result) {
+  std::move(callback).Run(result);
+}
+
+void MojoFileReader::ReadChunk(uint64_t bytes_to_read,
+                               ReadChunkCallback callback) {
+  file_reader_->ReadChunk(
+      bytes_to_read,
+      base::BindOnce(&ReadChunkCallbackWrapper, std::move(callback)));
+}
+
+MojoFileWriter::MojoFileWriter(std::unique_ptr<Writer> file_writer)
+    : file_writer_(std::move(file_writer)) {}
+
+void MojoFileWriter::Open(const base::FilePath& filename,
+                          Writer::Callback callback) {
+  file_writer_->Open(filename, std::move(callback));
+}
+
+void MojoFileWriter::OnFileOpened(
+    BeginFileWriteCallback callback,
+    mojo::PendingAssociatedRemote<mojom::FileWriter> remote,
+    Writer::Result result) {
+  if (result.is_error()) {
+    std::move(callback).Run(
+        mojom::BeginFileWriteResult::NewError(result.error()));
+    // This will asynchronously trigger the destruction of this object.
+    remote.reset();
     return;
   }
-  writer_iter->second->WriteChunk(
+
+  std::move(callback).Run(mojom::BeginFileWriteResult::NewSuccess(
+      mojom::BeginFileWriteSuccess::New(std::move(remote))));
+}
+
+base::WeakPtr<MojoFileWriter> MojoFileWriter::GetWeakPtr() {
+  return weak_ptr_factory.GetWeakPtr();
+}
+
+// Wraps a Mojo-generated callback to provide compatibility with the FileWriter
+// methods which expect a Writer::Callback.
+template <class T>
+void FileWriterCallbackWrapper(T callback, remoting::Writer::Result result) {
+  absl::optional<protocol::FileTransfer_Error> error;
+  if (result.is_error()) {
+    error = std::move(result.error());
+  }
+  std::move(callback).Run(std::move(error));
+}
+
+void MojoFileWriter::WriteChunk(const std::vector<std::uint8_t>& data,
+                                WriteChunkCallback callback) {
+  file_writer_->WriteChunk(
       std::move(data),
-      base::BindOnce(&SessionFileOperationsHandler::OnWriterOperationResult,
-                     base::Unretained(this), file_id));
+      base::BindOnce(&FileWriterCallbackWrapper<WriteChunkCallback>,
+                     std::move(callback)));
 }
 
-void SessionFileOperationsHandler::Close(uint64_t file_id) {
-  auto reader_iter = readers_.find(file_id);
-  if (reader_iter != readers_.end()) {
-    readers_.erase(reader_iter);
-    return;
-  }
-
-  auto writer_iter = writers_.find(file_id);
-  if (writer_iter != writers_.end()) {
-    writer_iter->second->Close(
-        base::BindOnce(&SessionFileOperationsHandler::OnWriterCloseResult,
-                       base::Unretained(this), file_id));
-    return;
-  }
-
-  // |file_id| is not a known reader or writer. Send an error in case the
-  // network process is waiting for a response.
-  result_handler_->OnResult(
-      file_id,
-      protocol::MakeFileTransferError(
-          FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+void MojoFileWriter::CloseFile(CloseFileCallback callback) {
+  file_writer_->Close(base::BindOnce(
+      &FileWriterCallbackWrapper<CloseFileCallback>, std::move(callback)));
 }
 
-void SessionFileOperationsHandler::Cancel(uint64_t file_id) {
-  // It's possible for a cancel request and an error response (from a previous
-  // request) to pass each other in the message pipe, so the lack of a reader or
-  // writer matching |file_id| is not necessarily an error. Since a cancel
-  // request doesn't expect a response, it's fine to do nothing in that case.
+}  // namespace
 
-  auto reader_iter = readers_.find(file_id);
-  if (reader_iter != readers_.end()) {
-    readers_.erase(reader_iter);
-  }
-
-  auto writer_iter = writers_.find(file_id);
-  if (writer_iter != writers_.end()) {
-    writers_.erase(writer_iter);
-  }
-}
-
-void SessionFileOperationsHandler::OnReaderOpenResult(
-    std::uint64_t file_id,
-    FileOperations::Reader::OpenResult result) {
-  if (!result) {
-    readers_.erase(file_id);
-    result_handler_->OnInfoResult(file_id, std::move(result.error()));
-    return;
-  }
-
-  auto reader_iter = readers_.find(file_id);
-  // Should never get a callback if the reader has been destroyed.
-  DCHECK(reader_iter != readers_.end());
-  result_handler_->OnInfoResult(file_id,
-                                {kSuccessTag, reader_iter->second->filename(),
-                                 reader_iter->second->size()});
-}
-
-void SessionFileOperationsHandler::OnReaderReadResult(
-    std::uint64_t file_id,
-    FileOperations::Reader::ReadResult result) {
-  if (!result || result->size() == 0) {
-    readers_.erase(file_id);
-  }
-
-  result_handler_->OnDataResult(file_id, std::move(result));
-}
-
-void SessionFileOperationsHandler::OnWriterOperationResult(
-    uint64_t file_id,
-    FileOperations::Writer::Result result) {
-  if (!result) {
-    writers_.erase(file_id);
-  }
-  result_handler_->OnResult(file_id, std::move(result));
-}
-
-void SessionFileOperationsHandler::OnWriterCloseResult(
-    uint64_t file_id,
-    FileOperations::Writer::Result result) {
-  writers_.erase(file_id);
-  result_handler_->OnResult(file_id, std::move(result));
-}
+SessionFileOperationsHandler::SessionFileOperationsHandler(
+    std::unique_ptr<FileOperations> file_operations)
+    : file_operations_(std::move(file_operations)) {}
 
 SessionFileOperationsHandler::~SessionFileOperationsHandler() = default;
 
+void SessionFileOperationsHandler::BeginFileRead(
+    BeginFileReadCallback callback) {
+  mojo::AssociatedRemote<mojom::FileReader> remote;
+  auto mojo_file_reader =
+      std::make_unique<MojoFileReader>(file_operations_->CreateReader());
+  MojoFileReader* ptr = mojo_file_reader.get();
+
+  // |file_readers_| now manages the lifetime of |mojo_file_reader| and will
+  // destroy the instance when |remote| is reset.
+  file_readers_.Add(std::move(mojo_file_reader),
+                    remote.BindNewEndpointAndPassReceiver());
+
+  // We Unbind() |remote| so we can return it to the other end of the IPC
+  // channel, this will not cause |mojo_file_reader| to be destroyed.
+  ptr->Open(base::BindOnce(&MojoFileReader::OnFileOpened, ptr->GetWeakPtr(),
+                           std::move(callback), remote.Unbind()));
+}
+
+void SessionFileOperationsHandler::BeginFileWrite(
+    const base::FilePath& file_path,
+    BeginFileWriteCallback callback) {
+  mojo::AssociatedRemote<mojom::FileWriter> remote;
+  auto mojo_file_writer =
+      std::make_unique<MojoFileWriter>(file_operations_->CreateWriter());
+  MojoFileWriter* ptr = mojo_file_writer.get();
+
+  // |file_writers_| now manages the lifetime of |mojo_file_writer| and will
+  // destroy the instance when |remote| is reset.
+  file_writers_.Add(std::move(mojo_file_writer),
+                    remote.BindNewEndpointAndPassReceiver());
+
+  // We Unbind() |remote| so we can return it to the other end of the IPC
+  // channel, this will not cause |mojo_file_writer| to be destroyed.
+  ptr->Open(file_path,
+            base::BindOnce(&MojoFileWriter::OnFileOpened, ptr->GetWeakPtr(),
+                           std::move(callback), remote.Unbind()));
+}
+
 }  // namespace remoting
diff --git a/remoting/host/file_transfer/session_file_operations_handler.h b/remoting/host/file_transfer/session_file_operations_handler.h
index ae4f0f5..243c488 100644
--- a/remoting/host/file_transfer/session_file_operations_handler.h
+++ b/remoting/host/file_transfer/session_file_operations_handler.h
@@ -5,53 +5,46 @@
 #ifndef REMOTING_HOST_FILE_TRANSFER_SESSION_FILE_OPERATIONS_HANDLER_H_
 #define REMOTING_HOST_FILE_TRANSFER_SESSION_FILE_OPERATIONS_HANDLER_H_
 
-#include <cstdint>
 #include <memory>
-#include <vector>
 
-#include "base/containers/flat_map.h"
-#include "base/memory/weak_ptr.h"
-#include "remoting/host/file_transfer/ipc_file_operations.h"
+#include "base/files/file_path.h"
+#include "mojo/public/cpp/bindings/unique_associated_receiver_set.h"
+#include "remoting/host/file_transfer/file_operations.h"
+#include "remoting/host/mojom/desktop_session.mojom.h"
 
 namespace remoting {
 
-// An implementation of IpcFileOperations::RequestHandler that forwards all
-// operations to another FileOperations implementation.
-class SessionFileOperationsHandler : public IpcFileOperations::RequestHandler {
+// A Mojo-aware FileOperations wrapper which handles requests to begin file read
+// and write operations. This class can handle concurrent requests as it creates
+// a new handler for each one and ensures and resource are released when the
+// new Mojo channel is closed.
+class SessionFileOperationsHandler {
  public:
-  // |result_handler| must be valid for the entire lifetime of
-  // SessionFileOperationsHandler.
   explicit SessionFileOperationsHandler(
-      IpcFileOperations::ResultHandler* result_handler,
       std::unique_ptr<FileOperations> file_operations);
-  ~SessionFileOperationsHandler() override;
 
-  // IpcFileOperations::RequestHandler implementation
-  void ReadFile(std::uint64_t file_id) override;
-  void ReadChunk(std::uint64_t file_id, std::uint64_t size) override;
-  void WriteFile(std::uint64_t file_id,
-                 const base::FilePath& filename) override;
-  void WriteChunk(std::uint64_t file_id,
-                  std::vector<std::uint8_t> data) override;
-  void Close(std::uint64_t file_id) override;
-  void Cancel(std::uint64_t file_id) override;
+  SessionFileOperationsHandler(const SessionFileOperationsHandler&) = delete;
+  SessionFileOperationsHandler& operator=(const SessionFileOperationsHandler&) =
+      delete;
+
+  ~SessionFileOperationsHandler();
+
+  using BeginFileReadCallback =
+      base::OnceCallback<void(mojom::BeginFileReadResultPtr)>;
+  void BeginFileRead(BeginFileReadCallback callback);
+
+  using BeginFileWriteCallback =
+      base::OnceCallback<void(mojom::BeginFileWriteResultPtr)>;
+  void BeginFileWrite(const base::FilePath& file_path,
+                      BeginFileWriteCallback callback);
 
  private:
-  void OnReaderOpenResult(std::uint64_t file_id,
-                          FileOperations::Reader::OpenResult result);
-  void OnReaderReadResult(std::uint64_t file_id,
-                          FileOperations::Reader::ReadResult result);
-  void OnWriterOperationResult(std::uint64_t file_id,
-                               FileOperations::Writer::Result result);
-  void OnWriterCloseResult(std::uint64_t file_id,
-                           FileOperations::Writer::Result result);
+  friend class IpcFileOperationsTest;
 
-  IpcFileOperations::ResultHandler* result_handler_;
-  std::unique_ptr<FileOperations> file_operations_;
-  base::flat_map<std::uint64_t, std::unique_ptr<FileOperations::Writer>>
-      writers_;
-  base::flat_map<std::uint64_t, std::unique_ptr<FileOperations::Reader>>
-      readers_;
+  const std::unique_ptr<FileOperations> file_operations_;
+
+  mojo::UniqueAssociatedReceiverSet<mojom::FileReader> file_readers_;
+  mojo::UniqueAssociatedReceiverSet<mojom::FileWriter> file_writers_;
 };
 
 }  // namespace remoting
diff --git a/remoting/host/mojom/BUILD.gn b/remoting/host/mojom/BUILD.gn
index 292676e..7827b50 100644
--- a/remoting/host/mojom/BUILD.gn
+++ b/remoting/host/mojom/BUILD.gn
@@ -79,6 +79,18 @@
           cpp = "::webrtc::DesktopVector"
         },
         {
+          mojom = "remoting.mojom.FileChooserResult"
+          cpp = "::remoting::Result<base::FilePath, ::remoting::protocol::FileTransfer_Error>"
+        },
+        {
+          mojom = "remoting.mojom.FileTransferError"
+          cpp = "::remoting::protocol::FileTransfer_Error"
+        },
+        {
+          mojom = "remoting.mojom.FileTransferError.Type"
+          cpp = "::remoting::protocol::FileTransfer_Error_Type"
+        },
+        {
           mojom = "remoting.mojom.KeyboardLayout"
           cpp = "::remoting::protocol::KeyboardLayout"
         },
@@ -115,6 +127,10 @@
           cpp = "::remoting::protocol::ErrorCode"
         },
         {
+          mojom = "remoting.mojom.ReadChunkResult"
+          cpp = "::remoting::protocol::FileTransferResult<std::vector<uint8_t>>"
+        },
+        {
           mojom = "remoting.mojom.ScreenResolution"
           cpp = "::remoting::ScreenResolution"
         },
@@ -159,6 +175,7 @@
         "//mojo/public/cpp/bindings:protobuf_support",
         "//remoting/host/base",
         "//remoting/proto",
+        "//remoting/protocol",
         "//remoting/protocol:errors",
         "//remoting/protocol:transport",
         "//services/network/public/cpp:ip_address_mojom_support",
diff --git a/remoting/host/mojom/desktop_session.mojom b/remoting/host/mojom/desktop_session.mojom
index 47c6fb36..95faec8 100644
--- a/remoting/host/mojom/desktop_session.mojom
+++ b/remoting/host/mojom/desktop_session.mojom
@@ -5,6 +5,7 @@
 module remoting.mojom;
 
 import "mojo/public/mojom/base/byte_string.mojom";
+import "mojo/public/mojom/base/file_path.mojom";
 import "mojo/public/mojom/base/shared_memory.mojom";
 import "remoting/host/mojom/keyboard_layout.mojom";
 import "remoting/host/mojom/webrtc_types.mojom";
@@ -297,6 +298,101 @@
              desktop_session_control);
 };
 
+// Enables writing to a file, this is used when the client website uploads a
+// a file to the host machine. The remote is provided in the return value of a
+// successful mojom::DesktopSessionControl::BeginFileWrite() call.
+// The remote for this interface is owned in the low-privilege network process
+// and the receiver is bound in the high-privilege desktop integration process.
+interface FileWriter {
+  // Writes |data| to the file opened in the BeginFileWrite call. Returns
+  // |error| if the data could not be written.
+  WriteChunk(array<uint8> data) => (FileTransferError? error);
+
+  // Closes the file opened in the BeginFileWrite call and moves it from the
+  // temporary location to the desktop of the logged-in user. Returns |error| if
+  // a problem occurs during this process.
+  CloseFile() => (FileTransferError? error);
+};
+
+// Returned by FileReader::ReadChunk(). |data| is populated if the read request
+// succeeded, otherwise |error| will indicate why the request failed.
+union ReadChunkResult {
+  array<uint8> data;
+  FileTransferError error;
+};
+
+// Allows reading from a file, this is used when the client website downloads
+// a file from the host machine. The remote is provided after a successful call
+// to mojom::DesktopSessionControl::BeginFileRead().
+// The remote for this interface is owned in the low-privilege network process
+// and the receiver is bound in the high-privilege desktop integration process.
+interface FileReader {
+  // Reads |bytes_to_read| from the file opened in the BeginFileRead call.
+  // |result| contains the file data if successful, otherwise an error.
+  ReadChunk(uint64 bytes_to_read) => (ReadChunkResult result);
+};
+
+// Used by the remoting::FileChooser class which prompts the user to select a
+// file to download to the client machine using a file picker dialog.
+// |filepath| is populated if the user selected a file from the dialog,
+// otherwise |error| indicates why the operation failed.
+// This message is serialized in a child process which is running in the current
+// user context, sent over STDIO as a stream of bytes, then deserialized in the
+// parent process which is the high-privilege desktop integration process.
+[EnableIf=is_win]
+union FileChooserResult {
+  mojo_base.mojom.FilePath filepath;
+  FileTransferError error;
+};
+
+// Error information for file transfer operations.
+// This enum mirrors the remoting::protocol::FileTransfer_Error message.
+struct FileTransferError {
+  // The set of errors which may occur while transferring files.
+  // This enum mirrors the remoting::protocol::FileTransfer_Error_Type enum.
+  enum Type {
+    kUnknown = 0,
+    kCanceled = 1,
+    kUnexpectedError = 2,
+    kProtocolError = 3,
+    kPermissionDenied = 4,
+    kOutOfDiskSpace = 5,
+    kIoError = 6,
+    kNotLoggedIn = 7,
+  };
+  Type type;
+  Int32? api_error_code;
+  string function;
+  string source_file;
+  uint32 line_number;
+};
+
+// Returned when the user has selected a file to begin transferring. This struct
+// contains the metadata for the file and the remote used to read from the file.
+struct BeginFileReadSuccess {
+  pending_associated_remote<FileReader> file_reader;
+  mojo_base.mojom.FilePath filename;
+  uint64 size;
+};
+
+// |success| is populated if BeginFileRead() succeeded, otherwise |error|.
+union BeginFileReadResult {
+  BeginFileReadSuccess success;
+  FileTransferError error;
+};
+
+// Returned when a new file has been written and the client can begin
+// transferring data. This struct contains the remote used to write to the file.
+struct BeginFileWriteSuccess {
+  pending_associated_remote<FileWriter> file_writer;
+};
+
+// |success| is populated if BeginFileRead() succeeded, otherwise |error|.
+union BeginFileWriteResult {
+  BeginFileWriteSuccess success;
+  FileTransferError error;
+};
+
 // Allows the network process to inject input events and control A/V capture in
 // the desktop session.
 // The remote for this interface is owned in the low-privilege network process
@@ -341,6 +437,15 @@
   // change, so the extension can determine whether it should attach to or
   // detach to the WebAuthn proxy API.
   SignalWebAuthnExtension();
+
+  // Opens a file chooser dialog so the user can choose a file to transfer to
+  // the client machine. This action is requested when the remote user chooses
+  // to download a file.
+  BeginFileRead() => (BeginFileReadResult result);
+
+  // Opens a new file for writing when the remote user chooses to upload a file.
+  BeginFileWrite(mojo_base.mojom.FilePath file_path)
+      => (BeginFileWriteResult result);
 };
 
 // |frame| is set and contains valid frame data when a frame is captured
diff --git a/remoting/host/mojom/remoting_mojom_traits.cc b/remoting/host/mojom/remoting_mojom_traits.cc
index b3408522..9e5f3dc 100644
--- a/remoting/host/mojom/remoting_mojom_traits.cc
+++ b/remoting/host/mojom/remoting_mojom_traits.cc
@@ -139,6 +139,102 @@
   return true;
 }
 
+#if BUILDFLAG(IS_WIN)
+// static
+bool mojo::UnionTraits<
+    remoting::mojom::FileChooserResultDataView,
+    ::remoting::Result<base::FilePath,
+                       ::remoting::protocol::FileTransfer_Error>>::
+    Read(remoting::mojom::FileChooserResultDataView data_view,
+         ::remoting::Result<base::FilePath,
+                            ::remoting::protocol::FileTransfer_Error>*
+             out_result) {
+  switch (data_view.tag()) {
+    case remoting::mojom::FileChooserResultDataView::Tag::kFilepath: {
+      base::FilePath filepath;
+      if (!data_view.ReadFilepath(&filepath)) {
+        return false;
+      }
+      out_result->EmplaceSuccess(std::move(filepath));
+      return true;
+    }
+    case remoting::mojom::FileChooserResultDataView::Tag::kError: {
+      ::remoting::protocol::FileTransfer_Error error;
+      if (!data_view.ReadError(&error)) {
+        return false;
+      }
+      out_result->EmplaceError(std::move(error));
+      return true;
+    }
+  }
+}
+#endif  // BUILDFLAG(IS_WIN)
+
+// static
+bool mojo::UnionTraits<
+    remoting::mojom::ReadChunkResultDataView,
+    ::remoting::Result<std::vector<uint8_t>,
+                       ::remoting::protocol::FileTransfer_Error>>::
+    Read(remoting::mojom::ReadChunkResultDataView data_view,
+         ::remoting::Result<std::vector<uint8_t>,
+                            ::remoting::protocol::FileTransfer_Error>*
+             out_result) {
+  switch (data_view.tag()) {
+    case remoting::mojom::ReadChunkResultDataView::Tag::kData: {
+      std::vector<uint8_t> data;
+      if (!data_view.ReadData(&data)) {
+        return false;
+      }
+      out_result->EmplaceSuccess(std::move(data));
+      return true;
+    }
+    case remoting::mojom::ReadChunkResultDataView::Tag::kError: {
+      ::remoting::protocol::FileTransfer_Error error;
+      if (!data_view.ReadError(&error)) {
+        return false;
+      }
+      out_result->EmplaceError(std::move(error));
+      return true;
+    }
+  }
+}
+
+// static
+bool mojo::StructTraits<remoting::mojom::FileTransferErrorDataView,
+                        ::remoting::protocol::FileTransfer_Error>::
+    Read(remoting::mojom::FileTransferErrorDataView data_view,
+         ::remoting::protocol::FileTransfer_Error* out_error) {
+  ::remoting::protocol::FileTransfer_Error_Type type;
+  if (!data_view.ReadType(&type)) {
+    return false;
+  }
+  out_error->set_type(type);
+
+  absl::optional<int32_t> api_error_code;
+  if (!data_view.ReadApiErrorCode(&api_error_code)) {
+    return false;
+  }
+  if (api_error_code) {
+    out_error->set_api_error_code(*api_error_code);
+  }
+
+  std::string function;
+  if (!data_view.ReadFunction(&function)) {
+    return false;
+  }
+  out_error->set_function(std::move(function));
+
+  std::string source_file;
+  if (!data_view.ReadSourceFile(&source_file)) {
+    return false;
+  }
+  out_error->set_source_file(std::move(source_file));
+
+  out_error->set_line_number(data_view.line_number());
+
+  return true;
+}
+
 // static
 bool mojo::StructTraits<remoting::mojom::KeyboardLayoutDataView,
                         ::remoting::protocol::KeyboardLayout>::
@@ -192,7 +288,7 @@
     return false;
   }
   if (caps_lock_state.has_value()) {
-    out_event->set_caps_lock_state(caps_lock_state.value());
+    out_event->set_caps_lock_state(*caps_lock_state);
   }
 
   absl::optional<bool> num_lock_state;
@@ -200,7 +296,7 @@
     return false;
   }
   if (num_lock_state.has_value()) {
-    out_event->set_num_lock_state(num_lock_state.value());
+    out_event->set_num_lock_state(*num_lock_state);
   }
 
   return true;
@@ -258,7 +354,7 @@
     return false;
   }
   if (x.has_value()) {
-    out_event->set_x(x.value());
+    out_event->set_x(*x);
   }
 
   absl::optional<int32_t> y;
@@ -266,7 +362,7 @@
     return false;
   }
   if (y.has_value()) {
-    out_event->set_y(y.value());
+    out_event->set_y(*y);
   }
 
   if (data_view.button() != remoting::mojom::MouseButton::kUndefined) {
@@ -282,7 +378,7 @@
     return false;
   }
   if (button_down.has_value()) {
-    out_event->set_button_down(button_down.value());
+    out_event->set_button_down(*button_down);
   }
 
   absl::optional<float> wheel_delta_x;
@@ -290,7 +386,7 @@
     return false;
   }
   if (wheel_delta_x.has_value()) {
-    out_event->set_wheel_delta_x(wheel_delta_x.value());
+    out_event->set_wheel_delta_x(*wheel_delta_x);
   }
 
   absl::optional<float> wheel_delta_y;
@@ -298,7 +394,7 @@
     return false;
   }
   if (wheel_delta_y.has_value()) {
-    out_event->set_wheel_delta_y(wheel_delta_y.value());
+    out_event->set_wheel_delta_y(*wheel_delta_y);
   }
 
   absl::optional<float> wheel_ticks_x;
@@ -306,7 +402,7 @@
     return false;
   }
   if (wheel_ticks_x.has_value()) {
-    out_event->set_wheel_ticks_x(wheel_ticks_x.value());
+    out_event->set_wheel_ticks_x(*wheel_ticks_x);
   }
 
   absl::optional<float> wheel_ticks_y;
@@ -314,7 +410,7 @@
     return false;
   }
   if (wheel_ticks_y.has_value()) {
-    out_event->set_wheel_ticks_y(wheel_ticks_y.value());
+    out_event->set_wheel_ticks_y(*wheel_ticks_y);
   }
 
   absl::optional<int32_t> delta_x;
@@ -322,7 +418,7 @@
     return false;
   }
   if (delta_x.has_value()) {
-    out_event->set_delta_x(delta_x.value());
+    out_event->set_delta_x(*delta_x);
   }
 
   absl::optional<int32_t> delta_y;
@@ -330,7 +426,7 @@
     return false;
   }
   if (delta_y.has_value()) {
-    out_event->set_delta_y(delta_y.value());
+    out_event->set_delta_y(*delta_y);
   }
 
   return true;
diff --git a/remoting/host/mojom/remoting_mojom_traits.h b/remoting/host/mojom/remoting_mojom_traits.h
index 39bbf9a99..4e5fea4 100644
--- a/remoting/host/mojom/remoting_mojom_traits.h
+++ b/remoting/host/mojom/remoting_mojom_traits.h
@@ -10,14 +10,17 @@
 #include <string>
 
 #include "base/containers/span.h"
+#include "base/files/file_path.h"
 #include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/base/byte_string_mojom_traits.h"
+#include "mojo/public/cpp/base/file_path_mojom_traits.h"
 #include "mojo/public/cpp/bindings/array_traits.h"
 #include "mojo/public/cpp/bindings/array_traits_protobuf.h"
 #include "mojo/public/cpp/bindings/enum_traits.h"
 #include "mojo/public/cpp/bindings/map_traits_protobuf.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
+#include "remoting/base/result.h"
 #include "remoting/host/base/desktop_environment_options.h"
 #include "remoting/host/base/screen_resolution.h"
 #include "remoting/host/mojom/desktop_session.mojom-shared.h"
@@ -27,6 +30,8 @@
 #include "remoting/proto/audio.pb.h"
 #include "remoting/proto/control.pb.h"
 #include "remoting/proto/event.pb.h"
+#include "remoting/proto/file_transfer.pb.h"
+#include "remoting/protocol/file_transfer_helpers.h"
 #include "remoting/protocol/transport.h"
 #include "services/network/public/cpp/ip_endpoint_mojom_traits.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -562,6 +567,184 @@
 };
 
 template <>
+class mojo::UnionTraits<
+    remoting::mojom::ReadChunkResultDataView,
+    ::remoting::Result<std::vector<uint8_t>,
+                       ::remoting::protocol::FileTransfer_Error>> {
+ public:
+  static remoting::mojom::ReadChunkResultDataView::Tag GetTag(
+      const ::remoting::Result<std::vector<uint8_t>,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    if (result.is_success())
+      return remoting::mojom::ReadChunkResultDataView::Tag::kData;
+    else if (result.is_error())
+      return remoting::mojom::ReadChunkResultDataView::Tag::kError;
+
+    NOTREACHED();
+    return remoting::mojom::ReadChunkResultDataView::Tag::kError;
+  }
+
+  static const std::vector<uint8_t>& data(
+      const ::remoting::Result<std::vector<uint8_t>,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    return result.success();
+  }
+
+  static const ::remoting::protocol::FileTransfer_Error& error(
+      const ::remoting::Result<std::vector<uint8_t>,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    return result.error();
+  }
+
+  static bool Read(
+      remoting::mojom::ReadChunkResultDataView data_view,
+      ::remoting::Result<std::vector<uint8_t>,
+                         ::remoting::protocol::FileTransfer_Error>* out_result);
+};
+
+template <>
+class mojo::StructTraits<remoting::mojom::FileTransferErrorDataView,
+                         ::remoting::protocol::FileTransfer_Error> {
+ public:
+  static ::remoting::protocol::FileTransfer_Error_Type type(
+      const ::remoting::protocol::FileTransfer_Error& error) {
+    return error.type();
+  }
+
+  static absl::optional<int32_t> api_error_code(
+      const ::remoting::protocol::FileTransfer_Error& error) {
+    if (error.has_api_error_code()) {
+      return error.api_error_code();
+    }
+    return absl::nullopt;
+  }
+
+  static const std::string& function(
+      const ::remoting::protocol::FileTransfer_Error& error) {
+    return error.function();
+  }
+
+  static const std::string& source_file(
+      const ::remoting::protocol::FileTransfer_Error& error) {
+    return error.source_file();
+  }
+
+  static uint32_t line_number(
+      const ::remoting::protocol::FileTransfer_Error& error) {
+    return error.line_number();
+  }
+
+  static bool Read(remoting::mojom::FileTransferErrorDataView data_view,
+                   ::remoting::protocol::FileTransfer_Error* out_error);
+};
+
+template <>
+struct EnumTraits<remoting::mojom::FileTransferError_Type,
+                  ::remoting::protocol::FileTransfer_Error_Type> {
+  static remoting::mojom::FileTransferError_Type ToMojom(
+      ::remoting::protocol::FileTransfer_Error_Type input) {
+    switch (input) {
+      case ::remoting::protocol::FileTransfer_Error::UNSPECIFIED:
+        return remoting::mojom::FileTransferError_Type::kUnknown;
+      case ::remoting::protocol::FileTransfer_Error::CANCELED:
+        return remoting::mojom::FileTransferError_Type::kCanceled;
+      case ::remoting::protocol::FileTransfer_Error::UNEXPECTED_ERROR:
+        return remoting::mojom::FileTransferError_Type::kUnexpectedError;
+      case ::remoting::protocol::FileTransfer_Error::PROTOCOL_ERROR:
+        return remoting::mojom::FileTransferError_Type::kProtocolError;
+      case ::remoting::protocol::FileTransfer_Error::PERMISSION_DENIED:
+        return remoting::mojom::FileTransferError_Type::kPermissionDenied;
+      case ::remoting::protocol::FileTransfer_Error::OUT_OF_DISK_SPACE:
+        return remoting::mojom::FileTransferError_Type::kOutOfDiskSpace;
+      case ::remoting::protocol::FileTransfer_Error::IO_ERROR:
+        return remoting::mojom::FileTransferError_Type::kIoError;
+      case ::remoting::protocol::FileTransfer_Error::NOT_LOGGED_IN:
+        return remoting::mojom::FileTransferError_Type::kNotLoggedIn;
+    }
+
+    NOTREACHED();
+    return remoting::mojom::FileTransferError_Type::kUnknown;
+  }
+
+  static bool FromMojom(remoting::mojom::FileTransferError_Type input,
+                        ::remoting::protocol::FileTransfer_Error_Type* out) {
+    switch (input) {
+      case remoting::mojom::FileTransferError_Type::kUnknown:
+        *out = ::remoting::protocol::FileTransfer_Error::UNSPECIFIED;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kCanceled:
+        *out = ::remoting::protocol::FileTransfer_Error::CANCELED;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kUnexpectedError:
+        *out = ::remoting::protocol::FileTransfer_Error::UNEXPECTED_ERROR;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kProtocolError:
+        *out = ::remoting::protocol::FileTransfer_Error::PROTOCOL_ERROR;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kPermissionDenied:
+        *out = ::remoting::protocol::FileTransfer_Error::PERMISSION_DENIED;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kOutOfDiskSpace:
+        *out = ::remoting::protocol::FileTransfer_Error::OUT_OF_DISK_SPACE;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kIoError:
+        *out = ::remoting::protocol::FileTransfer_Error::IO_ERROR;
+        return true;
+      case remoting::mojom::FileTransferError_Type::kNotLoggedIn:
+        *out = ::remoting::protocol::FileTransfer_Error::NOT_LOGGED_IN;
+        return true;
+    }
+
+    NOTREACHED();
+    return false;
+  }
+};
+
+#if BUILDFLAG(IS_WIN)
+template <>
+class mojo::UnionTraits<
+    remoting::mojom::FileChooserResultDataView,
+    ::remoting::Result<base::FilePath,
+                       ::remoting::protocol::FileTransfer_Error>> {
+ public:
+  static remoting::mojom::FileChooserResultDataView::Tag GetTag(
+      const ::remoting::Result<base::FilePath,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    if (result.is_success())
+      return remoting::mojom::FileChooserResultDataView::Tag::kFilepath;
+    else if (result.is_error())
+      return remoting::mojom::FileChooserResultDataView::Tag::kError;
+
+    NOTREACHED();
+    return remoting::mojom::FileChooserResultDataView::Tag::kError;
+  }
+
+  static const base::FilePath& filepath(
+      const ::remoting::Result<base::FilePath,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    return result.success();
+  }
+
+  static const ::remoting::protocol::FileTransfer_Error& error(
+      const ::remoting::Result<base::FilePath,
+                               ::remoting::protocol::FileTransfer_Error>&
+          result) {
+    return result.error();
+  }
+
+  static bool Read(
+      remoting::mojom::FileChooserResultDataView data_view,
+      ::remoting::Result<base::FilePath,
+                         ::remoting::protocol::FileTransfer_Error>* out_result);
+};
+#endif  // BUILDFLAG(IS_WIN)
+
+template <>
 class mojo::StructTraits<remoting::mojom::KeyboardLayoutDataView,
                          ::remoting::protocol::KeyboardLayout> {
  public:
diff --git a/services/device/geolocation/geolocation_service_unittest.cc b/services/device/geolocation/geolocation_service_unittest.cc
index 8f04503f..8364a0a 100644
--- a/services/device/geolocation/geolocation_service_unittest.cc
+++ b/services/device/geolocation/geolocation_service_unittest.cc
@@ -52,7 +52,7 @@
   void SetUp() override {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     chromeos::shill_clients::InitializeFakes();
-    chromeos::NetworkHandler::Initialize();
+    ash::NetworkHandler::Initialize();
 #endif
     network_change_notifier_ = net::NetworkChangeNotifier::CreateMockIfNeeded();
     // We need to initialize the above *before* the base fixture instantiates
@@ -73,7 +73,7 @@
     DeviceServiceTestBase::TearDown();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    chromeos::NetworkHandler::Shutdown();
+    ash::NetworkHandler::Shutdown();
     chromeos::shill_clients::Shutdown();
 #endif
 
diff --git a/services/device/geolocation/wifi_data_provider_chromeos.cc b/services/device/geolocation/wifi_data_provider_chromeos.cc
index bd6473d..f17a3566 100644
--- a/services/device/geolocation/wifi_data_provider_chromeos.cc
+++ b/services/device/geolocation/wifi_data_provider_chromeos.cc
@@ -15,7 +15,7 @@
 #include "chromeos/ash/components/network/network_handler.h"
 #include "services/device/geolocation/wifi_data_provider_handle.h"
 
-using chromeos::NetworkHandler;
+using ::ash::NetworkHandler;
 
 namespace device {
 
diff --git a/services/media_session/public/mojom/media_session.mojom b/services/media_session/public/mojom/media_session.mojom
index b376d2a..8abe36b 100644
--- a/services/media_session/public/mojom/media_session.mojom
+++ b/services/media_session/public/mojom/media_session.mojom
@@ -9,7 +9,7 @@
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "url/mojom/url.mojom";
 
-// Next MinVersion: 14
+// Next MinVersion: 15
 
 [Stable, Extensible]
 enum MediaPlaybackState {
@@ -193,6 +193,9 @@
 
   // Tracks whether the media player is muted.
   [MinVersion=12] bool muted;
+
+  // Tracks whether the associated WebContents has a presentation.
+  [MinVersion=14] bool has_presentation;
 };
 
 // Contains debugging information about a MediaSession. This will be displayed
diff --git a/storage/browser/quota/README.md b/storage/browser/quota/README.md
index c94d182a..7997cdf5 100644
--- a/storage/browser/quota/README.md
+++ b/storage/browser/quota/README.md
@@ -51,7 +51,7 @@
 last-modified-time, and last-accessed-time for each origin (used to implement
 LRU eviction on storage pressure, and Clear Site Data with a time filter), and
 quota granted via the deprecated API
-webkitStorageInfo.requestQuota(PERSISTENT,...).
+navigator.webkitPersistentStorage.requestQuota(1000, ...).
 
 ### QuotaTemporaryStorageEvictor
 Handles eviction and records stats about eviction rounds.
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 54295fe..84c22e00 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -80680,7 +80680,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80712,7 +80712,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80734,7 +80734,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80766,7 +80766,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80788,7 +80788,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80820,7 +80820,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80842,7 +80842,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80874,7 +80874,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80896,7 +80896,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80928,7 +80928,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80950,7 +80950,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -80982,7 +80982,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81004,7 +81004,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81036,7 +81036,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81058,7 +81058,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81090,7 +81090,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81112,7 +81112,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81144,7 +81144,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81166,7 +81166,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81198,7 +81198,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81220,7 +81220,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81252,7 +81252,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81274,7 +81274,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81306,7 +81306,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81328,7 +81328,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81360,7 +81360,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81382,7 +81382,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81414,7 +81414,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81436,7 +81436,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81468,7 +81468,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81490,7 +81490,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81522,7 +81522,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81544,7 +81544,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81576,7 +81576,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81598,7 +81598,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81630,7 +81630,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81652,7 +81652,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81684,7 +81684,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81706,7 +81706,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81738,7 +81738,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81760,7 +81760,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81792,7 +81792,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81814,7 +81814,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81846,7 +81846,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81868,7 +81868,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81900,7 +81900,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81922,7 +81922,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -81954,7 +81954,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -81976,7 +81976,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82008,7 +82008,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82030,7 +82030,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82062,7 +82062,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82084,7 +82084,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82116,7 +82116,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82138,7 +82138,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82170,7 +82170,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82192,7 +82192,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82224,7 +82224,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82246,7 +82246,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -82278,7 +82278,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82300,7 +82300,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82333,7 +82333,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82355,7 +82355,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82388,7 +82388,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82410,7 +82410,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82443,7 +82443,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82465,7 +82465,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82498,7 +82498,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82520,7 +82520,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82553,7 +82553,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82575,7 +82575,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82608,7 +82608,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82630,7 +82630,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82663,7 +82663,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82685,7 +82685,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82718,7 +82718,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82740,7 +82740,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82773,7 +82773,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82795,7 +82795,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82828,7 +82828,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82851,7 +82851,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82884,7 +82884,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82907,7 +82907,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82940,7 +82940,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -82963,7 +82963,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -82996,7 +82996,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83019,7 +83019,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83052,7 +83052,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83075,7 +83075,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83108,7 +83108,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83131,7 +83131,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83164,7 +83164,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83187,7 +83187,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83220,7 +83220,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83243,7 +83243,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83276,7 +83276,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83299,7 +83299,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83332,7 +83332,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83355,7 +83355,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83388,7 +83388,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83411,7 +83411,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83444,7 +83444,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83467,7 +83467,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83500,7 +83500,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83523,7 +83523,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83556,7 +83556,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83579,7 +83579,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83612,7 +83612,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83635,7 +83635,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83668,7 +83668,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83691,7 +83691,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83724,7 +83724,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83747,7 +83747,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83780,7 +83780,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83803,7 +83803,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83836,7 +83836,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83859,7 +83859,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83892,7 +83892,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83915,7 +83915,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -83948,7 +83948,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -83971,7 +83971,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84004,7 +84004,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84027,7 +84027,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84060,7 +84060,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84083,7 +84083,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84116,7 +84116,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84139,7 +84139,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84172,7 +84172,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84195,7 +84195,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84228,7 +84228,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84251,7 +84251,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84284,7 +84284,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84307,7 +84307,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84340,7 +84340,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84362,7 +84362,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84395,7 +84395,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84417,7 +84417,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84450,7 +84450,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84472,7 +84472,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84505,7 +84505,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84527,7 +84527,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84560,7 +84560,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84582,7 +84582,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84615,7 +84615,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84637,7 +84637,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84670,7 +84670,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84692,7 +84692,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84725,7 +84725,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84747,7 +84747,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84780,7 +84780,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84802,7 +84802,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84835,7 +84835,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84858,7 +84858,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84891,7 +84891,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84914,7 +84914,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -84947,7 +84947,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -84970,7 +84970,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85003,7 +85003,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85026,7 +85026,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85059,7 +85059,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85082,7 +85082,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85115,7 +85115,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85138,7 +85138,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85171,7 +85171,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85194,7 +85194,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85227,7 +85227,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85250,7 +85250,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85283,7 +85283,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85306,7 +85306,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -85338,7 +85338,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85360,7 +85360,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -85392,7 +85392,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85414,7 +85414,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -85446,7 +85446,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85468,7 +85468,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -85500,7 +85500,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85522,7 +85522,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85555,7 +85555,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85577,7 +85577,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85610,7 +85610,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85632,7 +85632,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85665,7 +85665,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85687,7 +85687,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85720,7 +85720,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85742,7 +85742,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85775,7 +85775,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85797,7 +85797,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85830,7 +85830,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85852,7 +85852,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85885,7 +85885,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85907,7 +85907,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85940,7 +85940,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -85962,7 +85962,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -85995,7 +85995,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86017,7 +86017,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86049,7 +86049,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86071,7 +86071,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86103,7 +86103,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86125,7 +86125,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86157,7 +86157,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86179,7 +86179,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86212,7 +86212,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86234,7 +86234,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86267,7 +86267,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86289,7 +86289,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86322,7 +86322,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86344,7 +86344,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86376,7 +86376,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86399,7 +86399,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86431,7 +86431,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86454,7 +86454,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86486,7 +86486,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86509,7 +86509,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86541,7 +86541,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86563,7 +86563,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86595,7 +86595,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86617,7 +86617,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -86649,7 +86649,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86671,7 +86671,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86704,7 +86704,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86726,7 +86726,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86759,7 +86759,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86781,7 +86781,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86814,7 +86814,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86836,7 +86836,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86869,7 +86869,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86891,7 +86891,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86924,7 +86924,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -86946,7 +86946,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -86979,7 +86979,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87001,7 +87001,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87034,7 +87034,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87056,7 +87056,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87089,7 +87089,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87111,7 +87111,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87144,7 +87144,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87166,7 +87166,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87198,7 +87198,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87220,7 +87220,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87252,7 +87252,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87274,7 +87274,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87306,7 +87306,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87328,7 +87328,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87360,7 +87360,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87382,7 +87382,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87414,7 +87414,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87436,7 +87436,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87468,7 +87468,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87490,7 +87490,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -87522,7 +87522,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87544,7 +87544,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87577,7 +87577,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87599,7 +87599,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87632,7 +87632,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87654,7 +87654,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87687,7 +87687,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87709,7 +87709,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87742,7 +87742,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87764,7 +87764,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87797,7 +87797,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87819,7 +87819,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87852,7 +87852,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87874,7 +87874,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87907,7 +87907,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87929,7 +87929,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -87962,7 +87962,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -87984,7 +87984,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest",
@@ -88017,7 +88017,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88039,7 +88039,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88071,7 +88071,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88093,7 +88093,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88125,7 +88125,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88147,7 +88147,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88179,7 +88179,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88201,7 +88201,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88233,7 +88233,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88255,7 +88255,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88287,7 +88287,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88309,7 +88309,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88341,7 +88341,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88363,7 +88363,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88395,7 +88395,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88417,7 +88417,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88449,7 +88449,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88471,7 +88471,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88503,7 +88503,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88525,7 +88525,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88557,7 +88557,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88579,7 +88579,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88611,7 +88611,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88633,7 +88633,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88665,7 +88665,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88687,7 +88687,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88719,7 +88719,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88741,7 +88741,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88773,7 +88773,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88795,7 +88795,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88827,7 +88827,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88849,7 +88849,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88881,7 +88881,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88903,7 +88903,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88935,7 +88935,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -88957,7 +88957,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -88989,7 +88989,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89011,7 +89011,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89043,7 +89043,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89065,7 +89065,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89097,7 +89097,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89119,7 +89119,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89151,7 +89151,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89173,7 +89173,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89205,7 +89205,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89227,7 +89227,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89259,7 +89259,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89281,7 +89281,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89313,7 +89313,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89335,7 +89335,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89367,7 +89367,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89389,7 +89389,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89421,7 +89421,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89443,7 +89443,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89475,7 +89475,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89497,7 +89497,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89529,7 +89529,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89551,7 +89551,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89583,7 +89583,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89605,7 +89605,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89637,7 +89637,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89659,7 +89659,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89691,7 +89691,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -89713,7 +89713,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--readline-timeout",
           "600",
           "--xctest"
@@ -89745,7 +89745,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index d99d8265..cc04210 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -1382,6 +1382,23 @@
       ],
     },
   },
+  # Xcode 14 beta 5.
+  'xcode_14_beta_5': {
+    '$mixin_append': {
+      'args': [
+        '--xcode-build-version',
+        '14a5294e'
+      ],
+    },
+    'swarming': {
+      'named_caches': [
+        {
+          'name': 'xcode_ios_14a5294e',
+          'path': 'Xcode.app',
+        },
+      ],
+    },
+  },
   # Xcode 14 on iOS main.
   'xcode_14_main': {
     '$mixin_append': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index cc58025..d55ad97 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3454,7 +3454,7 @@
           'mac_beta_x64',
           'mac_toolchain',
           'out_dir_arg',
-          'xcode_14_beta',
+          'xcode_14_beta_5',
           # crbug/1343123: temporarily increasing readline timeout due to slow simulator start up time.
           'xcode_14_readline_timeout',
           'xctest',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 928f288..309599e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3429,6 +3429,21 @@
             ]
         }
     ],
+    "DeprecateAssistantStylusFeatures": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "DeprecateAssistantStylusFeatures"
+                    ]
+                }
+            ]
+        }
+    ],
     "DeprioritizeTimersUntilDOMContentLoadedExperiment": [
         {
             "platforms": [
@@ -5123,6 +5138,16 @@
                     "name": "Enabled",
                     "enable_features": [
                         "CalendarExperienceKit"
+                    ],
+                    "disable_features": [
+                        "EnableExpKitCalendarTextClassifier"
+                    ]
+                },
+                {
+                    "name": "EnabledWithTextClassifier",
+                    "enable_features": [
+                        "CalendarExperienceKit",
+                        "EnableExpKitCalendarTextClassifier"
                     ]
                 }
             ]
@@ -5916,21 +5941,6 @@
             ]
         }
     ],
-    "LocationBarModelOptimizations": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "LocationBarModelOptimizations"
-                    ]
-                }
-            ]
-        }
-    ],
     "MacAllowBackgroundingProcesses": [
         {
             "platforms": [
@@ -6828,21 +6838,6 @@
             ]
         }
     ],
-    "PartialCustomTabs": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "CCTResizableAllowResizeByUserGesture"
-                    ]
-                }
-            ]
-        }
-    ],
     "PartialCustomTabs3rdPartyPolicy": [
         {
             "platforms": [
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index d10f539..7439e172e 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -255,6 +255,7 @@
     "permissions_policy/policy_value.h",
     "renderer_preferences/renderer_preferences.h",
     "responsiveness_metrics/user_interaction_latency.h",
+    "scheduler/task_attribution_id.h",
     "scheduler/web_scheduler_tracked_feature.h",
     "scheme_registry.h",
     "security/address_space_feature.h",
diff --git a/third_party/blink/public/common/scheduler/task_attribution_id.h b/third_party/blink/public/common/scheduler/task_attribution_id.h
new file mode 100644
index 0000000..f50e7ac
--- /dev/null
+++ b/third_party/blink/public/common/scheduler/task_attribution_id.h
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_SCHEDULER_TASK_ATTRIBUTION_ID_H_
+#define THIRD_PARTY_BLINK_PUBLIC_COMMON_SCHEDULER_TASK_ATTRIBUTION_ID_H_
+
+#include <cstdint>
+#include "base/types/strong_alias.h"
+
+namespace blink::scheduler {
+
+using TaskAttributionIdType = uint32_t;
+
+// TaskAttributionId represents the ID of a task scope, encompassing a task and
+// its continuations. It enables comparison and incrementation operations on it,
+// while abstracting the underlying value from callers.
+class TaskAttributionId {
+ public:
+  explicit TaskAttributionId(TaskAttributionIdType value) : value_(value) {}
+  TaskAttributionId(const TaskAttributionId&) = default;
+  TaskAttributionId& operator=(const TaskAttributionId&) = default;
+  TaskAttributionIdType value() const { return value_; }
+
+  bool operator==(const TaskAttributionId& id) const {
+    return id.value_ == value_;
+  }
+  bool operator!=(const TaskAttributionId& id) const {
+    return id.value_ != value_;
+  }
+  bool operator<(const TaskAttributionId& id) const {
+    return value_ < id.value_;
+  }
+  TaskAttributionId NextId() const { return TaskAttributionId(value_ + 1); }
+
+ private:
+  TaskAttributionIdType value_;
+};
+
+}  // namespace blink::scheduler
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_SCHEDULER_TASK_ATTRIBUTION_ID_H_
diff --git a/third_party/blink/public/web/web_plugin.h b/third_party/blink/public/web/web_plugin.h
index 9773e8d..72dd64ef 100644
--- a/third_party/blink/public/web/web_plugin.h
+++ b/third_party/blink/public/web/web_plugin.h
@@ -149,13 +149,17 @@
     return false;
   }
 
-  // Sets up printing with the specified printParams. Returns the number of
-  // pages to be printed at these settings.
+  // Begins a print session with the given `print_params`. A call to
+  // `PrintPage()` can only be made after after a successful call to
+  // `PrintBegin()`. Returns the number of pages required for the print output.
+  // A returned value of 0 indicates failure.
   virtual int PrintBegin(const WebPrintParams& print_params) { return 0; }
 
+  // Prints the page specified by `page_number`, using the parameters passed to
+  // `PrintBegin()`, into `canvas`.
   virtual void PrintPage(int page_number, cc::PaintCanvas* canvas) {}
 
-  // Ends the print operation.
+  // Ends the print session. Further calls to `PrintPages()` will fail.
   virtual void PrintEnd() {}
 
   virtual bool HasSelection() const { return false; }
diff --git a/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc b/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
index 151a2d5..3a0bbcb 100644
--- a/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
@@ -106,11 +106,11 @@
       //   have also elided creating a new scope entirely. 2) If there is no
       //   current running task, set the parent to absl::nullopt, making the
       //   current callback a root task.
-      absl::optional<scheduler::TaskId> parent_id =
+      absl::optional<scheduler::TaskAttributionId> parent_id =
           callback_->GetParentTaskId();
       if (!parent_id) {
-        parent_id =
-            tracker->RunningTaskId(callback_->CallbackRelevantScriptState());
+        parent_id = tracker->RunningTaskAttributionId(
+            callback_->CallbackRelevantScriptState());
       }
       task_attribution_scope_ = tracker->CreateTaskScope(
           callback_->CallbackRelevantScriptState(), parent_id);
diff --git a/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc b/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
index 3188fe6..23b7d12 100644
--- a/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
+++ b/third_party/blink/renderer/bindings/core/v8/scheduled_action.cc
@@ -64,7 +64,8 @@
     arguments_ = arguments;
     auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
     if (tracker && script_state->World().IsMainWorld()) {
-      function_->SetParentTaskId(tracker->RunningTaskId(script_state));
+      function_->SetParentTaskId(
+          tracker->RunningTaskAttributionId(script_state));
     }
   } else {
     UseCounter::Count(target, WebFeature::kScheduledActionIgnored);
@@ -84,7 +85,7 @@
     code_ = handler;
     auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
     if (tracker && script_state->World().IsMainWorld()) {
-      code_parent_task_id_ = tracker->RunningTaskId(script_state);
+      code_parent_task_id_ = tracker->RunningTaskAttributionId(script_state);
     }
   } else {
     UseCounter::Count(target, WebFeature::kScheduledActionIgnored);
diff --git a/third_party/blink/renderer/bindings/core/v8/scheduled_action.h b/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
index fc5df33d..cb73ef3d 100644
--- a/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
+++ b/third_party/blink/renderer/bindings/core/v8/scheduled_action.h
@@ -32,10 +32,10 @@
 #define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCHEDULED_ACTION_H_
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "v8/include/v8.h"
@@ -78,7 +78,7 @@
   Member<V8Function> function_;
   HeapVector<ScriptValue> arguments_;
   String code_;
-  absl::optional<scheduler::TaskId> code_parent_task_id_;
+  absl::optional<scheduler::TaskAttributionId> code_parent_task_id_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 0cef5d1..9a9b7a41 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -895,8 +895,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_save_file_picker_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_scheduler_post_task_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_scheduler_post_task_options.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_id.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_id.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_attribution_id.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_attribution_id.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_request.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_request.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_sensor_error_event_init.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 0f06adc..bf50224 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -653,7 +653,7 @@
           "//third_party/blink/renderer/modules/scheduler/scheduler.idl",
           "//third_party/blink/renderer/modules/scheduler/scheduler_post_task_callback.idl",
           "//third_party/blink/renderer/modules/scheduler/scheduler_post_task_options.idl",
-          "//third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.idl",
+          "//third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.idl",
           "//third_party/blink/renderer/modules/scheduler/task_controller.idl",
           "//third_party/blink/renderer/modules/scheduler/task_controller_init.idl",
           "//third_party/blink/renderer/modules/scheduler/task_priority_change_event.idl",
diff --git a/third_party/blink/renderer/core/editing/substring_util.mm b/third_party/blink/renderer/core/editing/substring_util.mm
index bb3815d..6dc25f21 100644
--- a/third_party/blink/renderer/core/editing/substring_util.mm
+++ b/third_party/blink/renderer/core/editing/substring_util.mm
@@ -86,7 +86,8 @@
 
     const Node& container = it.CurrentContainer();
     const LayoutObject* layout_object = container.GetLayoutObject();
-    DCHECK(layout_object);
+    if (!layout_object)
+      continue;
 
     // There are two ways that the size of text can be affected by the user. One
     // is the page scale factor, which is what the user changes by pinching on
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 77b574c3..cae474b 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -1821,7 +1821,7 @@
   ScriptState* script_state = callback->CallbackRelevantScriptState();
   auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
   if (tracker && script_state->World().IsMainWorld()) {
-    callback->SetParentTaskId(tracker->RunningTaskId(script_state));
+    callback->SetParentTaskId(tracker->RunningTaskAttributionId(script_state));
   }
   Microtask::EnqueueMicrotask(
       WTF::Bind(&V8VoidFunction::InvokeAndReportException,
diff --git a/third_party/blink/renderer/core/html/build.gni b/third_party/blink/renderer/core/html/build.gni
index c18d792..abf6554d 100644
--- a/third_party/blink/renderer/core/html/build.gni
+++ b/third_party/blink/renderer/core/html/build.gni
@@ -614,8 +614,6 @@
   "parser/html_token.h",
   "parser/html_tokenizer.cc",
   "parser/html_tokenizer.h",
-  "parser/html_tokenizer_metrics_reporter.cc",
-  "parser/html_tokenizer_metrics_reporter.h",
   "parser/html_tree_builder.cc",
   "parser/html_tree_builder.h",
   "parser/html_view_source_parser.cc",
@@ -722,7 +720,6 @@
   "parser/background_html_scanner_test.cc",
   "parser/html_resource_preloader_test.cc",
   "parser/html_srcset_parser_test.cc",
-  "parser/html_tokenizer_metrics_reporter_test.cc",
   "parser/html_tree_builder_test.cc",
   "parser/html_tokenizer_test.cc",
   "parser/html_view_source_parser_test.cc",
diff --git a/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc b/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
index b0a0120..ffd1759 100644
--- a/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
+++ b/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
@@ -337,18 +337,12 @@
   if (!input || input->IsDisabledFormControl() || !event)
     return;
 
-  auto* thumb = To<SliderThumbElement>(
-      GetTreeScope().getElementById(shadow_element_names::kIdSliderThumb));
-
-  // TODO: Also do this for touchcancel?
   if (event->type() == event_type_names::kTouchend) {
-    if (!touch_moved_ && thumb)
-      thumb->SetPositionFromPoint(start_point_);
+    // TODO: Also do this for touchcancel?
     input->DispatchFormControlChangeEvent();
     event->SetDefaultHandled();
     sliding_direction_ = Direction::kNoMove;
     touch_started_ = false;
-    touch_moved_ = false;
     return;
   }
 
@@ -359,6 +353,8 @@
   }
 
   TouchList* touches = event->targetTouches();
+  auto* thumb = To<SliderThumbElement>(
+      GetTreeScope().getElementById(shadow_element_names::kIdSliderThumb));
   if (!thumb || !touches)
     return;
 
@@ -367,9 +363,8 @@
       start_point_ = touches->item(0)->AbsoluteLocation();
       sliding_direction_ = Direction::kNoMove;
       touch_started_ = true;
-      touch_moved_ = false;
+      thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation());
     } else if (touch_started_) {
-      touch_moved_ = true;
       LayoutPoint current_point = touches->item(0)->AbsoluteLocation();
       if (sliding_direction_ == Direction::kNoMove) {
         // Still needs to update the direction.
diff --git a/third_party/blink/renderer/core/html/forms/slider_thumb_element.h b/third_party/blink/renderer/core/html/forms/slider_thumb_element.h
index 437ff3f..15f1cec 100644
--- a/third_party/blink/renderer/core/html/forms/slider_thumb_element.h
+++ b/third_party/blink/renderer/core/html/forms/slider_thumb_element.h
@@ -111,7 +111,6 @@
 
   bool has_touch_event_handler_ = false;
   bool touch_started_ = false;
-  bool touch_moved_ = false;
   Direction sliding_direction_ = Direction::kNoMove;
   LayoutPoint start_point_;
 };
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
index 14600a8..a02061e 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -537,12 +537,6 @@
         document.UkmSourceID(), document.UkmRecorder());
   }
 
-  if (GetDocument()->IsInOutermostMainFrame() &&
-      !task_runner_state_->IsSynchronous()) {
-    tokenizer_metrics_reporter_ =
-        std::make_unique<HTMLTokenizerMetricsReporter>(tokenizer_.get());
-  }
-
   // Don't create preloader for parsing clipboard content.
   if (content_policy == kDisallowScriptingAndPluginContent)
     return;
@@ -592,8 +586,6 @@
   insertion_preload_scanner_.reset();
   background_script_scanner_.Reset();
   background_scanner_.Reset();
-  // `tokenizer_metrics_reporter_` has a reference to `tokenizer_`.
-  tokenizer_metrics_reporter_.reset();
   // Oilpan: It is important to clear token_ to deallocate backing memory of
   // HTMLToken::data_ and let the allocator reuse the memory for
   // HTMLToken::data_ of a next HTMLDocumentParser. We need to clear
@@ -842,9 +834,6 @@
       RUNTIME_CALL_TIMER_SCOPE(
           V8PerIsolateData::MainThreadIsolate(),
           RuntimeCallStats::CounterId::kHTMLTokenizerNextToken);
-      if (tokenizer_metrics_reporter_)
-        tokenizer_metrics_reporter_->WillProcessNextToken(input_.Current());
-
       if (!tokenizer_->NextToken(input_.Current(), Token()))
         break;
       budget--;
@@ -969,11 +958,6 @@
   // parser.
   Token().Clear();
 
-  if (tokenizer_metrics_reporter_) {
-    tokenizer_metrics_reporter_->WillConstructTreeFromToken(atomic_token,
-                                                            input_.Current());
-  }
-
   tree_builder_->ConstructTree(&atomic_token);
   CheckIfBlockingStylesheetAdded();
 }
@@ -995,9 +979,6 @@
   TRACE_EVENT2("blink", "HTMLDocumentParser::insert", "source_length",
                source.length(), "parser", (void*)this);
 
-  if (tokenizer_metrics_reporter_ && !source.IsEmpty())
-    tokenizer_metrics_reporter_->OnDocumentWrite(input_.Current());
-
   SegmentedString excluded_line_number_source(source);
   excluded_line_number_source.SetExcludeLineNumbers();
   input_.InsertAtCurrentInsertionPoint(excluded_line_number_source);
@@ -1072,8 +1053,6 @@
     }
   }
 
-  if (tokenizer_metrics_reporter_)
-    tokenizer_metrics_reporter_->WillAppend(input_source);
   input_.AppendToEnd(source);
   task_runner_state_->MarkSeenFirstByte();
 
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.h b/third_party/blink/renderer/core/html/parser/html_document_parser.h
index 9d3cc6e8..a9f7900 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.h
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.h
@@ -41,7 +41,6 @@
 #include "third_party/blink/renderer/core/html/parser/html_preload_scanner.h"
 #include "third_party/blink/renderer/core/html/parser/html_token.h"
 #include "third_party/blink/renderer/core/html/parser/html_tokenizer.h"
-#include "third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h"
 #include "third_party/blink/renderer/core/html/parser/parser_synchronization_policy.h"
 #include "third_party/blink/renderer/core/html/parser/preload_request.h"
 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
@@ -112,16 +111,6 @@
 
   HTMLTokenizer* Tokenizer() const { return tokenizer_.get(); }
 
-  void SetTokenizerState(const AtomicHTMLToken& token,
-                         HTMLTokenizer::State state) {
-    DCHECK(tokenizer_);
-    if (tokenizer_metrics_reporter_) {
-      tokenizer_metrics_reporter_->WillChangeTokenizerState(input_.Current(),
-                                                            token, state);
-    }
-    tokenizer_->SetState(state);
-  }
-
   TextPosition GetTextPosition() const final;
   OrdinalNumber LineNumber() const final;
 
@@ -230,7 +219,6 @@
   Member<HTMLParserReentryPermit> reentry_permit_ =
       MakeGarbageCollected<HTMLParserReentryPermit>();
 
-  std::unique_ptr<HTMLTokenizerMetricsReporter> tokenizer_metrics_reporter_;
   std::unique_ptr<HTMLToken> token_;
   std::unique_ptr<HTMLTokenizer> tokenizer_;
   Member<HTMLParserScriptRunner> script_runner_;
diff --git a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.cc b/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.cc
deleted file mode 100644
index e54b7e49..0000000
--- a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.cc
+++ /dev/null
@@ -1,200 +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 "third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h"
-
-#include "base/metrics/histogram_functions.h"
-#include "base/task/sequenced_task_runner.h"
-#include "third_party/blink/renderer/core/html/parser/atomic_html_token.h"
-#include "third_party/blink/renderer/core/html/parser/html_token.h"
-#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
-#include "third_party/blink/renderer/platform/text/segmented_string.h"
-
-namespace blink {
-namespace {
-
-const LChar kCData[] = "<![CDATA[";
-// Don't include the \0 in the count.
-constexpr wtf_size_t kCDataLength =
-    static_cast<wtf_size_t>(std::size(kCData) - 1);
-
-}  // namespace
-
-// static
-base::RepeatingClosure*
-    HTMLTokenizerMetricsReporter::metrics_logged_callback_for_test_ = nullptr;
-
-HTMLTokenizerMetricsReporter::BackgroundReporter::~BackgroundReporter() {
-  // Only log if something was actually parsed.
-  if (input_length_encountered_ == 0)
-    return;
-
-  const int bitmask =
-      (document_write_encountered_ ? 1 : 0) |
-      (speculative_state_mismatch_ ? 2 : 0) |
-      (index_of_null_char_ != std::numeric_limits<unsigned>::max() ? 4 : 0) |
-      (index_of_cdata_section_ != std::numeric_limits<unsigned>::max() ? 8 : 0);
-  base::UmaHistogramExactLinear("Blink.Tokenizer.MainDocument.ATypicalStates",
-                                bitmask, 16);
-  if (bitmask != 0) {
-    const unsigned min_position = std::min(
-        std::min(write_or_state_mismatch_position_, index_of_null_char_),
-        index_of_cdata_section_);
-    base::UmaHistogramCounts10M(
-        "Blink.Tokenizer.MainDocument.LocationOfFirstATypicalState",
-        min_position);
-  }
-  if (metrics_logged_callback_for_test_)
-    metrics_logged_callback_for_test_->Run();
-}
-
-void HTMLTokenizerMetricsReporter::BackgroundReporter::WillAppend(
-    const String& content) {
-  UpdateIndexOfNullChar(content);
-  UpdateIndexOfCDATA(content);
-  input_length_encountered_ += content.length();
-}
-
-void HTMLTokenizerMetricsReporter::BackgroundReporter::DocumentWriteEncountered(
-    int position) {
-  DCHECK(!document_write_encountered_);
-  document_write_encountered_ = true;
-  write_or_state_mismatch_position_ = std::min(
-      write_or_state_mismatch_position_, static_cast<unsigned>(position));
-}
-
-void HTMLTokenizerMetricsReporter::BackgroundReporter::SpeculativeStateMismatch(
-    int position) {
-  DCHECK(!speculative_state_mismatch_);
-  speculative_state_mismatch_ = true;
-  write_or_state_mismatch_position_ = std::min(
-      write_or_state_mismatch_position_, static_cast<unsigned>(position));
-}
-
-void HTMLTokenizerMetricsReporter::BackgroundReporter::UpdateIndexOfNullChar(
-    const String& string) {
-  if (index_of_null_char_ != std::numeric_limits<unsigned>::max())
-    return;
-
-  index_of_null_char_ = string.find(static_cast<UChar>('\0'));
-  if (index_of_null_char_ != kNotFound)
-    index_of_null_char_ += input_length_encountered_;
-  else
-    index_of_null_char_ = std::numeric_limits<unsigned>::max();
-}
-
-bool HTMLTokenizerMetricsReporter::BackgroundReporter::
-    MatchPossibleCDataSection(const String& string,
-                              wtf_size_t start_string_index) {
-  DCHECK_GT(num_matching_cdata_chars_, 0u);
-  DCHECK_LT(start_string_index, kNotFound);
-  const wtf_size_t string_length = string.length();
-  wtf_size_t i = 0;
-  // Iterate through `string` while it matches `kCData`. i starts at 0, but is
-  // relative to `start_string_index`. `num_matching_cdata_chars_` is the
-  // position into `kCData` to start the match from (portion of previous string
-  // that matched).
-  while (i + num_matching_cdata_chars_ < kCDataLength &&
-         (i + start_string_index) < string_length &&
-         string[i + start_string_index] ==
-             kCData[i + num_matching_cdata_chars_]) {
-    ++i;
-  }
-  if (i + num_matching_cdata_chars_ == kCDataLength) {
-    // Matched all cdata.
-    index_of_cdata_section_ =
-        input_length_encountered_ + i + start_string_index - kCDataLength;
-    return true;
-  }
-  if ((i + start_string_index) == string_length) {
-    // This branch is hit in the case of matching all available data, but
-    // more is required for a full match.
-    num_matching_cdata_chars_ += i;
-    return true;
-  }
-  return false;
-}
-
-void HTMLTokenizerMetricsReporter::BackgroundReporter::UpdateIndexOfCDATA(
-    const String& string) {
-  if (index_of_cdata_section_ != std::numeric_limits<unsigned>::max())
-    return;
-
-  if (num_matching_cdata_chars_ != 0) {
-    if (MatchPossibleCDataSection(string, 0))
-      return;
-    num_matching_cdata_chars_ = 0;
-  }
-
-  for (wtf_size_t next_possible_index = 0; next_possible_index != kNotFound;
-       next_possible_index = string.find(kCData[0], next_possible_index)) {
-    num_matching_cdata_chars_ = 1;
-    if (MatchPossibleCDataSection(string, next_possible_index + 1))
-      return;
-    ++next_possible_index;
-  }
-  num_matching_cdata_chars_ = 0;
-}
-
-HTMLTokenizerMetricsReporter::HTMLTokenizerMetricsReporter(
-    const HTMLTokenizer* tokenizer)
-    : tokenizer_(tokenizer),
-      background_reporter_(worker_pool::CreateSequencedTaskRunner(
-          {base::TaskPriority::BEST_EFFORT})) {}
-
-void HTMLTokenizerMetricsReporter::WillConstructTreeFromToken(
-    const AtomicHTMLToken& token,
-    const SegmentedString& input) {
-  if (speculative_state_mismatch_)
-    return;
-
-  if (token.GetType() == HTMLToken::kStartTag) {
-    last_token_was_start_ = true;
-    tokenizer_state_for_start_ =
-        tokenizer_->SpeculativeStateForTag(token.GetName());
-  }
-}
-
-void HTMLTokenizerMetricsReporter::WillChangeTokenizerState(
-    const SegmentedString& input,
-    const AtomicHTMLToken& token,
-    HTMLTokenizer::State state) {
-  if (speculative_state_mismatch_)
-    return;
-
-  if (token.GetType() != HTMLToken::kStartTag &&
-      state != tokenizer_->GetState()) {
-    RecordSpeculativeStateMismatch(input.NumberOfCharactersConsumed());
-  }
-}
-
-void HTMLTokenizerMetricsReporter::OnDocumentWrite(
-    const SegmentedString& input) {
-  if (document_write_encountered_)
-    return;
-  document_write_encountered_ = true;
-  // At the time this is called `input` should have a next segment string with
-  // the data before the write. See InsertionPointRecord.
-  const int length =
-      input.NextSegmentedString()
-          ? input.NextSegmentedString()->NumberOfCharactersConsumed()
-          : input.NumberOfCharactersConsumed();
-  background_reporter_.AsyncCall(&BackgroundReporter::DocumentWriteEncountered)
-      .WithArgs(length);
-}
-
-void HTMLTokenizerMetricsReporter::WillAppend(const String& content) {
-  background_reporter_.AsyncCall(&BackgroundReporter::WillAppend)
-      .WithArgs(content);
-}
-
-void HTMLTokenizerMetricsReporter::RecordSpeculativeStateMismatch(
-    int position) {
-  DCHECK(!speculative_state_mismatch_);
-  speculative_state_mismatch_ = true;
-  background_reporter_.AsyncCall(&BackgroundReporter::SpeculativeStateMismatch)
-      .WithArgs(position);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h b/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h
deleted file mode 100644
index 4206dd6..0000000
--- a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h
+++ /dev/null
@@ -1,143 +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.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_HTML_TOKENIZER_METRICS_REPORTER_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_HTML_TOKENIZER_METRICS_REPORTER_H_
-
-#include <limits>
-
-#include "base/callback.h"
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/html/parser/html_tokenizer.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/sequence_bound.h"
-#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
-
-namespace blink {
-
-class AtomicHTMLToken;
-class SegmentedString;
-
-// HTMLTokenizerMetricsReporter is used to track how often a handful of
-// non-typical cases occur when tokenizing. It specifically tracks the
-// following:
-// . document.write().
-// . A null character.
-// . CDATA section.
-// . How often SpeculativeStateForTag() doesn't match the actual state.
-// This code is called on the critical path, so detection of a null character
-// and CDATA are done in the background. Logging is done once in the destructor
-// (in the background).
-//
-// TODO(crbug.com/1345267): remove this class once data has been collected.
-class CORE_EXPORT HTMLTokenizerMetricsReporter {
-  USING_FAST_MALLOC(HTMLTokenizerMetricsReporter);
-
- public:
-  explicit HTMLTokenizerMetricsReporter(const HTMLTokenizer* tokenizer);
-
-  // Called prior to HTMLTokenizer::NextToken().
-  void WillProcessNextToken(const SegmentedString& input) {
-    if (speculative_state_mismatch_)
-      return;
-
-    if (last_token_was_start_) {
-      last_token_was_start_ = false;
-      if (tokenizer_state_for_start_.has_value() &&
-          tokenizer_state_for_start_ != tokenizer_->GetState()) {
-        RecordSpeculativeStateMismatch(input.NumberOfCharactersConsumed());
-      }
-    }
-  }
-
-  // Called after a token has been created by the tokenizer but before
-  // ConstructTree().
-  void WillConstructTreeFromToken(const AtomicHTMLToken& token,
-                                  const SegmentedString& input);
-
-  // Called when the state of the tokenizer is going to be explicitly set.
-  void WillChangeTokenizerState(const SegmentedString& input,
-                                const AtomicHTMLToken& token,
-                                HTMLTokenizer::State state);
-
-  // Called when document.write() occurs.
-  void OnDocumentWrite(const SegmentedString& input);
-
-  // Called when data is available to be tokenized.
-  void WillAppend(const String& content);
-
-  // BackgroundReporter does the actual metric recording, as well as any
-  // non-trivial processing. The public methods of HTMLTokenizerMetricsReporter
-  // call through to this object so that it can log the metrics in the
-  // destructor.
-  //
-  // NOTE: public for tests, treat as private.
-  class CORE_EXPORT BackgroundReporter {
-    USING_FAST_MALLOC(BackgroundReporter);
-
-   public:
-    ~BackgroundReporter();
-
-    void WillAppend(const String& content);
-    void DocumentWriteEncountered(int position);
-    void SpeculativeStateMismatch(int position);
-
-    unsigned index_of_null_char() const { return index_of_null_char_; }
-
-    unsigned index_of_cdata_section() const { return index_of_cdata_section_; }
-
-   private:
-    void UpdateIndexOfNullChar(const String& string);
-    // Attempts to match a possible cdata secition. `string` is the string to
-    // search in, starting at `start_string_index`. Returns true on success,
-    // or the section matches but the end of input is reached (partial match).
-    bool MatchPossibleCDataSection(const String& string,
-                                   wtf_size_t start_string_index);
-    void UpdateIndexOfCDATA(const String& string);
-
-    bool document_write_encountered_ = false;
-    bool speculative_state_mismatch_ = false;
-    unsigned write_or_state_mismatch_position_ =
-        std::numeric_limits<unsigned>::max();
-
-    // Amount of data encountered to date (sum of length of strings supplied
-    // to WillAppend()).
-    unsigned input_length_encountered_ = 0;
-
-    unsigned index_of_null_char_ = std::numeric_limits<unsigned>::max();
-
-    // Following is used to match a CDATA section:
-    unsigned index_of_cdata_section_ = std::numeric_limits<unsigned>::max();
-    unsigned num_matching_cdata_chars_ = 0;
-  };
-
-  // Run when metrics have been logged. Provided for tests. This callback is
-  // run on a background thread.
-  static base::RepeatingClosure* metrics_logged_callback_for_test_;
-
- private:
-  void RecordSpeculativeStateMismatch(int position);
-
-  const HTMLTokenizer* tokenizer_;
-
-  // True if the last token was a start.
-  bool last_token_was_start_ = false;
-
-  // If the last token was a start tag, this is the corresponding speculative
-  // state (which may not be set).
-  absl::optional<HTMLTokenizer::State> tokenizer_state_for_start_;
-
-  // Whether document.write() was encountered.
-  bool document_write_encountered_ = false;
-
-  // Whether the Tokenizer state from the builder does not match the
-  // speculative state.
-  bool speculative_state_mismatch_ = false;
-
-  WTF::SequenceBound<BackgroundReporter> background_reporter_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_HTML_TOKENIZER_METRICS_REPORTER_H_
diff --git a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter_test.cc b/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter_test.cc
deleted file mode 100644
index e0fb1dc..0000000
--- a/third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter_test.cc
+++ /dev/null
@@ -1,112 +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 "third_party/blink/renderer/core/html/parser/html_tokenizer_metrics_reporter.h"
-
-#include "base/bind.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/core/html/html_document.h"
-#include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
-#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
-#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
-
-namespace blink {
-
-TEST(HTMLTokenizerMetricsReporterTest, FindNullChar) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend(String("123\0", 4u));
-  EXPECT_EQ(3u, reporter.index_of_null_char());
-}
-
-TEST(HTMLTokenizerMetricsReporterTest, FindNullCharSecondChunk) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend("abc");
-  reporter.WillAppend(String("d\0f", 3u));
-  EXPECT_EQ(4u, reporter.index_of_null_char());
-}
-
-TEST(HTMLTokenizerMetricsReporterTest, SimpleCData) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend("abcdef<<![CDATA[");
-  EXPECT_EQ(7u, reporter.index_of_cdata_section());
-}
-
-TEST(HTMLTokenizerMetricsReporterTest, SplitCData) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend("abc<![");
-  reporter.WillAppend("CD");
-  reporter.WillAppend("ATA[");
-  EXPECT_EQ(3u, reporter.index_of_cdata_section());
-}
-
-TEST(HTMLTokenizerMetricsReporterTest, IncompleteCData) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend("abcdef<<![CDATA");
-  EXPECT_EQ(std::numeric_limits<unsigned>::max(),
-            reporter.index_of_cdata_section());
-}
-
-TEST(HTMLTokenizerMetricsReporterTest, SplitWithPartialThenFull) {
-  HTMLTokenizerMetricsReporter::BackgroundReporter reporter;
-  reporter.WillAppend("abc<![");
-  reporter.WillAppend("<![CDATA[");
-  EXPECT_EQ(6u, reporter.index_of_cdata_section());
-}
-
-class HTMLTokenizerMetricsReporterSimTest : public SimTest {
- public:
-  void LoadPageWithContentForReporter(const String& string) {
-    // To ensure metrics reporter is created.
-    Document::SetForceSynchronousParsingForTesting(false);
-    base::RunLoop run_loop;
-    auto quit_closure = run_loop.QuitClosure();
-    HTMLTokenizerMetricsReporter::metrics_logged_callback_for_test_ =
-        &quit_closure;
-
-    String source("https://example.com/p1");
-    SimRequest main_resource(source, "text/html");
-    LoadURL(source);
-    main_resource.Complete(string);
-
-    // Because SetForceSynchronousParsingForTesting(false) was called,
-    // tokenizing doesn't happen immediately. Use this to ensure it runs.
-    base::RunLoop().RunUntilIdle();
-
-    String source2("https://example.com/p2");
-    SimRequest resource2(source2, "text/html");
-    Document::SetForceSynchronousParsingForTesting(true);
-    LoadURL(source2);
-    resource2.Complete("empty");
-
-    run_loop.Run();
-    HTMLTokenizerMetricsReporter::metrics_logged_callback_for_test_ = nullptr;
-  }
-};
-
-TEST_F(HTMLTokenizerMetricsReporterSimTest, UnexpectedState) {
-  base::HistogramTester tester;
-  LoadPageWithContentForReporter("<svg><style></style></svg>");
-  // Bucket 2 means a state mismatch.
-  tester.ExpectUniqueSample("Blink.Tokenizer.MainDocument.ATypicalStates", 2,
-                            1);
-}
-
-TEST_F(HTMLTokenizerMetricsReporterSimTest, DocumentWrite) {
-  base::HistogramTester tester;
-  LoadPageWithContentForReporter("<script>document.write('test');</script>");
-  // Bucket 1 is for document.write().
-  tester.ExpectUniqueSample("Blink.Tokenizer.MainDocument.ATypicalStates", 1,
-                            1);
-}
-
-TEST_F(HTMLTokenizerMetricsReporterSimTest, EmbeddedNull) {
-  base::HistogramTester tester;
-  LoadPageWithContentForReporter(String("<div>t\0ext", 10u));
-  tester.ExpectUniqueSample("Blink.Tokenizer.MainDocument.ATypicalStates", 4,
-                            1);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc
index 887deab..1561b3d 100644
--- a/third_party/blink/renderer/core/html/parser/html_tree_builder.cc
+++ b/third_party/blink/renderer/core/html/parser/html_tree_builder.cc
@@ -677,7 +677,7 @@
     case TagParsingGroup::kPlaintextTag:
       ProcessFakePEndTagIfPInButtonScope();
       tree_.InsertHTMLElement(token);
-      parser_->SetTokenizerState(*token, HTMLTokenizer::kPLAINTEXTState);
+      parser_->Tokenizer()->SetState(HTMLTokenizer::kPLAINTEXTState);
       break;
     case TagParsingGroup::kATag: {
       Element* active_a_tag =
@@ -751,7 +751,7 @@
     case TagParsingGroup::kTextareaTag:
       tree_.InsertHTMLElement(token);
       should_skip_leading_newline_ = true;
-      parser_->SetTokenizerState(*token, HTMLTokenizer::kRCDATAState);
+      parser_->Tokenizer()->SetState(HTMLTokenizer::kRCDATAState);
       original_insertion_mode_ = insertion_mode_;
       frameset_ok_ = false;
       SetInsertionMode(kTextMode);
@@ -2165,7 +2165,7 @@
 
         // We must set the tokenizer's state to DataState explicitly if the
         // tokenizer didn't have a chance to.
-        parser_->SetTokenizerState(*token, HTMLTokenizer::kDataState);
+        parser_->Tokenizer()->SetState(HTMLTokenizer::kDataState);
         return;
       }
       tree_.OpenElements()->Pop();
@@ -2698,7 +2698,7 @@
 void HTMLTreeBuilder::ProcessGenericRCDATAStartTag(AtomicHTMLToken* token) {
   DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
   tree_.InsertHTMLElement(token);
-  parser_->SetTokenizerState(*token, HTMLTokenizer::kRCDATAState);
+  parser_->Tokenizer()->SetState(HTMLTokenizer::kRCDATAState);
   original_insertion_mode_ = insertion_mode_;
   SetInsertionMode(kTextMode);
 }
@@ -2706,7 +2706,7 @@
 void HTMLTreeBuilder::ProcessGenericRawTextStartTag(AtomicHTMLToken* token) {
   DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
   tree_.InsertHTMLElement(token);
-  parser_->SetTokenizerState(*token, HTMLTokenizer::kRAWTEXTState);
+  parser_->Tokenizer()->SetState(HTMLTokenizer::kRAWTEXTState);
   original_insertion_mode_ = insertion_mode_;
   SetInsertionMode(kTextMode);
 }
@@ -2714,7 +2714,7 @@
 void HTMLTreeBuilder::ProcessScriptStartTag(AtomicHTMLToken* token) {
   DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
   tree_.InsertScriptElement(token);
-  parser_->SetTokenizerState(*token, HTMLTokenizer::kScriptDataState);
+  parser_->Tokenizer()->SetState(HTMLTokenizer::kScriptDataState);
   original_insertion_mode_ = insertion_mode_;
 
   TextPosition position = parser_->GetTextPosition();
diff --git a/third_party/blink/renderer/core/input/touch_event_manager_test.cc b/third_party/blink/renderer/core/input/touch_event_manager_test.cc
index aa83914..5266a0a 100644
--- a/third_party/blink/renderer/core/input/touch_event_manager_test.cc
+++ b/third_party/blink/renderer/core/input/touch_event_manager_test.cc
@@ -98,11 +98,6 @@
       Vector<WebPointerEvent>(), Vector<WebPointerEvent>());
   GetEventHandler().DispatchBufferedTouchEvents();
 
-  GetEventHandler().HandlePointerEvent(
-      CreateTouchPointerEvent(WebInputEvent::Type::kPointerUp),
-      Vector<WebPointerEvent>(), Vector<WebPointerEvent>());
-  GetEventHandler().DispatchBufferedTouchEvents();
-
   auto* input =
       To<HTMLInputElement>(GetDocument().getElementById("slideElement"));
   // Allow off by 1 error because it may result in different value in some
diff --git a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
index 004b760..a20c3ee6 100644
--- a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
+++ b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc
@@ -11,12 +11,16 @@
 
 namespace blink {
 
-class CullRectUpdaterTest : public PaintControllerPaintTestBase {
+class CullRectUpdaterTest : public PaintControllerPaintTest {
  protected:
   CullRect GetCullRect(const char* id) {
     return GetLayoutObjectByElementId(id)->FirstFragment().GetCullRect();
   }
 
+  CullRect GetCullRect(const PaintLayer& layer) {
+    return layer.GetLayoutObject().FirstFragment().GetCullRect();
+  }
+
   CullRect GetContentsCullRect(const char* id) {
     return GetLayoutObjectByElementId(id)
         ->FirstFragment()
@@ -24,14 +28,355 @@
   }
 };
 
-// TODO(wangxianzhu): Move other cull rect tests from PaintLayerPainterTest
-// into this file.
+INSTANTIATE_PAINT_TEST_SUITE_P(CullRectUpdaterTest);
 
-CullRect GetCullRect(const PaintLayer& layer) {
-  return layer.GetLayoutObject().FirstFragment().GetCullRect();
+TEST_P(CullRectUpdaterTest, SimpleCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 200px; position: relative'>
+    </div>
+  )HTML");
+
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("target").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, FixedPositionUnderClipPath) {
+TEST_P(CullRectUpdaterTest, TallLayerCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 10000px; position: relative'>
+    </div>
+  )HTML");
+
+  // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped
+  // by the contents rect.
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, WideLayerCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 10000px; height: 200px; position: relative'>
+    </div>
+  )HTML");
+
+  // Same as TallLayerCullRect.
+  EXPECT_EQ(gfx::Rect(0, 0, 4800, 600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, VerticalRightLeftWritingModeDocument) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      html { writing-mode: vertical-rl; }
+      body { margin: 0; }
+    </style>
+    <div id='target' style='width: 10000px; height: 200px; position: relative'>
+    </div>
+  )HTML");
+
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(-5000, 0), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  // A scroll by -5000px is equivalent to a scroll by (10000 - 5000 - 800)px =
+  // 4200px in non-RTL mode. Expanding the resulting rect by 4000px in each
+  // direction and clipping by the contents rect yields this result.
+  EXPECT_EQ(gfx::Rect(200, 0, 8800, 600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, ScaledCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                transform: scaleX(3) scaleY(0.5)'>
+    </div>
+  )HTML");
+
+  // The expansion is 4000 / max(scaleX, scaleY).
+  // TODO(wangxianzhu): The above comment is not right. We should probably
+  // adjust LocalPixelDistanceToExpand() to make the comment true.
+  EXPECT_EQ(gfx::Rect(-7936, -8166, 16267, 17200),
+            GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, ScaledCullRectUnderCompositedScroller) {
+  SetBodyInnerHTML(R"HTML(
+    <div style='width: 200px; height: 300px; overflow: scroll; background: blue;
+                transform: scaleX(3) scaleY(0.5)'>
+      <div id='target' style='height: 400px; position: relative'></div>
+      <div style='width: 10000px; height: 10000px'></div>
+    </div>
+  )HTML");
+
+  // The expansion is 4000 / max(scaleX, scaleY).
+  // TODO(wangxianzhu): The above comment is not right. We should probably
+  // adjust LocalPixelDistanceToExpand() to make the comment true.
+  EXPECT_EQ(gfx::Rect(0, 0, 8200, 8300), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, ScaledAndRotatedCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                transform: scaleX(3) scaleY(0.5) rotateZ(45deg)'>
+    </div>
+  )HTML");
+
+  // The expansion 6599 is 4000 * max_dimension(1x1 rect projected from screen
+  // to local).
+  // TODO(wangxianzhu): The above comment is not right. We should probably
+  // adjust LocalPixelDistanceToExpand() to make the comment true.
+  EXPECT_EQ(gfx::Rect(-6748, -6836, 14236, 14236),
+            GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, ScaledAndRotatedCullRectUnderCompositedScroller) {
+  SetBodyInnerHTML(R"HTML(
+    <div style='width: 200px; height: 300px; overflow: scroll; background: blue;
+                transform: scaleX(3) scaleY(0.5) rotateZ(45deg)'>
+      <div id='target' style='height: 400px; position: relative;
+               will-change: transform'></div>
+      <div style='width: 10000px; height: 10000px'></div>
+    </div>
+  )HTML");
+
+  // The expansion 6599 is 4000 * max_dimension(1x1 rect projected from screen
+  // to local).
+  // TODO(wangxianzhu): The above comment is not right. We should probably
+  // adjust LocalPixelDistanceToExpand() to make the comment true.
+  EXPECT_EQ(gfx::Rect(0, 0, 6799, 6899), GetCullRect("target").Rect());
+  EXPECT_EQ(gfx::Rect(0, 0, 6799, 6899), GetContentsCullRect("target").Rect());
+}
+
+// This is a testcase for https://crbug.com/1227907 where repeated cull rect
+// updates are expensive on the motionmark microbenchmark.
+TEST_P(CullRectUpdaterTest, OptimizeNonCompositedTransformUpdate) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #target {
+        width: 50px;
+        height: 50px;
+        background: green;
+        transform: translate(-8px, -8px);
+      }
+    </style>
+    <div id='target'></div>
+  )HTML");
+
+  // The cull rect should be correctly calculated on first paint.
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("target").Rect());
+
+  // On subsequent paints, fall back to an infinite cull rect.
+  GetDocument().getElementById("target")->setAttribute(
+      html_names::kStyleAttr, "transform: rotate(10deg);");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_TRUE(GetCullRect("target").IsInfinite());
+}
+
+TEST_P(CullRectUpdaterTest, 3DRotated90DegreesCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                transform: rotateY(90deg)'>
+    </div>
+  )HTML");
+
+  EXPECT_TRUE(GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 200, 300)));
+}
+
+TEST_P(CullRectUpdaterTest, 3DRotatedNear90DegreesCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                transform: rotateY(89.9999deg)'>
+    </div>
+  )HTML");
+
+  EXPECT_TRUE(GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 200, 300)));
+}
+
+TEST_P(CullRectUpdaterTest, PerspectiveCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id=target style='transform: perspective(1000px) rotateX(-100deg);'>
+      <div style='width: 2000px; height: 3000px></div>
+    </div>
+  )HTML");
+
+  EXPECT_TRUE(
+      GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 2000, 3000)));
+}
+
+TEST_P(CullRectUpdaterTest, 3D45DegRotatedTallCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <style>body { margin: 0 }</style>
+    <div id='target'
+         style='width: 200px; height: 10000px; transform: rotateY(45deg)'>
+    </div>
+  )HTML");
+
+  EXPECT_TRUE(
+      GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 200, 10000)));
+}
+
+TEST_P(CullRectUpdaterTest, FixedPositionInNonScrollableViewCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target' style='width: 1000px; height: 2000px;
+                            position: fixed; top: 100px; left: 200px;'>
+    </div>
+  )HTML");
+
+  // The cull rect is inflated when scrolling, because fixed elements don't
+  // participate in overscroll.
+  EXPECT_EQ(gfx::Rect(-200, -100, 800, 600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, FixedPositionInScrollableViewCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target' style='width: 1000px; height: 2000px;
+                            position: fixed; top: 100px; left: 200px;'>
+    </div>
+    <div style='height: 3000px'></div>
+  )HTML");
+
+  EXPECT_EQ(gfx::Rect(-200, -100, 800, 600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, LayerOffscreenNearCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                position: absolute; top: 3000px; left: 0px;'>
+    </div>
+  )HTML");
+
+  auto cull_rect = GetCullRect("target").Rect();
+  EXPECT_TRUE(cull_rect.Contains(gfx::Rect(0, 0, 200, 300)));
+}
+
+TEST_P(CullRectUpdaterTest, LayerOffscreenFarCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target'
+         style='width: 200px; height: 300px; will-change: transform;
+                position: absolute; top: 9000px'>
+    </div>
+  )HTML");
+
+  // The layer is too far away from the viewport.
+  EXPECT_FALSE(
+      GetCullRect("target").Rect().Intersects(gfx::Rect(0, 0, 200, 300)));
+}
+
+TEST_P(CullRectUpdaterTest, ScrollingLayerCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      div::-webkit-scrollbar { width: 5px; }
+    </style>
+    <div style='width: 200px; height: 200px; overflow: scroll;
+                background: blue'>
+      <div id='target'
+           style='width: 100px; height: 10000px; position: relative'>
+      </div>
+    </div>
+  )HTML");
+
+  // In screen space, the scroller is (8, 8, 195, 193) (because of overflow clip
+  // of 'target', scrollbar and root margin).
+  // Applying the viewport clip of the root has no effect because
+  // the clip is already small. Mapping it down into the graphics layer
+  // space yields (0, 0, 195, 193). This is then expanded by 4000px and clipped
+  // by the contents rect.
+  EXPECT_EQ(gfx::Rect(0, 0, 195, 4193), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, NonCompositedScrollingLayerCullRect) {
+  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      div::-webkit-scrollbar { width: 5px; }
+    </style>
+    <div style='width: 200px; height: 200px; overflow: scroll'>
+      <div id='target'
+           style='width: 100px; height: 10000px; position: relative'>
+      </div>
+    </div>
+  )HTML");
+
+  // See ScrollingLayerCullRect for the calculation.
+  EXPECT_EQ(gfx::Rect(0, 0, 195, 193), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, ClippedBigLayer) {
+  SetBodyInnerHTML(R"HTML(
+    <div style='width: 1px; height: 1px; overflow: hidden'>
+      <div id='target'
+           style='width: 10000px; height: 10000px; position: relative'>
+      </div>
+    </div>
+  )HTML");
+
+  EXPECT_EQ(gfx::Rect(8, 8, 1, 1), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, TallScrolledLayerCullRect) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='target' style='width: 200px; height: 12000px; position: relative'>
+    </div>
+  )HTML");
+
+  // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped
+  // by the contents rect.
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetCullRect("target").Rect());
+
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 4000), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 8600), GetCullRect("target").Rect());
+
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 4500), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+  // Used the previous cull rect because the scroll amount is small.
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 8600), GetCullRect("target").Rect());
+
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 4600), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+  // Used new cull rect.
+  EXPECT_EQ(gfx::Rect(0, 600, 800, 8600), GetCullRect("target").Rect());
+}
+
+TEST_P(CullRectUpdaterTest, WholeDocumentCullRect) {
+  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
+  GetDocument().GetSettings()->SetMainFrameClipsContent(false);
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      div { background: blue; }
+      ::-webkit-scrollbar { display: none; }
+    </style>
+    <div id='relative'
+         style='width: 200px; height: 10000px; position: relative'>
+    </div>
+    <div id='fixed' style='width: 200px; height: 200px; position: fixed'>
+    </div>
+    <div id='scroll' style='width: 200px; height: 200px; overflow: scroll'>
+      <div id='below-scroll' style='height: 5000px; position: relative'></div>
+      <div style='height: 200px'>Should not paint</div>
+    </div>
+    <div id='normal' style='width: 200px; height: 200px'></div>
+  )HTML");
+
+  // Viewport clipping is disabled.
+  EXPECT_TRUE(GetCullRect(*GetLayoutView().Layer()).IsInfinite());
+  EXPECT_TRUE(GetCullRect("relative").IsInfinite());
+  EXPECT_TRUE(GetCullRect("fixed").IsInfinite());
+  EXPECT_TRUE(GetCullRect("scroll").IsInfinite());
+
+  // Cull rect is normal for contents below scroll other than the viewport.
+  EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("below-scroll").Rect());
+
+  EXPECT_EQ(7u, ContentDisplayItems().size());
+}
+
+TEST_P(CullRectUpdaterTest, FixedPositionUnderClipPath) {
   GetDocument().View()->Resize(800, 600);
   SetBodyInnerHTML(R"HTML(
     <div style="height: 100vh"></div>
@@ -52,7 +397,7 @@
   EXPECT_EQ(gfx::Rect(0, 0, 800, 1000), GetCullRect("fixed").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, FixedPositionUnderClipPathWillChangeTransform) {
+TEST_P(CullRectUpdaterTest, FixedPositionUnderClipPathWillChangeTransform) {
   GetDocument().View()->Resize(800, 600);
   SetBodyInnerHTML(R"HTML(
     <div style="height: 100vh"></div>
@@ -73,7 +418,7 @@
   EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 10000), GetCullRect("fixed").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, AbsolutePositionUnderNonContainingStackingContext) {
+TEST_P(CullRectUpdaterTest, AbsolutePositionUnderNonContainingStackingContext) {
   GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
   SetBodyInnerHTML(R"HTML(
     <div id="scroller" style="width: 200px; height: 200px; overflow: auto;
@@ -96,7 +441,7 @@
             GetCullRect("absolute").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, StackedChildOfNonStackingContextScroller) {
+TEST_P(CullRectUpdaterTest, StackedChildOfNonStackingContextScroller) {
   SetBodyInnerHTML(R"HTML(
     <div id="scroller" style="width: 200px; height: 200px; overflow: auto;
                               background: white">
@@ -137,7 +482,7 @@
   EXPECT_EQ(gfx::Rect(0, 2800, 200, 4200), GetCullRect("child").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, ContentsCullRectCoveringWholeContentsRect) {
+TEST_P(CullRectUpdaterTest, ContentsCullRectCoveringWholeContentsRect) {
   GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
   SetBodyInnerHTML(R"HTML(
     <div id="scroller" style="width: 400px; height: 400px; overflow: scroll">
@@ -166,7 +511,7 @@
   EXPECT_EQ(gfx::Rect(-4000, -8100, 8600, 8120), GetCullRect("child").Rect());
 }
 
-TEST_F(CullRectUpdaterTest, SVGForeignObject) {
+TEST_P(CullRectUpdaterTest, SVGForeignObject) {
   GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
   SetBodyInnerHTML(R"HTML(
     <div id="scroller" style="width: 100px; height: 100px; overflow: scroll">
@@ -202,7 +547,7 @@
   EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate());
 }
 
-TEST_F(CullRectUpdaterTest, LayerUnderSVGHiddenContainer) {
+TEST_P(CullRectUpdaterTest, LayerUnderSVGHiddenContainer) {
   SetBodyInnerHTML(R"HTML(
     <div id="div" style="display: contents">
       <svg id="svg1"></svg>
@@ -221,7 +566,7 @@
   EXPECT_TRUE(GetCullRect("svg1").Rect().IsEmpty());
 }
 
-TEST_F(CullRectUpdaterTest, PerspectiveDescendants) {
+TEST_P(CullRectUpdaterTest, PerspectiveDescendants) {
   SetBodyInnerHTML(R"HTML(
     <div style="perspective: 1000px">
       <div style="height: 300px; transform-style: preserve-3d; contain: strict">
@@ -315,7 +660,9 @@
   )HTML";
 };
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, Opacity) {
+INSTANTIATE_PAINT_TEST_SUITE_P(CullRectUpdateOnPaintPropertyChangeTest);
+
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, Opacity) {
   TestTargetChange("opacity: 0.2", "opacity: 0.8", false, false, false);
   TestTargetChange("opacity: 0.5", "", true, false, true);
   TestTargetChange("", "opacity: 0.5", true, false, true);
@@ -325,7 +672,7 @@
                    false, false, false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, NonPixelMovingFilter) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, NonPixelMovingFilter) {
   TestTargetChange("filter: invert(5%)", "filter: invert(8%)", false, false,
                    false);
   TestTargetChange("filter: invert(5%)", "", true, false, true);
@@ -337,7 +684,7 @@
                    false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, PixelMovingFilter) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, PixelMovingFilter) {
   TestTargetChange("filter: blur(5px)", "filter: blur(8px)", false, false,
                    false);
   TestTargetChange("filter: blur(5px)", "", true, true, true);
@@ -349,7 +696,7 @@
                    false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, Transform) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, Transform) {
   TestTargetChange("transform: translateX(10px)", "transform: translateX(20px)",
                    false, true, false);
   TestTargetChange("transform: translateX(10px)", "", true, true, true);
@@ -361,7 +708,7 @@
                    true, false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, AnimatingTransform) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, AnimatingTransform) {
   html_ = html_ + R"HTML(
     <style>
       @keyframes test {
@@ -377,13 +724,13 @@
   TestTargetChange("", "transform: translateX(10px)", false, false, false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, ScrollContentsSizeChange) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, ScrollContentsSizeChange) {
   TestChildChange("", "width: 3000px", true, true, true);
   TestChildChange("", "height: 3000px", true, true, true);
   TestChildChange("", "width: 50px; height: 50px", true, true, true);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, SmallContentsScroll) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, SmallContentsScroll) {
   // TODO(wangxianzhu): Optimize for scrollers with small contents.
   TestTargetScroll(ScrollOffset(), ScrollOffset(100, 200), false, true, false);
   TestTargetScroll(ScrollOffset(100, 200), ScrollOffset(1000, 1000), false,
@@ -392,7 +739,7 @@
                    false);
 }
 
-TEST_F(CullRectUpdateOnPaintPropertyChangeTest, LargeContentsScroll) {
+TEST_P(CullRectUpdateOnPaintPropertyChangeTest, LargeContentsScroll) {
   html_ = html_ + "<style>#child { width: 10000px; height: 10000px; }</style>";
   // TODO(wangxianzhu): Optimize for small scroll delta.
   TestTargetScroll(ScrollOffset(), ScrollOffset(100, 200), false, true, false);
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
index 2f4329ae..d9064a1 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
@@ -749,379 +749,6 @@
   EXPECT_TRUE(html_layer.NeedsPaintPhaseDescendantOutlines());
 }
 
-TEST_P(PaintLayerPainterTest, SimpleCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target'
-         style='width: 200px; height: 200px; position: relative'>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, TallLayerCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target'
-         style='width: 200px; height: 10000px; position: relative'>
-    </div>
-  )HTML");
-
-  // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped
-  // by the contents rect.
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 4600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, WideLayerCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target'
-         style='width: 10000px; height: 200px; position: relative'>
-    </div>
-  )HTML");
-
-  // Same as TallLayerCullRect.
-  EXPECT_EQ(gfx::Rect(0, 0, 4800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, TallScrolledLayerCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target' style='width: 200px; height: 12000px; position: relative'>
-    </div>
-  )HTML");
-
-  // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped
-  // by the contents rect.
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 4600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, 4000), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 8600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, 4500), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-  // Used the previous cull rect because the scroll amount is small.
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 8600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, 4600), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-  // Used new cull rect.
-  EXPECT_EQ(gfx::Rect(0, 600, 800, 8600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, WholeDocumentCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  GetDocument().GetSettings()->SetMainFrameClipsContent(false);
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      div { background: blue; }
-      ::-webkit-scrollbar { display: none; }
-    </style>
-    <div id='relative'
-         style='width: 200px; height: 10000px; position: relative'>
-    </div>
-    <div id='fixed' style='width: 200px; height: 200px; position: fixed'>
-    </div>
-    <div id='scroll' style='width: 200px; height: 200px; overflow: scroll'>
-      <div id='below-scroll' style='height: 5000px; position: relative'></div>
-      <div style='height: 200px'>Should not paint</div>
-    </div>
-    <div id='normal' style='width: 200px; height: 200px'></div>
-  )HTML");
-
-  // Viewport clipping is disabled.
-  EXPECT_TRUE(GetCullRect(*GetLayoutView().Layer()).IsInfinite());
-  EXPECT_TRUE(GetCullRect(*GetPaintLayerByElementId("relative")).IsInfinite());
-  EXPECT_TRUE(GetCullRect(*GetPaintLayerByElementId("fixed")).IsInfinite());
-  EXPECT_TRUE(GetCullRect(*GetPaintLayerByElementId("scroll")).IsInfinite());
-
-  // Cull rect is normal for contents below scroll other than the viewport.
-  EXPECT_EQ(gfx::Rect(0, 0, 200, 4200),
-            GetCullRect(*GetPaintLayerByElementId("below-scroll")).Rect());
-
-  EXPECT_THAT(
-      ContentDisplayItems(),
-      UnorderedElementsAre(
-          VIEW_SCROLLING_BACKGROUND_DISPLAY_ITEM,
-          IsSameId(GetDisplayItemClientFromElementId("relative")->Id(),
-                   kBackgroundType),
-          IsSameId(GetDisplayItemClientFromElementId("normal")->Id(),
-                   kBackgroundType),
-          IsSameId(GetDisplayItemClientFromElementId("scroll")->Id(),
-                   kBackgroundType),
-          IsSameId(GetLayoutBoxByElementId("scroll")
-                       ->GetScrollableArea()
-                       ->GetScrollingBackgroundDisplayItemClient()
-                       .Id(),
-                   kBackgroundType),
-          IsSameId(GetDisplayItemClientFromElementId("below-scroll")->Id(),
-                   kBackgroundType),
-          IsSameId(GetDisplayItemClientFromElementId("fixed")->Id(),
-                   kBackgroundType)));
-}
-
-TEST_P(PaintLayerPainterTest, VerticalRightLeftWritingModeDocument) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      html { writing-mode: vertical-rl; }
-      body { margin: 0; }
-    </style>
-    <div id='target' style='width: 10000px; height: 200px; position: relative'>
-    </div>
-  )HTML");
-
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(-5000, 0), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-
-  // A scroll by -5000px is equivalent to a scroll by (10000 - 5000 - 800)px =
-  // 4200px in non-RTL mode. Expanding the resulting rect by 4000px in each
-  // direction and clipping by the contents rect yields this result.
-  EXPECT_EQ(gfx::Rect(200, 0, 8800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-// TODO(wangxianzhu): These tests should correspond to the tests in
-// CompositedLayerMapping testing interest rects. However, for now because in
-// CompositeAfterPaint we expand cull rect for composited scrollers only, so
-// the tests are modified to use composited scrolling. Will change these back to
-// their original version when we support expansion for all composited layers.
-// Will be done in CullRectUpdate.
-TEST_P(PaintLayerPainterTest, ScaledCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                transform: scaleX(3) scaleY(0.5)'>
-      <div id='target' style='height: 400px; position: relative'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  // The expansion is 4000 / max(scaleX, scaleY).
-  EXPECT_EQ(gfx::Rect(0, 0, 8200, 8300),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, ScaledAndRotatedCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                transform: scaleX(3) scaleY(0.5) rotateZ(45deg)'>
-      <div id='target' style='height: 400px; position: relative;
-               will-change: transform'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  // The expansion 6599 is 4000 * max_dimension(1x1 rect projected from screen
-  // to local).
-  EXPECT_EQ(gfx::Rect(0, 0, 6799, 6899),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-// This is a testcase for https://crbug.com/1227907 where repeated cull rect
-// updates are expensive on the motionmark microbenchmark.
-TEST_P(PaintLayerPainterTest, OptimizeNonCompositedTransformUpdate) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #target {
-        width: 50px;
-        height: 50px;
-        background: green;
-        transform: translate(-8px, -8px);
-      }
-    </style>
-    <div id='target'></div>
-  )HTML");
-
-  // The cull rect should be correctly calculated on first paint.
-  EXPECT_EQ(gfx::Rect(0, 0, 800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-
-  // On subsequent paints, fall back to an infinite cull rect.
-  GetDocument().getElementById("target")->setAttribute(
-      html_names::kStyleAttr, "transform: rotate(10deg);");
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_EQ(CullRect::Infinite().Rect(),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, 3DRotated90DegreesCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                transform: rotateY(90deg)'>
-      <div id='target' style='height: 400px; position: relative'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  // It's rotated 90 degrees about the X axis, which means its visual content
-  // rect is empty, we fall back to the 4000px cull rect padding amount.
-  EXPECT_EQ(gfx::Rect(0, 0, 4200, 4300),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, 3DRotatedNear90DegreesCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                transform: rotateY(89.9999deg)'>
-      <div id='target' style='height: 400px; position: relative'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  // Because the layer is rotated to almost 90 degrees, floating-point error
-  // leads to a reverse-projected rect that is much much larger than the
-  // original layer size in certain dimensions. In such cases, we often fall
-  // back to the 4000px cull rect padding amount.
-  EXPECT_EQ(gfx::Rect(0, 0, 4200, 4300),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, PerspectiveCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id=target style='transform: perspective(1000px) rotateX(-100deg);'>
-      <div style='width: 2000px; height: 3000px></div>
-    </div>
-  )HTML");
-
-  EXPECT_TRUE(GetCullRect(*GetPaintLayerByElementId("target"))
-                  .Rect()
-                  .Contains(gfx::Rect(0, 0, 2000, 3000)));
-}
-
-TEST_P(PaintLayerPainterTest, 3D45DegRotatedTallCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target'
-         style='width: 200px; height: 10000px; transform: rotateY(45deg)'>
-    </div>
-  )HTML");
-
-  // See CompositedLayerMappingTest.3D45DegRotatedTallInterestRect (which with
-  // be combined with this one) for why the cull rect covers the whole layer.
-  EXPECT_TRUE(GetCullRect(*GetPaintLayerByElementId("target"))
-                  .Rect()
-                  .Contains(gfx::Rect(0, 0, 200, 10000)));
-}
-
-TEST_P(PaintLayerPainterTest, FixedPositionInNonScrollableViewCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target' style='width: 1000px; height: 2000px;
-                            position: fixed; top: 100px; left: 200px;'>
-    </div>
-  )HTML");
-
-  // The cull rect is inflated when scrolling, because fixed elements don't
-  // participate in overscroll.
-  EXPECT_EQ(gfx::Rect(-200, -100, 800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, FixedPositionInScrollableViewCullRect) {
-  SetBodyInnerHTML(R"HTML(
-    <div id='target' style='width: 1000px; height: 2000px;
-                            position: fixed; top: 100px; left: 200px;'>
-    </div>
-    <div style='height: 3000px'></div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(-200, -100, 800, 600),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, LayerOffscreenNearCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                position: absolute; top: 3000px; left: 0px;'>
-      <div id='target' style='height: 500px; position: relative'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(0, 0, 4200, 4300),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, LayerOffscreenFarCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 200px; height: 300px; overflow: scroll;
-                position: absolute; top: 9000px'>
-      <div id='target' style='height: 500px; position: relative'></div>
-      <div style='width: 10000px; height: 10000px'></div>
-    </div>
-  )HTML");
-
-  // The layer is too far away from the viewport.
-  EXPECT_EQ(gfx::Rect(),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, ScrollingLayerCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      div::-webkit-scrollbar { width: 5px; }
-    </style>
-    <div style='width: 200px; height: 200px; overflow: scroll'>
-      <div id='target'
-           style='width: 100px; height: 10000px; position: relative'>
-      </div>
-    </div>
-  )HTML");
-
-  // In screen space, the scroller is (8, 8, 195, 193) (because of overflow clip
-  // of 'target', scrollbar and root margin).
-  // Applying the viewport clip of the root has no effect because
-  // the clip is already small. Mapping it down into the graphics layer
-  // space yields (0, 0, 195, 193). This is then expanded by 4000px and clipped
-  // by the contents rect.
-  EXPECT_EQ(gfx::Rect(0, 0, 195, 4193),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, NonCompositedScrollingLayerCullRect) {
-  GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false);
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      div::-webkit-scrollbar { width: 5px; }
-    </style>
-    <div style='width: 200px; height: 200px; overflow: scroll'>
-      <div id='target'
-           style='width: 100px; height: 10000px; position: relative'>
-      </div>
-    </div>
-  )HTML");
-
-  // See ScrollingLayerCullRect for the calculation.
-  EXPECT_EQ(gfx::Rect(0, 0, 195, 193),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
-TEST_P(PaintLayerPainterTest, ClippedBigLayer) {
-  SetBodyInnerHTML(R"HTML(
-    <div style='width: 1px; height: 1px; overflow: hidden'>
-      <div id='target'
-           style='width: 10000px; height: 10000px; position: relative'>
-      </div>
-    </div>
-  )HTML");
-
-  EXPECT_EQ(gfx::Rect(8, 8, 1, 1),
-            GetCullRect(*GetPaintLayerByElementId("target")).Rect());
-}
-
 class PaintLayerPainterPaintedOutputInvisibleTest
     : public PaintLayerPainterTest {
  protected:
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 25e01f6..c76285f4 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -2814,10 +2814,8 @@
             box_model_object.OffsetForInFlowPosition();
         break;
       case EPosition::kAbsolute: {
-#if DCHECK_IS_ON()
         DCHECK_EQ(full_context_.container_for_absolute_position,
                   box_model_object.Container());
-#endif
         SwitchToOOFContext(context_.absolute_position);
 
         // Absolutely positioned content in an inline should be positioned
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
index 8e276bd..615f47d2 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
@@ -167,7 +167,7 @@
 }
 
 void SoftNavigationHeuristics::OnCreateTaskScope(
-    const scheduler::TaskId& task_id) {
+    const scheduler::TaskAttributionId& task_id) {
   // We're inside a click event handler, so need to add this task to the set of
   // potential soft navigation root tasks.
   potential_soft_navigation_task_ids_.insert(task_id.value());
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
index 34df3b0..331baa7 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
@@ -6,10 +6,10 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_SOFT_NAVIGATION_HEURISTICS_H_
 
 #include "base/containers/enum_set.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
@@ -41,7 +41,7 @@
   uint32_t SoftNavigationCount() { return soft_navigation_count_; }
 
   // TaskAttributionTracker::Observer's implementation.
-  void OnCreateTaskScope(const scheduler::TaskId&) override;
+  void OnCreateTaskScope(const scheduler::TaskAttributionId&) override;
 
  private:
   void CheckSoftNavigation(ScriptState*);
@@ -56,7 +56,8 @@
   bool SetFlagIfDescendantAndCheck(ScriptState*, FlagType);
   void ResetHeuristic();
 
-  WTF::HashSet<scheduler::TaskIdType> potential_soft_navigation_task_ids_;
+  WTF::HashSet<scheduler::TaskAttributionIdType>
+      potential_soft_navigation_task_ids_;
   FlagTypeSet flag_set_;
   uint32_t soft_navigation_count_ = 0;
 };
diff --git a/third_party/blink/renderer/modules/scheduler/BUILD.gn b/third_party/blink/renderer/modules/scheduler/BUILD.gn
index c7908c41..185ab16 100644
--- a/third_party/blink/renderer/modules/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/modules/scheduler/BUILD.gn
@@ -14,7 +14,7 @@
     "dom_task_controller.h",
     "dom_task_signal.cc",
     "dom_task_signal.h",
-    "script_wrappable_task_id.h",
+    "script_wrappable_task_attribution_id.h",
     "task_attribution_tracker_impl.cc",
     "task_attribution_tracker_impl.h",
     "task_priority_change_event.cc",
diff --git a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
index 5fc5703..a7da6ca 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
@@ -80,7 +80,8 @@
 
   auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
   if (tracker && script_state->World().IsMainWorld()) {
-    callback_function->SetParentTaskId(tracker->RunningTaskId(script_state));
+    callback_function->SetParentTaskId(
+        tracker->RunningTaskAttributionId(script_state));
   }
   // Always honor the priority and the task signal if given.
   DOMTaskQueue* task_queue;
@@ -111,20 +112,23 @@
   return resolver->Promise();
 }
 
-scheduler::TaskIdType DOMScheduler::taskId(ScriptState* script_state) {
+scheduler::TaskAttributionIdType DOMScheduler::taskId(
+    ScriptState* script_state) {
   ThreadScheduler* scheduler = ThreadScheduler::Current();
   DCHECK(scheduler);
   DCHECK(scheduler->GetTaskAttributionTracker());
-  absl::optional<scheduler::TaskId> task_id =
-      scheduler->GetTaskAttributionTracker()->RunningTaskId(script_state);
+  absl::optional<scheduler::TaskAttributionId> task_id =
+      scheduler->GetTaskAttributionTracker()->RunningTaskAttributionId(
+          script_state);
   // task_id cannot be unset here, as a task has presumably already ran in order
   // for this API call to be called.
   DCHECK(task_id);
   return task_id.value().value();
 }
 
-AtomicString DOMScheduler::isAncestor(ScriptState* script_state,
-                                      scheduler::TaskIdType parentId) {
+AtomicString DOMScheduler::isAncestor(
+    ScriptState* script_state,
+    scheduler::TaskAttributionIdType parentId) {
   scheduler::TaskAttributionTracker::AncestorStatus status =
       scheduler::TaskAttributionTracker::AncestorStatus::kNotAncestor;
   ThreadScheduler* scheduler = ThreadScheduler::Current();
@@ -132,7 +136,8 @@
   scheduler::TaskAttributionTracker* tracker =
       scheduler->GetTaskAttributionTracker();
   DCHECK(tracker);
-  status = tracker->IsAncestor(script_state, scheduler::TaskId(parentId));
+  status =
+      tracker->IsAncestor(script_state, scheduler::TaskAttributionId(parentId));
   switch (status) {
     case scheduler::TaskAttributionTracker::AncestorStatus::kAncestor:
       return "ancestor";
diff --git a/third_party/blink/renderer/modules/scheduler/dom_scheduler.h b/third_party/blink/renderer/modules/scheduler/dom_scheduler.h
index 49d5b4b..6c0320e 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_scheduler.h
+++ b/third_party/blink/renderer/modules/scheduler/dom_scheduler.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_DOM_SCHEDULER_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -14,7 +15,6 @@
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
@@ -75,8 +75,9 @@
                          SchedulerPostTaskOptions*,
                          ExceptionState&);
 
-  scheduler::TaskIdType taskId(ScriptState*);
-  AtomicString isAncestor(ScriptState*, scheduler::TaskIdType parent_id);
+  scheduler::TaskAttributionIdType taskId(ScriptState*);
+  AtomicString isAncestor(ScriptState*,
+                          scheduler::TaskAttributionIdType parent_id);
 
   void ContextDestroyed() override;
 
diff --git a/third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.h b/third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.h
new file mode 100644
index 0000000..ec22391c
--- /dev/null
+++ b/third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.h
@@ -0,0 +1,27 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ATTRIBUTION_ID_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ATTRIBUTION_ID_H_
+
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/bindings/v8_set_return_value.h"
+
+namespace blink {
+
+class ScriptWrappableTaskAttributionId final
+    : public ScriptWrappable,
+      public scheduler::TaskAttributionId {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit ScriptWrappableTaskAttributionId(
+      const scheduler::TaskAttributionId& id)
+      : TaskAttributionId(id) {}
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ATTRIBUTION_ID_H_
diff --git a/third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.idl b/third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.idl
similarity index 89%
rename from third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.idl
rename to third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.idl
index f3b5a4f..1f58130 100644
--- a/third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.idl
+++ b/third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.idl
@@ -5,6 +5,6 @@
 // This interface of not really web-exposed, and only used to generate a
 // ScriptWrappable object, that would pass along the TaskId to V8 and back, as
 // continuation embedder data.
-interface ScriptWrappableTaskId {
+interface ScriptWrappableTaskAttributionId {
   readonly attribute long value;
 };
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.h b/third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.h
deleted file mode 100644
index d75776c..0000000
--- a/third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.h
+++ /dev/null
@@ -1,24 +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.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ID_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ID_H_
-
-#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/bindings/v8_set_return_value.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
-
-namespace blink {
-
-class ScriptWrappableTaskId final : public ScriptWrappable,
-                                    public scheduler::TaskId {
-  DEFINE_WRAPPERTYPEINFO();
-
- public:
-  explicit ScriptWrappableTaskId(const scheduler::TaskId& id) : TaskId(id) {}
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_SCRIPT_WRAPPABLE_TASK_ID_H_
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
index 36aca86..dbac95b 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
@@ -9,8 +9,8 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_id.h"
-#include "third_party/blink/renderer/modules/scheduler/script_wrappable_task_id.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_script_wrappable_task_attribution_id.h"
+#include "third_party/blink/renderer/modules/scheduler/script_wrappable_task_attribution_id.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/bindings/to_v8.h"
@@ -21,7 +21,7 @@
 
 namespace {
 
-static unsigned Hash(TaskId id) {
+static unsigned Hash(TaskAttributionId id) {
   return id.value() % TaskAttributionTrackerImpl::kVectorSize;
 }
 
@@ -30,25 +30,29 @@
 TaskAttributionTrackerImpl::TaskAttributionTrackerImpl()
     : next_task_id_(0), v8_adapter_(std::make_unique<V8Adapter>()) {}
 
-absl::optional<TaskId> TaskAttributionTrackerImpl::RunningTaskId(
+absl::optional<TaskAttributionId>
+TaskAttributionTrackerImpl::RunningTaskAttributionId(
     ScriptState* script_state) const {
   DCHECK(v8_adapter_);
-  absl::optional<TaskId> task_id = v8_adapter_->GetValue(script_state);
+  absl::optional<TaskAttributionId> task_id =
+      v8_adapter_->GetValue(script_state);
 
   // V8 embedder state may have no value in the case of a JSPromise that wasn't
   // yet resolved.
   return task_id ? task_id : running_task_id_;
 }
 
-void TaskAttributionTrackerImpl::InsertTaskIdPair(
-    TaskId task_id,
-    absl::optional<TaskId> parent_task_id) {
+void TaskAttributionTrackerImpl::InsertTaskAttributionIdPair(
+    TaskAttributionId task_id,
+    absl::optional<TaskAttributionId> parent_task_id) {
   unsigned task_id_hash = Hash(task_id);
-  task_container_[task_id_hash] = TaskIdPair(parent_task_id, task_id);
+  task_container_[task_id_hash] =
+      TaskAttributionIdPair(parent_task_id, task_id);
 }
 
-TaskAttributionTrackerImpl::TaskIdPair&
-TaskAttributionTrackerImpl::GetTaskIdPairFromTaskContainer(TaskId id) {
+TaskAttributionTrackerImpl::TaskAttributionIdPair&
+TaskAttributionTrackerImpl::GetTaskAttributionIdPairFromTaskContainer(
+    TaskAttributionId id) {
   unsigned slot = Hash(id);
   DCHECK_LT(slot, task_container_.size());
   return (task_container_[slot]);
@@ -60,12 +64,13 @@
                                                F is_ancestor) {
   DCHECK(script_state);
   if (!script_state->World().IsMainWorld()) {
-    // As RunningTaskId will not return a TaskId for non-main-world tasks,
-    // there's no point in testing their ancestry.
+    // As RunningTaskAttributionId will not return a TaskAttributionId for
+    // non-main-world tasks, there's no point in testing their ancestry.
     return AncestorStatus::kNotAncestor;
   }
 
-  absl::optional<TaskId> current_task_id = RunningTaskId(script_state);
+  absl::optional<TaskAttributionId> current_task_id =
+      RunningTaskAttributionId(script_state);
   if (!current_task_id) {
     // TODO(yoav): This should not happen, but does. See crbug.com/1326872.
     return AncestorStatus::kNotAncestor;
@@ -80,13 +85,13 @@
   // finds a parent, which current task ID doesn't match the one its child
   // pointed at, indicating that the parent's slot in the array was overwritten.
   // In that case, it's returning the kUnknown value.
-  const TaskIdPair& current_pair =
-      GetTaskIdPairFromTaskContainer(current_task_id.value());
-  absl::optional<TaskId> parent_id = current_pair.parent;
+  const TaskAttributionIdPair& current_pair =
+      GetTaskAttributionIdPairFromTaskContainer(current_task_id.value());
+  absl::optional<TaskAttributionId> parent_id = current_pair.parent;
   DCHECK(current_pair.current);
   while (parent_id) {
-    const TaskIdPair& parent_pair =
-        GetTaskIdPairFromTaskContainer(parent_id.value());
+    const TaskAttributionIdPair& parent_pair =
+        GetTaskAttributionIdPairFromTaskContainer(parent_id.value());
     if (parent_pair.current && parent_pair.current != parent_id) {
       // Found a parent slot, but its ID doesn't match what we thought it would
       // be. That means we circled around the circular array, and we can no
@@ -105,34 +110,35 @@
 
 TaskAttributionTracker::AncestorStatus TaskAttributionTrackerImpl::IsAncestor(
     ScriptState* script_state,
-    TaskId ancestor_id) {
-  return IsAncestorInternal(script_state, [&](const TaskId& task_id) {
-    return task_id == ancestor_id;
-  });
+    TaskAttributionId ancestor_id) {
+  return IsAncestorInternal(
+      script_state,
+      [&](const TaskAttributionId& task_id) { return task_id == ancestor_id; });
 }
 
 TaskAttributionTracker::AncestorStatus
 TaskAttributionTrackerImpl::HasAncestorInSet(
     ScriptState* script_state,
-    const WTF::HashSet<scheduler::TaskIdType>& set) {
-  return IsAncestorInternal(script_state, [&](const TaskId& task_id) {
-    return set.Contains(task_id.value());
-  });
+    const WTF::HashSet<scheduler::TaskAttributionIdType>& set) {
+  return IsAncestorInternal(script_state,
+                            [&](const TaskAttributionId& task_id) {
+                              return set.Contains(task_id.value());
+                            });
 }
 
 std::unique_ptr<TaskAttributionTracker::TaskScope>
 TaskAttributionTrackerImpl::CreateTaskScope(
     ScriptState* script_state,
-    absl::optional<TaskId> parent_task_id) {
-  absl::optional<TaskId> previous_task_id = running_task_id_;
+    absl::optional<TaskAttributionId> parent_task_id) {
+  absl::optional<TaskAttributionId> previous_task_id = running_task_id_;
   DCHECK(v8_adapter_);
-  absl::optional<TaskId> previous_v8_task_id =
+  absl::optional<TaskAttributionId> previous_v8_task_id =
       v8_adapter_->GetValue(script_state);
 
-  next_task_id_ = next_task_id_.NextTaskId();
+  next_task_id_ = next_task_id_.NextId();
   running_task_id_ = next_task_id_;
 
-  InsertTaskIdPair(next_task_id_, parent_task_id);
+  InsertTaskAttributionIdPair(next_task_id_, parent_task_id);
   if (observer_) {
     observer_->OnCreateTaskScope(next_task_id_);
   }
@@ -145,14 +151,14 @@
 void TaskAttributionTrackerImpl::TaskScopeCompleted(
     const TaskScopeImpl& task_scope) {
   DCHECK(running_task_id_ == task_scope.GetTaskId());
-  running_task_id_ = task_scope.PreviousTaskId();
+  running_task_id_ = task_scope.PreviousTaskAttributionId();
   SaveTaskIdStateInV8(task_scope.GetScriptState(),
-                      task_scope.PreviousV8TaskId());
+                      task_scope.PreviousV8TaskAttributionId());
 }
 
 void TaskAttributionTrackerImpl::SaveTaskIdStateInV8(
     ScriptState* script_state,
-    absl::optional<TaskId> task_id) {
+    absl::optional<TaskAttributionId> task_id) {
   DCHECK(v8_adapter_);
   v8_adapter_->SetValue(script_state, task_id);
 }
@@ -162,9 +168,9 @@
 TaskAttributionTrackerImpl::TaskScopeImpl::TaskScopeImpl(
     ScriptState* script_state,
     TaskAttributionTrackerImpl* task_tracker,
-    TaskId scope_task_id,
-    absl::optional<TaskId> previous_task_id,
-    absl::optional<TaskId> previous_v8_task_id)
+    TaskAttributionId scope_task_id,
+    absl::optional<TaskAttributionId> previous_task_id,
+    absl::optional<TaskAttributionId> previous_v8_task_id)
     : task_tracker_(task_tracker),
       scope_task_id_(scope_task_id),
       previous_task_id_(previous_task_id),
@@ -177,8 +183,8 @@
 
 // V8Adapter's implementation
 //////////////////////////////////////
-absl::optional<TaskId> TaskAttributionTrackerImpl::V8Adapter::GetValue(
-    ScriptState* script_state) {
+absl::optional<TaskAttributionId>
+TaskAttributionTrackerImpl::V8Adapter::GetValue(ScriptState* script_state) {
   DCHECK(script_state);
   if (!script_state->ContextIsValid()) {
     return absl::nullopt;
@@ -197,18 +203,18 @@
   if (isolate->IsExecutionTerminating()) {
     return absl::nullopt;
   }
-  // If not empty, the value must be a ScriptWrappableTaskId.
+  // If not empty, the value must be a ScriptWrappableTaskAttributionId.
   NonThrowableExceptionState exception_state;
-  ScriptWrappableTaskId* script_wrappable_task_id =
-      NativeValueTraits<ScriptWrappableTaskId>::NativeValue(isolate, v8_value,
-                                                            exception_state);
+  ScriptWrappableTaskAttributionId* script_wrappable_task_id =
+      NativeValueTraits<ScriptWrappableTaskAttributionId>::NativeValue(
+          isolate, v8_value, exception_state);
   DCHECK(script_wrappable_task_id);
   return *script_wrappable_task_id;
 }
 
 void TaskAttributionTrackerImpl::V8Adapter::SetValue(
     ScriptState* script_state,
-    absl::optional<TaskId> task_id) {
+    absl::optional<TaskAttributionId> task_id) {
   DCHECK(script_state);
   if (!script_state->ContextIsValid()) {
     return;
@@ -224,11 +230,11 @@
   DCHECK(!context.IsEmpty());
 
   if (task_id) {
-    ScriptWrappableTaskId* script_wrappable_task_id =
-        MakeGarbageCollected<ScriptWrappableTaskId>(task_id.value());
+    ScriptWrappableTaskAttributionId* script_wrappable_task_id =
+        MakeGarbageCollected<ScriptWrappableTaskAttributionId>(task_id.value());
     context->SetContinuationPreservedEmbedderData(
-        ToV8Traits<ScriptWrappableTaskId>::ToV8(script_state,
-                                                script_wrappable_task_id)
+        ToV8Traits<ScriptWrappableTaskAttributionId>::ToV8(
+            script_state, script_wrappable_task_id)
             .ToLocalChecked());
   } else {
     context->SetContinuationPreservedEmbedderData(v8::Local<v8::Value>());
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
index b4091f3a..de7f597 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
@@ -5,10 +5,10 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_TASK_ATTRIBUTION_TRACKER_IMPL_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SCHEDULER_TASK_ATTRIBUTION_TRACKER_IMPL_H_
 
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -30,16 +30,17 @@
  public:
   TaskAttributionTrackerImpl();
 
-  absl::optional<TaskId> RunningTaskId(ScriptState*) const override;
+  absl::optional<TaskAttributionId> RunningTaskAttributionId(
+      ScriptState*) const override;
 
-  AncestorStatus IsAncestor(ScriptState*, TaskId parent_id) override;
+  AncestorStatus IsAncestor(ScriptState*, TaskAttributionId parent_id) override;
   AncestorStatus HasAncestorInSet(
       ScriptState*,
-      const WTF::HashSet<scheduler::TaskIdType>&) override;
+      const WTF::HashSet<scheduler::TaskAttributionIdType>&) override;
 
   std::unique_ptr<TaskScope> CreateTaskScope(
       ScriptState* script_state,
-      absl::optional<TaskId> parent_task_id) override;
+      absl::optional<TaskAttributionId> parent_task_id) override;
 
   // The vector size limits the amount of tasks we keep track of. Setting this
   // value too small can result in calls to `IsAncestor` returning an `Unknown`
@@ -47,9 +48,11 @@
   // increase this value (at the expense of memory dedicated to task tracking).
   static constexpr size_t kVectorSize = 1024;
 
-  void SetRunningTaskId(absl::optional<TaskId> id) { running_task_id_ = id; }
+  void SetRunningTaskAttributionId(absl::optional<TaskAttributionId> id) {
+    running_task_id_ = id;
+  }
 
-  void TaskScopeCompleted(ScriptState*, TaskId);
+  void TaskScopeCompleted(ScriptState*, TaskAttributionId);
 
   void RegisterObserver(TaskAttributionTracker::Observer* observer) override {
     DCHECK(!observer_ || observer == observer_);
@@ -59,15 +62,15 @@
   void UnregisterObserver() override { observer_.Clear(); }
 
  private:
-  struct TaskIdPair {
-    TaskIdPair() = default;
-    TaskIdPair(absl::optional<TaskId> parent_id,
-               absl::optional<TaskId> current_id)
+  struct TaskAttributionIdPair {
+    TaskAttributionIdPair() = default;
+    TaskAttributionIdPair(absl::optional<TaskAttributionId> parent_id,
+                          absl::optional<TaskAttributionId> current_id)
         : parent(parent_id), current(current_id) {}
 
     explicit operator bool() const { return parent.has_value(); }
-    absl::optional<TaskId> parent;
-    absl::optional<TaskId> current;
+    absl::optional<TaskAttributionId> parent;
+    absl::optional<TaskAttributionId> current;
   };
 
   template <typename F>
@@ -77,61 +80,67 @@
    public:
     TaskScopeImpl(ScriptState*,
                   TaskAttributionTrackerImpl*,
-                  TaskId scope_task_id,
-                  absl::optional<TaskId> previous_task_id,
-                  absl::optional<TaskId> previous_v8_task_id);
+                  TaskAttributionId scope_task_id,
+                  absl::optional<TaskAttributionId> previous_task_id,
+                  absl::optional<TaskAttributionId> previous_v8_task_id);
     ~TaskScopeImpl() override;
     TaskScopeImpl(const TaskScopeImpl&) = delete;
     TaskScopeImpl& operator=(const TaskScopeImpl&) = delete;
 
-    TaskId GetTaskId() const { return scope_task_id_; }
-    absl::optional<TaskId> PreviousTaskId() const { return previous_task_id_; }
-    absl::optional<TaskId> PreviousV8TaskId() const {
+    TaskAttributionId GetTaskId() const { return scope_task_id_; }
+    absl::optional<TaskAttributionId> PreviousTaskAttributionId() const {
+      return previous_task_id_;
+    }
+    absl::optional<TaskAttributionId> PreviousV8TaskAttributionId() const {
       return previous_v8_task_id_;
     }
     ScriptState* GetScriptState() const { return script_state_; }
 
    private:
     TaskAttributionTrackerImpl* task_tracker_;
-    TaskId scope_task_id_;
-    absl::optional<TaskId> previous_task_id_;
-    absl::optional<TaskId> previous_v8_task_id_;
+    TaskAttributionId scope_task_id_;
+    absl::optional<TaskAttributionId> previous_task_id_;
+    absl::optional<TaskAttributionId> previous_v8_task_id_;
     Persistent<ScriptState> script_state_;
   };
 
   class MODULES_EXPORT V8Adapter {
    public:
-    virtual absl::optional<TaskId> GetValue(ScriptState*);
-    virtual void SetValue(ScriptState*, absl::optional<TaskId>);
+    virtual absl::optional<TaskAttributionId> GetValue(ScriptState*);
+    virtual void SetValue(ScriptState*, absl::optional<TaskAttributionId>);
     virtual ~V8Adapter() = default;
   };
 
   void TaskScopeCompleted(const TaskScopeImpl&);
-  TaskIdPair& GetTaskIdPairFromTaskContainer(TaskId);
-  void InsertTaskIdPair(TaskId task_id, absl::optional<TaskId> parent_task_id);
-  void SaveTaskIdStateInV8(ScriptState*, absl::optional<TaskId>);
+  TaskAttributionIdPair& GetTaskAttributionIdPairFromTaskContainer(
+      TaskAttributionId);
+  void InsertTaskAttributionIdPair(
+      TaskAttributionId task_id,
+      absl::optional<TaskAttributionId> parent_task_id);
+  void SaveTaskIdStateInV8(ScriptState*, absl::optional<TaskAttributionId>);
 
   void SetV8AdapterForTesting(std::unique_ptr<V8Adapter> adapter) {
     v8_adapter_.swap(adapter);
   }
 
-  TaskId next_task_id_;
-  absl::optional<TaskId> running_task_id_;
+  TaskAttributionId next_task_id_;
+  absl::optional<TaskAttributionId> running_task_id_;
 
   std::unique_ptr<V8Adapter> v8_adapter_;
 
-  // The task container is a vector of optional TaskIdPairs where its indexes
-  // are TaskId hashes, and its values are the TaskId of the parent task for the
-  // TaskId that is resulted in the index. We're using this vector as a circular
-  // array, where in order to find if task A is an ancestor of task B, we look
-  // up the value at B's taskId hash position, get its parent, and repeat that
-  // process until we either find A in the ancestor chain, get no parent task
-  // (indicating that a task has no parent, so wasn't initiated by another JS
-  // task), or reach a parent that doesn't have the current ID its child though
-  // it should have, which indicates that the parent was overwritten by a newer
-  // task, indicating that we went "full circle".
-  WTF::Vector<TaskIdPair> task_container_ =
-      WTF::Vector<TaskIdPair>(kVectorSize);
+  // The task container is a vector of optional TaskAttributionIdPairs where its
+  // indexes are TaskAttributionId hashes, and its values are the TaskId of the
+  // parent task for the TaskAttributionId that is resulted in the index. We're
+  // using this vector as a circular array, where in order to find if task A is
+  // an ancestor of task B, we look up the value at B's taskAttributionId hash
+  // position, get its parent, and repeat that process until we either find A in
+  // the ancestor chain, get no parent task (indicating that a task has no
+  // parent, so wasn't initiated by another JS task), or reach a parent that
+  // doesn't have the current ID its child though it should have, which
+  // indicates that the parent was overwritten by a newer task, indicating that
+  // we went "full circle".
+  WTF::Vector<TaskAttributionIdPair> task_container_ =
+      WTF::Vector<TaskAttributionIdPair>(kVectorSize);
 
   WeakPersistent<TaskAttributionTracker::Observer> observer_;
 };
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl_test.cc b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl_test.cc
index f6d0dcf..b92164a 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl_test.cc
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl_test.cc
@@ -7,9 +7,9 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink::scheduler {
@@ -21,44 +21,45 @@
  public:
   class MockV8Adapter : public TaskAttributionTrackerImpl::V8Adapter {
    public:
-    absl::optional<TaskId> GetValue(ScriptState*) override { return value_; }
-    void SetValue(ScriptState*, absl::optional<TaskId> task_id) override {
-      value_ = task_id;
+    absl::optional<TaskAttributionId> GetValue(ScriptState*) override {
+      return value_;
+    }
+    void SetValue(ScriptState*, absl::optional<TaskAttributionId> id) override {
+      value_ = id;
     }
 
    private:
-    absl::optional<TaskId> value_;
+    absl::optional<TaskAttributionId> value_;
   };
 
   void PostTasks(TaskAttributionTrackerImpl& tracker,
                  unsigned task_number,
                  unsigned number_to_assert,
-                 TaskIdType parent_to_assert,
+                 TaskAttributionIdType parent_to_assert,
                  bool complete,
-                 TaskId* task_id = nullptr) {
-    TaskId previous_task_id = TaskId(parent_to_assert);
+                 TaskAttributionId* id = nullptr) {
+    TaskAttributionId previous_id = TaskAttributionId(parent_to_assert);
     ScriptState* script_state = ToScriptStateForMainWorld(&GetFrame());
     for (unsigned i = 0; i < task_number; ++i) {
-      task_stack_.push_back(
-          tracker.CreateTaskScope(script_state, previous_task_id));
-      absl::optional<TaskId> running_task_id =
-          tracker.RunningTaskId(script_state);
+      task_stack_.push_back(tracker.CreateTaskScope(script_state, previous_id));
+      absl::optional<TaskAttributionId> running_id =
+          tracker.RunningTaskAttributionId(script_state);
       if (i < number_to_assert) {
         // Make sure that the parent task is an ancestor.
-        TaskId parent_task(parent_to_assert);
+        TaskAttributionId parent_task(parent_to_assert);
         TaskAttributionTracker::AncestorStatus is_ancestor =
             tracker.IsAncestor(script_state, parent_task);
         ASSERT_TRUE(is_ancestor ==
                     TaskAttributionTracker::AncestorStatus::kAncestor);
         if (!complete) {
-          ASSERT_TRUE(tracker.IsAncestor(script_state, previous_task_id) ==
+          ASSERT_TRUE(tracker.IsAncestor(script_state, previous_id) ==
                       TaskAttributionTracker::AncestorStatus::kAncestor);
         }
       }
-      if (task_id) {
-        *task_id = running_task_id.value();
+      if (id) {
+        *id = running_id.value();
       }
-      previous_task_id = running_task_id.value();
+      previous_id = running_id.value();
       if (complete) {
         task_stack_.pop_back();
       }
@@ -73,30 +74,31 @@
     MockV8ForTracker(tracker);
     // Post tasks for half the queue.
     unsigned half_queue = TaskAttributionTrackerImpl::kVectorSize / 2;
-    TaskId task_id(0);
+    TaskAttributionId id(0);
     ScriptState* script_state = ToScriptStateForMainWorld(&GetFrame());
     PostTasks(tracker, half_queue, /*number_to_assert=*/0,
               /*parent_to_assert=*/0,
-              /*complete=*/true, &task_id);
+              /*complete=*/true, &id);
     // Verify that the ID of the last task that ran is what we expect it to be.
-    ASSERT_EQ(half_queue, task_id.value());
+    ASSERT_EQ(half_queue, id.value());
     // Start the parent task, but don't complete it.
     task_stack_.push_back(tracker.CreateTaskScope(
-        script_state, tracker.RunningTaskId(script_state)));
+        script_state, tracker.RunningTaskAttributionId(script_state)));
     // Get its ID.
-    TaskId parent_task_id = tracker.RunningTaskId(script_state).value();
+    TaskAttributionId parent_id =
+        tracker.RunningTaskAttributionId(script_state).value();
     // Post |overflow_length| tasks.
-    PostTasks(tracker, overflow_length, asserts_length, parent_task_id.value(),
+    PostTasks(tracker, overflow_length, asserts_length, parent_id.value(),
               nested_tasks_complete);
     if (assert_last_task && ((overflow_length + half_queue) >
                              TaskAttributionTrackerImpl::kVectorSize)) {
       // Post another task.
       task_stack_.push_back(tracker.CreateTaskScope(
-          script_state, tracker.RunningTaskId(script_state)));
+          script_state, tracker.RunningTaskAttributionId(script_state)));
 
       // Since it goes beyond the queue length and the parent task was
       // overwritten, we cannot track ancestry.
-      ASSERT_TRUE(tracker.IsAncestor(script_state, parent_task_id) !=
+      ASSERT_TRUE(tracker.IsAncestor(script_state, parent_id) !=
                   TaskAttributionTracker::AncestorStatus::kAncestor);
       if (nested_tasks_complete) {
         task_stack_.pop_back();
@@ -146,19 +148,19 @@
 TEST_F(TaskAttributionTrackerTest, NotAncestor) {
   TaskAttributionTrackerImpl tracker;
   MockV8ForTracker(tracker);
-  TaskId first_task_id(0);
+  TaskAttributionId first_id(0);
   ScriptState* script_state = ToScriptStateForMainWorld(&GetFrame());
 
   // Start a task, get its ID and complete it.
   {
     auto scope = tracker.CreateTaskScope(script_state, absl::nullopt);
-    first_task_id = tracker.RunningTaskId(script_state).value();
+    first_id = tracker.RunningTaskAttributionId(script_state).value();
   }
 
   // Start an incomplete task.
   auto scope = tracker.CreateTaskScope(script_state, absl::nullopt);
   // Make sure that the first task is not an ancestor.
-  ASSERT_TRUE(tracker.IsAncestor(script_state, first_task_id) ==
+  ASSERT_TRUE(tracker.IsAncestor(script_state, first_id) ==
               TaskAttributionTracker::AncestorStatus::kNotAncestor);
 }
 
diff --git a/third_party/blink/renderer/platform/bindings/callback_function_base.h b/third_party/blink/renderer/platform/bindings/callback_function_base.h
index fc559ebd9..a78b704 100644
--- a/third_party/blink/renderer/platform/bindings/callback_function_base.h
+++ b/third_party/blink/renderer/platform/bindings/callback_function_base.h
@@ -6,11 +6,11 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_CALLBACK_FUNCTION_BASE_H_
 
 #include "base/callback.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 
 namespace blink {
 
@@ -89,11 +89,11 @@
     callback_function_.Reset();
   }
 
-  absl::optional<scheduler::TaskId> GetParentTaskId() const {
+  absl::optional<scheduler::TaskAttributionId> GetParentTaskId() const {
     return parent_task_id_;
   }
 
-  void SetParentTaskId(absl::optional<scheduler::TaskId> task_id) {
+  void SetParentTaskId(absl::optional<scheduler::TaskAttributionId> task_id) {
     parent_task_id_ = task_id;
   }
 
@@ -117,7 +117,7 @@
   // https://webidl.spec.whatwg.org/#dfn-callback-context
   Member<ScriptState> incumbent_script_state_;
 
-  absl::optional<scheduler::TaskId> parent_task_id_;
+  absl::optional<scheduler::TaskAttributionId> parent_task_id_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/bindings/callback_interface_base.h b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
index 6d33ee6..bf091371 100644
--- a/third_party/blink/renderer/platform/bindings/callback_interface_base.h
+++ b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
@@ -5,11 +5,11 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_CALLBACK_INTERFACE_BASE_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_CALLBACK_INTERFACE_BASE_H_
 
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 
 namespace blink {
 
@@ -83,7 +83,7 @@
 
   DOMWrapperWorld& GetWorld() const { return incumbent_script_state_->World(); }
 
-  absl::optional<scheduler::TaskId> GetParentTaskId() const {
+  absl::optional<scheduler::TaskAttributionId> GetParentTaskId() const {
     return absl::nullopt;
   }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0333bbe..54908d9 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2560,7 +2560,7 @@
     },
     {
       name: "WebCodecsDequeueEvent",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "WebGLColorManagement",
diff --git a/third_party/blink/renderer/platform/scheduler/BUILD.gn b/third_party/blink/renderer/platform/scheduler/BUILD.gn
index e397674..d5c1fb0d 100644
--- a/third_party/blink/renderer/platform/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/platform/scheduler/BUILD.gn
@@ -132,7 +132,6 @@
     "public/scheduling_lifecycle_state.h",
     "public/scheduling_policy.h",
     "public/task_attribution_tracker.h",
-    "public/task_id.h",
     "public/thread.h",
     "public/thread_cpu_throttler.h",
     "public/thread_scheduler.h",
diff --git a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
index d1f461f..d19feb6 100644
--- a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
+++ b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
@@ -6,9 +6,9 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_TASK_ATTRIBUTION_TRACKER_H_
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_id.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
 namespace blink {
@@ -43,7 +43,7 @@
 
   class Observer : public GarbageCollectedMixin {
    public:
-    virtual void OnCreateTaskScope(const TaskId&) = 0;
+    virtual void OnCreateTaskScope(const TaskAttributionId&) = 0;
   };
 
   virtual ~TaskAttributionTracker() = default;
@@ -51,17 +51,19 @@
   // Create a new task scope.
   virtual std::unique_ptr<TaskScope> CreateTaskScope(
       ScriptState*,
-      absl::optional<TaskId> parent_task_id) = 0;
+      absl::optional<TaskAttributionId> parent_task_id) = 0;
 
   // Get the ID of the currently running task.
-  virtual absl::optional<TaskId> RunningTaskId(ScriptState*) const = 0;
+  virtual absl::optional<TaskAttributionId> RunningTaskAttributionId(
+      ScriptState*) const = 0;
 
   // Check for ancestry of the currently running task against an input
   // |parentId|.
-  virtual AncestorStatus IsAncestor(ScriptState*, TaskId parentId) = 0;
+  virtual AncestorStatus IsAncestor(ScriptState*,
+                                    TaskAttributionId parentId) = 0;
   virtual AncestorStatus HasAncestorInSet(
       ScriptState*,
-      const WTF::HashSet<scheduler::TaskIdType>&) = 0;
+      const WTF::HashSet<scheduler::TaskAttributionIdType>&) = 0;
 
   // Register an observer to be notified when a task is started. Only one
   // observer can be set at every point in time.
diff --git a/third_party/blink/renderer/platform/scheduler/public/task_id.h b/third_party/blink/renderer/platform/scheduler/public/task_id.h
deleted file mode 100644
index 84267f9..0000000
--- a/third_party/blink/renderer/platform/scheduler/public/task_id.h
+++ /dev/null
@@ -1,35 +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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_TASK_ID_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_TASK_ID_H_
-
-#include <cstdint>
-#include "base/types/strong_alias.h"
-
-namespace blink::scheduler {
-
-using TaskIdType = uint32_t;
-
-// TaskId represents the ID of a task, enabling comparison and incrementation
-// operations on it, while abstracting the underlying value from callers.
-class TaskId {
- public:
-  explicit TaskId(TaskIdType value) : value_(value) {}
-  TaskId(const TaskId&) = default;
-  TaskId& operator=(const TaskId&) = default;
-  scheduler::TaskIdType value() const { return value_; }
-
-  bool operator==(const TaskId& id) const { return id.value_ == value_; }
-  bool operator!=(const TaskId& id) const { return id.value_ != value_; }
-  bool operator<(const TaskId& id) const { return value_ < id.value_; }
-  TaskId NextTaskId() const { return TaskId(value_ + 1); }
-
- private:
-  TaskIdType value_;
-};
-
-}  // namespace blink::scheduler
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_TASK_ID_H_
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 402377e..bac317db 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2395,7 +2395,7 @@
 crbug.com/922618 external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html [ Timeout ]
 
 # crbug.com/1095379: These are flaky-slow, but are already present in SlowTests
-crbug.com/73609 http/tests/media/video-play-stall.html [ Pass Timeout ]
+crbug.com/73609 http/tests/media/video-play-stall.html [ Pass Timeout Failure ]
 crbug.com/518987 http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
 crbug.com/538717 [ Win ] http/tests/permissions/chromium/test-request-worker.html [ Pass Timeout ]
 crbug.com/829740 fast/history/history-back-twice-with-subframes-assert.html [ Pass Timeout ]
@@ -6145,6 +6145,8 @@
 crbug.com/1249176 [ Mac12-arm64 ] media/video-replaces-poster.html [ Skip ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/gpu-rasterization/images/color-jpeg-with-color-profile.html [ Failure ]
 crbug.com/1249176 [ Mac12-arm64 ] virtual/gpu-rasterization/images/color-jpeg-with-color-profile.html [ Failure ]
+crbug.com/1249176 [ Mac11-arm64 ] media/controls/overflow-menu-focus.html [ Failure Pass ]
+crbug.com/1249176 [ Mac12-arm64 ] media/controls/overflow-menu-focus.html [ Failure Pass ]
 
 # Failures that surfaced on the mac12-arm64 waterfall
 crbug.com/1249176 [ Mac12-arm64 ] media/color-profile-video-seek.html [ Failure ]
@@ -6169,7 +6171,7 @@
 crbug.com/1249176 [ Mac12 ] fast/dom/Element/scrollTop-scrollLeft-body.html [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] external/wpt/html/rendering/replaced-elements/embedded-content/cross-domain-iframe-in-multicol.sub.html [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] fast/js/instanceof-test.html [ Failure Pass ]
-crbug.com/1249176 [ Mac12-arm64 ] media/controls/overflow-menu-focus.html [ Failure Pass ]
+
 # Sheriff 2022-07-04
 crbug.com/1341689 [ Linux ] media/controls/overflow-menu-focus.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index 6260046..9e72a67 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -423,6 +423,9 @@
 external/wpt/webdriver/tests/find_element_from_shadow_root/find.py [ Skip ]
 external/wpt/webdriver/tests/find_elements_from_shadow_root/find.py [ Skip ]
 
+# This test failed presubmit check. Skip for now.
+external/wpt/editing/crashtests/indent-outdent-after-closing-editable-dialog-element.html [ Skip ]
+
 # Sheriff 16/09/2021, crbug.com/1250215
 external/wpt/html/dom/idlharness.worker.html [ Skip ]
 external/wpt/html/dom/idlharness.https.html [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/resources/scrolling.js b/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/resources/scrolling.js
index 1e96a7f..43c6938 100644
--- a/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/resources/scrolling.js
+++ b/third_party/blink/web_tests/external/wpt/dom/events/non-cancelable-when-passive/resources/scrolling.js
@@ -4,7 +4,7 @@
   target.addEventListener(eventName, function (event) {
     cancelable = event.cancelable;
     arrived = true;
-  }, {passive});
+  }, {passive:passive, once:true});
 
   promise_test(async (t) => {
     t.add_cleanup(() => {
diff --git a/third_party/blink/web_tests/external/wpt/reporting/document-reporting-default-endpoint.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/document-reporting-default-endpoint.https.sub.html
index 24c1216a..f1951e3 100644
--- a/third_party/blink/web_tests/external/wpt/reporting/document-reporting-default-endpoint.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/reporting/document-reporting-default-endpoint.https.sub.html
@@ -19,7 +19,7 @@
     observer.observe();
   }, "report generated");
 </script>
-<script>window.webkitStorageInfo;</script>
+<script>webkitRequestAnimationFrame(() => {});</script>
 <script>
   const base_url = `${location.protocol}//${location.host}`;
   const endpoint = `${base_url}/reporting/resources/report.py`;
diff --git a/third_party/blink/web_tests/external/wpt/reporting/document-reporting-path-absolute.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/document-reporting-path-absolute.https.sub.html
index e23dd85..48be010a 100644
--- a/third_party/blink/web_tests/external/wpt/reporting/document-reporting-path-absolute.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/reporting/document-reporting-path-absolute.https.sub.html
@@ -21,7 +21,7 @@
     observer.observe();
   }, "report generated");
 </script>
-<script>window.webkitStorageInfo;</script>
+<script>window.webkitCancelAnimationFrame(() => {});</script>
 <script>
   const base_url = `${location.protocol}//${location.host}`;
   const endpoint = `${base_url}/reporting/resources/report.py`;
diff --git a/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report-once.py b/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report-once.py
index 076d500..163846a 100644
--- a/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report-once.py
+++ b/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report-once.py
@@ -29,6 +29,6 @@
 <meta charset=utf-8>
 <title>Generate deprecation report</title>
 <script>
-  window.webkitStorageInfo;
+  webkitRequestAnimationFrame(() => {});
 </script>
 """
diff --git a/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report.https.sub.html
index ee40c46e..f1f4e963 100644
--- a/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/reporting/resources/generate-report.https.sub.html
@@ -2,5 +2,5 @@
 <meta charset=utf-8>
 <title>Generate deprecation report</title>
 <script>
-  window.webkitStorageInfo;
+  webkitRequestAnimationFrame(() => {});
 </script>
diff --git a/third_party/blink/web_tests/fast/events/touch/touch-slider-othogonal-movement.html b/third_party/blink/web_tests/fast/events/touch/touch-slider-othogonal-movement.html
deleted file mode 100644
index d95c094..0000000
--- a/third_party/blink/web_tests/fast/events/touch/touch-slider-othogonal-movement.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<title>Orthogonal touch movement should not change slider value</title>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<input id="slider" type="range" value="0" style="width:200px; height: 50px">
-<script>
-const w = slider.offsetWidth;
-const h = slider.offsetHeight;
-
-test(() => {
-  assert_own_property(window, 'eventSender', 'This test needs eventSender to emulate touch');
-
-  // Emulate a touch that starts inside slider and moves vertically.
-  const x = slider.offsetLeft + w / 2;
-  const startY = slider.offsetTop + h / 2;
-  const endY = startY + h;
-
-  eventSender.clearTouchPoints();
-  eventSender.addTouchPoint(x, startY);
-  eventSender.touchStart();
-
-  eventSender.updateTouchPoint(0, x, endY);
-  eventSender.touchMove();
-
-  eventSender.releaseTouchPoint(0);
-  eventSender.touchEnd();
-
-  assert_equals(slider.value, "0");
-}, 'Orthogonal touch movement should not change slider value');
-
-test(() => {
-  assert_own_property(window, 'eventSender', 'This test needs eventSender to emulate touch');
-
-  // Emulate a tap on the slider.
-  const x = slider.offsetLeft + w / 2;
-  const y = slider.offsetTop + h / 2;
-
-  eventSender.clearTouchPoints();
-  eventSender.addTouchPoint(x, y);
-  eventSender.touchStart();
-  eventSender.releaseTouchPoint(0);
-  eventSender.touchEnd();
-
-  assert_not_equals(slider.value, "0");
-}, 'Tapping should still change slider value');
-</script>
diff --git a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 14a0c62..22458ad 100644
--- a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -36,6 +36,7 @@
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter decodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -43,10 +44,12 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface AudioEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -54,6 +57,7 @@
 [Worker]     method encode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface BackgroundFetchManager
 [Worker]     attribute @@toStringTag
 [Worker]     method constructor
@@ -1687,6 +1691,7 @@
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter decodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -1694,10 +1699,12 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface VideoEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -1705,6 +1712,7 @@
 [Worker]     method encode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface VideoFrame
 [Worker]     attribute @@toStringTag
 [Worker]     getter codedHeight
diff --git a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
index 54fcae3d..ec65ec90 100644
--- a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -173,6 +173,7 @@
     static method isConfigSupported
     attribute @@toStringTag
     getter decodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -180,6 +181,7 @@
     method decode
     method flush
     method reset
+    setter ondequeue
 interface AudioDestinationNode : AudioNode
     attribute @@toStringTag
     getter maxChannelCount
@@ -188,6 +190,7 @@
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -195,6 +198,7 @@
     method encode
     method flush
     method reset
+    setter ondequeue
 interface AudioListener
     attribute @@toStringTag
     getter forwardX
@@ -8231,6 +8235,7 @@
     static method isConfigSupported
     attribute @@toStringTag
     getter decodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -8238,10 +8243,12 @@
     method decode
     method flush
     method reset
+    setter ondequeue
 interface VideoEncoder : EventTarget
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -8249,6 +8256,7 @@
     method encode
     method flush
     method reset
+    setter ondequeue
 interface VideoFrame
     attribute @@toStringTag
     getter codedHeight
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body-expected.txt
deleted file mode 100644
index 867b5e2..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-body-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive mousewheel event listener on body assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div-expected.txt
deleted file mode 100644
index 07cf21e2..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-div-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive mousewheel event listener on div assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document-expected.txt
deleted file mode 100644
index de8a413..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-document-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive mousewheel event listener on document assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root-expected.txt
deleted file mode 100644
index 39aafa1..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-root-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive mousewheel event listener on root assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window-expected.txt
deleted file mode 100644
index 0c62421..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-mousewheel-event-listener-on-window-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive mousewheel event listener on window assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body-expected.txt
deleted file mode 100644
index 25539dd..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-body-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive wheel event listener on body assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div-expected.txt
deleted file mode 100644
index aa0e869..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-div-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive wheel event listener on div assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document-expected.txt
deleted file mode 100644
index 33c1b82..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-document-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive wheel event listener on document assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root-expected.txt
deleted file mode 100644
index 4dbaa29..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-root-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive wheel event listener on root assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window-expected.txt
deleted file mode 100644
index a90d20c..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/dom/events/non-cancelable-when-passive/non-passive-wheel-event-listener-on-window-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL non-passive wheel event listener on window assert_equals: expected true but got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/buffer-overflow.html b/third_party/blink/web_tests/wpt_internal/reporting/buffer-overflow.html
index cd43a3d..4bce75d 100644
--- a/third_party/blink/web_tests/wpt_internal/reporting/buffer-overflow.html
+++ b/third_party/blink/web_tests/wpt_internal/reporting/buffer-overflow.html
@@ -23,7 +23,7 @@
   for (var i = 0; i != 110; ++i) {
     causeIntervention();
   }
-  window.webkitStorageInfo;
+  webkitRequestAnimationFrame(() => {});
 
   observer3.observe();
   observer3.disconnect();
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/buffering.html b/third_party/blink/web_tests/wpt_internal/reporting/buffering.html
index bbd916a..b04df48 100644
--- a/third_party/blink/web_tests/wpt_internal/reporting/buffering.html
+++ b/third_party/blink/web_tests/wpt_internal/reporting/buffering.html
@@ -23,7 +23,7 @@
   // and one after calling observe().
   causeIntervention();
   observer1.observe();
-  window.webkitStorageInfo;
+  webkitCancelAnimationFrame(() => {});
   observer1.disconnect();
 }, "Buffered reports observed");
 
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/reporting-api.html b/third_party/blink/web_tests/wpt_internal/reporting/reporting-api.html
index 5750332..8edc077 100644
--- a/third_party/blink/web_tests/wpt_internal/reporting/reporting-api.html
+++ b/third_party/blink/web_tests/wpt_internal/reporting/reporting-api.html
@@ -55,7 +55,7 @@
   let promise = proxy.promise;
 
   // Use a deprecated feature.
-  window.webkitStorageInfo;
+  webkitRequestAnimationFrame(() => {});
 
   // Ensure the deprecation report is generated and routed to the reporting mojo
   // interface.
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/resources/deprecation.js b/third_party/blink/web_tests/wpt_internal/reporting/resources/deprecation.js
index 84bf037..73f6eed 100644
--- a/third_party/blink/web_tests/wpt_internal/reporting/resources/deprecation.js
+++ b/third_party/blink/web_tests/wpt_internal/reporting/resources/deprecation.js
@@ -53,6 +53,6 @@
   window.gc();
 
   // Use two deprecated features to generate two deprecation reports.
-  window.webkitStorageInfo;
+  window.webkitCancelAnimationFrame(() => {});
   window.webkitRequestAnimationFrame(() => {});
 }, "Deprecation reports");
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/type-filtering.html b/third_party/blink/web_tests/wpt_internal/reporting/type-filtering.html
index bf8a487..25dbe17 100644
--- a/third_party/blink/web_tests/wpt_internal/reporting/type-filtering.html
+++ b/third_party/blink/web_tests/wpt_internal/reporting/type-filtering.html
@@ -44,7 +44,7 @@
   observer2.observe();
 
   // Generate a deprecation report and an intervention report.
-  window.webkitStorageInfo;
+  webkitCancelAnimationFrame(() => {});
   causeIntervention();
 
   observer2.disconnect();
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index b64a418..21288f5 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: c495cf830fe2fc59de34beacfe0356f4405f9c0d
+Version: 0a8f1f1c39af06dff550d8ca96c6e087994155b7
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/puffin/BUILD.gn b/third_party/puffin/BUILD.gn
index a47a988..c60092a 100644
--- a/third_party/puffin/BUILD.gn
+++ b/third_party/puffin/BUILD.gn
@@ -39,11 +39,13 @@
     "//base",
     "//components/zucchini:zucchini_lib",
     "//third_party/brotli:dec",
+    "//third_party/brotli:enc",
   ]
   sources = [
     "src/bit_reader.cc",
     "src/bit_writer.cc",
     "src/brotli_util.cc",
+    "src/file_stream.cc",
     "src/huffer.cc",
     "src/huffman_table.cc",
     "src/memory_stream.cc",
@@ -61,6 +63,7 @@
     ":libpuffin-proto",
     "//base",
     "//components/zucchini:zucchini_lib",
+    "//third_party/brotli:dec",
     "//third_party/brotli:enc",
   ]
   sources = [
@@ -113,8 +116,7 @@
     "src/unittest_common.cc",
     "src/utils_unittest.cc",
   ]
-  data_deps =
-      [ "//chrome/test/data/updater/puffin_patch_test:puffin_patch_test_files" ]
+  data_deps = [ "//components/test/data/update_client/puffin_patch_test:puffin_patch_test_files" ]
   deps = [
     ":libpuffdiff",
     ":libpuffpatch",
diff --git a/third_party/puffin/README.chromium b/third_party/puffin/README.chromium
index e14113f..dd75cc5 100644
--- a/third_party/puffin/README.chromium
+++ b/third_party/puffin/README.chromium
@@ -33,4 +33,12 @@
 - Added puffin::Status enum to help disambiguate errors from puffin::PuffPatch
 - Added puffin::ApplyPuffPatch API which allows chromium libraries to call
     puffin::PuffPatch without having worry about UniqueStreamPtr's and Buffer's.
-- Added ApplyPuffPatchTest to patching_unittest to test the new method.
\ No newline at end of file
+- Added ApplyPuffPatchTest to patching_unittest to test the new method.
+- Updated puffin/BUILD.gn to grab the new puffin_patch_test_files target under
+components
+- Updated puffin::ApplyPuffPatch test to use the new patch files under
+components.
+- Updated puffin::ApplyPuffPatch test to make sure the output files are deleted
+so the tests will pass in back-to-back runs.
+- Updated puffin::ApplyPuffPatch test to use base::ContentsEqual rather than
+manually reading each file and comparing the results.
\ No newline at end of file
diff --git a/third_party/puffin/scripts/test_corpus.py b/third_party/puffin/scripts/test_corpus.py
index e62b6807..eebe7cc 100755
--- a/third_party/puffin/scripts/test_corpus.py
+++ b/third_party/puffin/scripts/test_corpus.py
@@ -5,7 +5,7 @@
 # found in the LICENSE file.
 #
 
-"""A tool for running puffin tests in a corpus of deflate compressed files."""
+"""A tool for running Puffin tests in a corpus of deflate compressed files."""
 
 import argparse
 import filecmp
diff --git a/third_party/puffin/src/file_stream.cc b/third_party/puffin/src/file_stream.cc
index b7a2ab5..aa6a2a67 100644
--- a/third_party/puffin/src/file_stream.cc
+++ b/third_party/puffin/src/file_stream.cc
@@ -38,6 +38,10 @@
   return UniqueStreamPtr(new FileStream(file_path, flags));
 }
 
+UniqueStreamPtr FileStream::CreateStreamFromFile(base::File file) {
+  return UniqueStreamPtr(new FileStream(std::move(file)));
+}
+
 bool FileStream::GetSize(uint64_t* size) {
   TEST_AND_RETURN_FALSE(file_.IsValid());
   int64_t result = file_.GetLength();
diff --git a/third_party/puffin/src/include/puffin/common.h b/third_party/puffin/src/include/puffin/common.h
index f76c538..ff9b00f 100644
--- a/third_party/puffin/src/include/puffin/common.h
+++ b/third_party/puffin/src/include/puffin/common.h
@@ -26,15 +26,15 @@
 namespace puffin {
 
 enum class CompressorType : uint8_t {
-  kNoCompression = 0,  // Unsupported by chromium/src's puffin implementation.
-  kBZ2 = 1,            // Unsupported by chromium/src's puffin implementation.
+  kNoCompression = 0,  // Unsupported by chromium/src's Puffin implementation.
+  kBZ2 = 1,            // Unsupported by chromium/src's Puffin implementation.
   kBrotli = 2,
 };
 
 using Buffer = std::vector<uint8_t>;
 
 // This class is similar to the protobuf generated for |ProtoByteExtent|. We
-// defined an extra class so the users of puffin do not have to include
+// defined an extra class so the users of Puffin do not have to include
 // puffin.pb.h and deal with its use.
 struct ByteExtent {
   constexpr ByteExtent(uint64_t offset, uint64_t length)
diff --git a/third_party/puffin/src/include/puffin/file_stream.h b/third_party/puffin/src/include/puffin/file_stream.h
index 466983b..9ebb3cf9 100644
--- a/third_party/puffin/src/include/puffin/file_stream.h
+++ b/third_party/puffin/src/include/puffin/file_stream.h
@@ -22,9 +22,12 @@
     file_.Initialize(path, flags);
     Seek(0);
   }
+
+  FileStream(base::File file) : file_(std::move(file)) {}
   ~FileStream() override = default;
 
   static UniqueStreamPtr Open(const std::string& path, bool read, bool write);
+  static UniqueStreamPtr CreateStreamFromFile(base::File file);
 
   bool GetSize(uint64_t* size) override;
   bool GetOffset(uint64_t* offset) override;
diff --git a/third_party/puffin/src/include/puffin/puffpatch.h b/third_party/puffin/src/include/puffin/puffpatch.h
index b7a6ac71..272a6ff 100644
--- a/third_party/puffin/src/include/puffin/puffpatch.h
+++ b/third_party/puffin/src/include/puffin/puffpatch.h
@@ -5,6 +5,7 @@
 #ifndef SRC_INCLUDE_PUFFIN_PUFFPATCH_H_
 #define SRC_INCLUDE_PUFFIN_PUFFPATCH_H_
 
+#include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "puffin/common.h"
 #include "puffin/stream.h"
@@ -51,7 +52,7 @@
   P_INPUT_NOT_RECOGNIZED = 24,    // Unrecognized input (not a crx)
 };
 
-// Applies the puffin patch to deflate stream |src| to create deflate stream
+// Applies the Puffin patch to deflate stream |src| to create deflate stream
 // |dst|. This function is used in the client and internally uses bspatch to
 // apply the patch. The input streams are of type |shared_ptr| because
 // |PuffPatch| needs to wrap these streams into another ones and we don't want
@@ -73,6 +74,10 @@
                       const base::FilePath& patch_path,
                       const base::FilePath& output_path);
 
+Status ApplyPuffPatch(base::File input_file,
+                      base::File patch_file,
+                      base::File output_file);
+
 }  // namespace puffin
 
 #endif  // SRC_INCLUDE_PUFFIN_PUFFPATCH_H_
diff --git a/third_party/puffin/src/include/puffin/stream.h b/third_party/puffin/src/include/puffin/stream.h
index c305c4d..f62a346 100644
--- a/third_party/puffin/src/include/puffin/stream.h
+++ b/third_party/puffin/src/include/puffin/stream.h
@@ -11,7 +11,7 @@
 
 namespace puffin {
 
-// The base stream interface used by puffin for all operations. This interface
+// The base stream interface used by Puffin for all operations. This interface
 // is designed to be as simple as possible.
 class StreamInterface {
  public:
diff --git a/third_party/puffin/src/patching_unittest.cc b/third_party/puffin/src/patching_unittest.cc
index d8b9ddb..ea90568 100644
--- a/third_party/puffin/src/patching_unittest.cc
+++ b/third_party/puffin/src/patching_unittest.cc
@@ -149,26 +149,26 @@
 }
 
 TEST(PatchingTest, ApplyPuffPatchTest) {
-  ASSERT_EQ(ApplyPuffPatch(out_test_file("puffin_app_v1.crx3"),
-                           out_test_file("puffin_app_v1_to_v2.puff"),
-                           out_test_file("puffin_app_v1_to_v2.crx3")),
-            Status::P_OK);
-  std::string expected, actual;
-  ASSERT_TRUE(
-      base::ReadFileToString(out_test_file("puffin_app_v2.crx3"), &expected));
-  ASSERT_TRUE(base::ReadFileToString(out_test_file("puffin_app_v1_to_v2.crx3"),
-                                     &actual));
-  ASSERT_EQ(expected.compare(actual), 0);
+  base::FilePath app_v1_crx = out_test_file("puffin_app_v1.crx3");
+  base::FilePath app_v2_crx = out_test_file("puffin_app_v2.crx3");
+  base::FilePath patch_v1_to_v2_puff =
+      out_test_file("puffin_app_v1_to_v2.puff");
+  base::FilePath patch_v2_to_v1_puff =
+      out_test_file("puffin_app_v2_to_v1.puff");
+  base::FilePath app_v1_to_v2_crx = out_test_file("puffin_app_v1_to_v2.crx3");
+  base::FilePath app_v2_to_v1_crx = out_test_file("puffin_app_v2_to_v1.crx3");
 
-  ASSERT_EQ(ApplyPuffPatch(out_test_file("puffin_app_v2.crx3"),
-                           out_test_file("puffin_app_v2_to_v1.puff"),
-                           out_test_file("puffin_app_v2_to_v1.crx3")),
+  // Test patching v1 to v2:
+  ASSERT_TRUE(base::DeleteFile(app_v1_to_v2_crx));
+  ASSERT_EQ(ApplyPuffPatch(app_v1_crx, patch_v1_to_v2_puff, app_v1_to_v2_crx),
             Status::P_OK);
-  ASSERT_TRUE(
-      base::ReadFileToString(out_test_file("puffin_app_v1.crx3"), &expected));
-  ASSERT_TRUE(base::ReadFileToString(out_test_file("puffin_app_v2_to_v1.crx3"),
-                                     &actual));
-  ASSERT_EQ(expected.compare(actual), 0);
+  EXPECT_TRUE(base::ContentsEqual(app_v2_crx, app_v1_to_v2_crx));
+
+  // Test patching v2 to v1:
+  ASSERT_TRUE(base::DeleteFile(app_v2_to_v1_crx));
+  ASSERT_EQ(ApplyPuffPatch(app_v2_crx, patch_v2_to_v1_puff, app_v2_to_v1_crx),
+            Status::P_OK);
+  EXPECT_TRUE(base::ContentsEqual(app_v1_crx, app_v2_to_v1_crx));
 }
 
 // TODO(ahassani): add tests for:
diff --git a/third_party/puffin/src/puffdiff.cc b/third_party/puffin/src/puffdiff.cc
index 381b26e..1c1fc0f7 100644
--- a/third_party/puffin/src/puffdiff.cc
+++ b/third_party/puffin/src/puffdiff.cc
@@ -44,7 +44,7 @@
   }
 }
 
-// Structure of a puffin patch
+// Structure of a Puffin patch
 // +-------+------------------+-------------+--------------+
 // |P|U|F|1| PatchHeader Size | PatchHeader | raw patch |
 // +-------+------------------+-------------+--------------+
diff --git a/third_party/puffin/src/puffer.cc b/third_party/puffin/src/puffer.cc
index 6da93ac..5796147 100644
--- a/third_party/puffin/src/puffer.cc
+++ b/third_party/puffin/src/puffer.cc
@@ -182,14 +182,14 @@
         auto bits_to_cache = cur_ht->DistanceMaxBits();
         if (!br->CacheBits(bits_to_cache)) {
           // This is a corner case that is present in the older versions of the
-          // puffin. So we need to catch it and correctly discard this kind of
+          // Puffin. So we need to catch it and correctly discard this kind of
           // deflate when we encounter it. See crbug.com/915559 for more info.
           bits_to_cache = br->BitsRemaining();
           TEST_AND_RETURN_FALSE(br->CacheBits(bits_to_cache));
           if (exclude_bad_distance_caches_) {
             include_deflate = false;
           }
-          LOG(WARNING) << "A rare condition that older puffin clients fail to"
+          LOG(WARNING) << "A rare condition that older Puffin clients fail to"
                        << " recognize happened. Nothing to worry about."
                        << " See crbug.com/915559";
         }
diff --git a/third_party/puffin/src/puffpatch.cc b/third_party/puffin/src/puffpatch.cc
index 93e52a19..00d9938 100644
--- a/third_party/puffin/src/puffpatch.cc
+++ b/third_party/puffin/src/puffpatch.cc
@@ -166,7 +166,7 @@
                  const uint8_t* patch,
                  size_t patch_length,
                  size_t max_cache_size) {
-  size_t patch_offset;  // raw patch offset in puffin |patch|.
+  size_t patch_offset;  // raw patch offset in Puffin |patch|.
   size_t raw_patch_size = 0;
   vector<BitExtent> src_deflates, dst_deflates;
   vector<ByteExtent> src_puffs, dst_puffs;
@@ -237,4 +237,35 @@
                            puffdiff_delta.size(), kDefaultPuffCacheSize);
 }
 
+Status ApplyPuffPatch(base::File input_file,
+                      base::File patch_file,
+                      base::File output_file) {
+  puffin::UniqueStreamPtr input_stream =
+      puffin::FileStream::CreateStreamFromFile(std::move(input_file));
+  if (!input_stream) {
+    return Status::P_READ_OPEN_ERROR;
+  }
+  puffin::UniqueStreamPtr output_stream =
+      puffin::FileStream::CreateStreamFromFile(std::move(output_file));
+  if (!output_stream) {
+    return Status::P_WRITE_OPEN_ERROR;
+  }
+  puffin::UniqueStreamPtr patch_stream =
+      puffin::FileStream::CreateStreamFromFile(std::move(patch_file));
+  if (!patch_stream) {
+    return Status::P_READ_OPEN_ERROR;
+  }
+  uint64_t patch_size = 0;
+  if (!patch_stream->GetSize(&patch_size)) {
+    return Status::P_STREAM_ERROR;
+  }
+  puffin::Buffer puffdiff_delta(patch_size);
+  if (!patch_stream->Read(puffdiff_delta.data(), puffdiff_delta.size())) {
+    return Status::P_READ_ERROR;
+  }
+  return puffin::PuffPatch(std::move(input_stream), std::move(output_stream),
+                           std::move(puffdiff_delta.data()),
+                           puffdiff_delta.size(), kDefaultPuffCacheSize);
+}
+
 }  // namespace puffin
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl
index ede3585..176ed2b 100644
--- a/tools/gritsettings/translation_expectations.pyl
+++ b/tools/gritsettings/translation_expectations.pyl
@@ -34,9 +34,10 @@
       "chrome/app/chromium_strings.grd",
       "chrome/app/generated_resources.grd",
       "chrome/app/google_chrome_strings.grd",
+      "chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd",
       "chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings.grd",
       "chrome/browser/touch_to_fill/android/internal/java/strings/android_touch_to_fill_strings.grd",
-      "chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd",
+      "chrome/browser/ui/android/fast_checkout/internal/java/strings/android_fast_checkout_strings.grd",
       "chrome/browser/ui/android/strings/android_chrome_strings.grd",
       "chrome/credential_provider/gaiacp/gaia_resources.grd",
       "chromeos/chromeos_strings.grd",
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 32c237b..d42d005 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -1976,11 +1976,11 @@
     # Cast Linux takes very long in linking, possibly due to being on GCE
     # (crbug/794423).
     'cast_release_bot_reclient': [
-      'cast', 'release_bot_reclient', 'minimal_symbols',
+      'cast_receiver', 'cast_os', 'release_bot_reclient', 'minimal_symbols',
     ],
 
     'cast_release_trybot': [
-      'cast', 'release_trybot',
+      'cast_receiver', 'cast_os', 'release_trybot',
     ],
 
     'cfi_full_cfi_icall_cfi_diag_recover_release_static_reclient': [
@@ -3791,6 +3791,10 @@
       'gn_args': 'is_cast_audio_only=true'
     },
 
+    'cast_os': {
+      'gn_args': 'is_castos=true'
+    },
+
     'cast_receiver': {
       'gn_args': 'enable_cast_receiver=true'
     },
diff --git a/tools/mb/mb_config_expectations/chromium.linux.json b/tools/mb/mb_config_expectations/chromium.linux.json
index 9ea6c28..ff6c6ddb 100644
--- a/tools/mb/mb_config_expectations/chromium.linux.json
+++ b/tools/mb/mb_config_expectations/chromium.linux.json
@@ -13,7 +13,8 @@
   "Cast Linux": {
     "gn_args": {
       "dcheck_always_on": false,
-      "is_chromecast": true,
+      "enable_cast_receiver": true,
+      "is_castos": true,
       "is_component_build": false,
       "is_debug": false,
       "symbol_level": 1,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index 4dce4418..714e5f15 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -23,7 +23,8 @@
   "cast_shell_linux": {
     "gn_args": {
       "dcheck_always_on": true,
-      "is_chromecast": true,
+      "enable_cast_receiver": true,
+      "is_castos": true,
       "is_component_build": false,
       "is_debug": false,
       "symbol_level": 0,
diff --git a/tools/metrics/actions/PRESUBMIT.py b/tools/metrics/actions/PRESUBMIT.py
index eb47b1b..ee1390c 100644
--- a/tools/metrics/actions/PRESUBMIT.py
+++ b/tools/metrics/actions/PRESUBMIT.py
@@ -24,7 +24,7 @@
       if exit_code != 0:
         return [output_api.PresubmitError(
             'actions.xml is not up to date or is not formatted correctly; '
-            'run extract_actions.py to fix')]
+            'run tools/metrics/actions/extract_actions.py to fix')]
   return []
 
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e148264..3fe69a6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -32139,6 +32139,7 @@
   <int value="998" label="EnterpriseProfileCreationKeepBrowsingData"/>
   <int value="999" label="KerberosDomainAutocomplete"/>
   <int value="1000" label="KerberosDefaultConfiguration"/>
+  <int value="1001" label="UnmanagedDeviceSignalsConsentFlowEnabled"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -41174,6 +41175,7 @@
   <int value="27" label="ManifestListTooBig"/>
   <int value="28" label="DisabledEmbargo"/>
   <int value="29" label="UserInterfaceTimedOut"/>
+  <int value="30" label="RpPageNotVisible"/>
 </enum>
 
 <enum name="FedCmRevokeStatus">
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 10055dfc..a767e0e 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -47,6 +47,7 @@
 iclelland@chromium.org
 ioanap@chromium.org
 jihanli@chromium.org
+jimmyxgong@chromium.org
 joenotcharles@google.com
 johnidel@chromium.org
 jonross@chromium.org
diff --git a/tools/metrics/histograms/metadata/ash/OWNERS b/tools/metrics/histograms/metadata/ash/OWNERS
index 68b7725..07c6957d 100644
--- a/tools/metrics/histograms/metadata/ash/OWNERS
+++ b/tools/metrics/histograms/metadata/ash/OWNERS
@@ -3,4 +3,5 @@
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
 tby@chromium.org
-zentaro@chromium.org
+jimmyxgong@chromium.org
+zentaro@chromium.org
\ No newline at end of file
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 41a6f0f..1a96e1de 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -3197,6 +3197,22 @@
   </token>
 </histogram>
 
+<histogram name="Ash.NotifierFramework.Toast.Dismissed.{TimeRange}"
+    enum="ToastCatalogName" expires_after="2023-02-10">
+  <owner>kradtke@google.com</owner>
+  <owner>cros-status-area-eng@google.com</owner>
+  <summary>
+    Tracks the time a specific toast spends on screen before being dismissed by
+    the user or system. Records the toast catalog name in one of the time
+    buckets available when toast is dismissed either manually or by time out.
+  </summary>
+  <token key="TimeRange">
+    <variant name="After7s"/>
+    <variant name="Within2s"/>
+    <variant name="Within7s"/>
+  </token>
+</histogram>
+
 <histogram name="Ash.NotifierFramework.Toast.ShownCount"
     enum="ToastCatalogName" expires_after="2023-01-15">
   <owner>kradtke@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 2e9b945..b71724be 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -2451,40 +2451,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.Tokenizer.MainDocument.ATypicalStates" units="bitmask"
-    expires_after="2022-09-01">
-  <owner>sky@chromium.org</owner>
-  <owner>swarm-team@google.com</owner>
-  <summary>
-    This metric is reported for the main document when tokenizing of the input
-    completes. The histogram is a bitmask of values capturing how often
-    non-typical states are encountered. A value of 0 means none were
-    encountered. Bitmask values are:
-
-    Bit 1: Set if document.write() is encountered.
-
-    Bit 2: Set if the tokenizer state as determined by HTMLTreeBuilder does not
-    match the speculative state from HTMLTokenizer.
-
-    Bit 3: Set if the input contains a null character.
-
-    Bit 4: Set if input contains CDATA.
-  </summary>
-</histogram>
-
-<histogram name="Blink.Tokenizer.MainDocument.LocationOfFirstATypicalState"
-    units="location" expires_after="2022-09-01">
-  <owner>sky@chromium.org</owner>
-  <owner>swarm-team@google.com</owner>
-  <summary>
-    This metric is reported for the main document when tokenizing of the input
-    completes. This is emitted if a non-typical tokenization state is
-    encountered (see Blink.Tokenizer.MainDocument.ATypicalStates for more
-    details). The value is the location in the input stream of the first
-    non-typical state.
-  </summary>
-</histogram>
-
 <histogram name="Blink.UpdateViewportIntersection.UpdateTime"
     units="microseconds" expires_after="2022-12-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
diff --git a/tools/metrics/histograms/metadata/chromeos_hps/OWNERS b/tools/metrics/histograms/metadata/chromeos_hps/OWNERS
index 293c497..241c4eb 100644
--- a/tools/metrics/histograms/metadata/chromeos_hps/OWNERS
+++ b/tools/metrics/histograms/metadata/chromeos_hps/OWNERS
@@ -10,4 +10,5 @@
 khorimoto@chromium.org
 tby@chromium.org
 tsergeant@chromium.org
+jimmyxgong@chromium.org
 zentaro@chromium.org
diff --git a/tools/metrics/histograms/metadata/diagnostics/OWNERS b/tools/metrics/histograms/metadata/diagnostics/OWNERS
new file mode 100644
index 0000000..96d6a8a
--- /dev/null
+++ b/tools/metrics/histograms/metadata/diagnostics/OWNERS
@@ -0,0 +1,8 @@
+per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+# Primary reviewers
+jimmyxgong@chromium.org
+# Secondary reviewers
+zentaro@chromium.org
\ No newline at end of file
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 9519344..8e09cc1f 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -401,7 +401,7 @@
 </histogram>
 
 <histogram name="InputMethod.CharactersPerSession{InputModality}"
-    units="characters" expires_after="2022-06-30">
+    units="characters" expires_after="2022-12-04">
   <owner>keithlee@chromium.org</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index a0d9c44..b6c1027ba 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -2014,6 +2014,29 @@
   </summary>
 </histogram>
 
+<histogram name="Media.D3D11.UnusedPictureBufferCount.MultiTexture"
+    units="instances" expires_after="2022-11-01">
+  <owner>liberato@chromium.org</owner>
+  <owner>tmathmeyer@chromium.org</owner>
+  <summary>
+    This histogram lets us count how many PictureBuffers we've overallocated
+    when we're allocating each texture separately. This case is relatively
+    simple to optimize compared to SingleTexture.
+  </summary>
+</histogram>
+
+<histogram name="Media.D3D11.UnusedPictureBufferCount.SingleTexture"
+    units="instances" expires_after="2022-11-01">
+  <owner>liberato@chromium.org</owner>
+  <owner>tmathmeyer@chromium.org</owner>
+  <summary>
+    This histogram lets us count how many PictureBuffers we've overallocated
+    when we're allocating in contiguous blocks. This case is harder to optimize
+    than the MultiTexture variant, but it is good to know the proportion of the
+    two of them to determine usage.
+  </summary>
+</histogram>
+
 <histogram name="Media.DetectedAudioCodecHash" enum="FFmpegCodecHashes"
     expires_after="never">
 <!-- expires-never: Codec support planning metric. -->
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index cf025f1..5ca378b1 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -571,6 +571,18 @@
   </summary>
 </histogram>
 
+<histogram name="Navigation.CrossOtr.ContextMenu.RefreshCount" units="count"
+    expires_after="2023-01-02">
+  <owner>mreichhoff@chromium.org</owner>
+  <owner>wanderview@chromium.org</owner>
+  <owner>bcl@chromium.org</owner>
+  <owner>dc-komics@google.com</owner>
+  <summary>
+    The count of refreshes on the first document after a cross-OTR navigation
+    completes. This metric is written when default classifications were applied.
+  </summary>
+</histogram>
+
 <histogram name="Navigation.CrossOtr.ContextMenu.RefreshCountExperimental"
     units="count" expires_after="2023-01-02">
   <owner>mreichhoff@chromium.org</owner>
@@ -584,6 +596,21 @@
   </summary>
 </histogram>
 
+<histogram name="Navigation.CrossOtr.ContextMenu.ResponseCode"
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2023-01-02">
+  <owner>mreichhoff@chromium.org</owner>
+  <owner>wanderview@chromium.org</owner>
+  <owner>bcl@chromium.org</owner>
+  <owner>dc-komics@google.com</owner>
+  <summary>
+    Http response codes seen when opening links from non-OTR to OTR browsing.
+    Logged when using the &quot;Open Link in Incognito Window&quot; menu item.
+    Note that, in the case of redirects, multiple entries will be written for a
+    single navigation. This metric is written when default classifications were
+    applied.
+  </summary>
+</histogram>
+
 <histogram name="Navigation.CrossOtr.ContextMenu.ResponseCodeExperimental"
     enum="CombinedHttpResponseAndNetErrorCode" expires_after="2023-01-02">
   <owner>mreichhoff@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/OWNERS b/tools/metrics/histograms/metadata/others/OWNERS
index 86929a4..995da070 100644
--- a/tools/metrics/histograms/metadata/others/OWNERS
+++ b/tools/metrics/histograms/metadata/others/OWNERS
@@ -5,6 +5,9 @@
 per-file histograms.xml=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
 
 # For display, mouse, and input related histograms.
+# Primary reviewers
+jimmyxgong@chromium.org
+# Secondary reviewers
 zentaro@chromium.org
 # For Conversions.* histograms:
 johnidel@chromium.org
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 4bebc984..48813a3 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -6142,6 +6142,17 @@
   </summary>
 </histogram>
 
+<histogram name="FirstRun.TermsOfServicesPromoDisplayTime" units="ms"
+    expires_after="2023-08-08">
+  <owner>ginnyhuang@chromium.org</owner>
+  <owner>bling-get-set-up@google.com</owner>
+  <summary>
+    Records the time it takes for the user to accept Terms of Services after
+    it's shown in the first Promo screen on First Run. Note: This is not
+    available for the legacy FRE.
+  </summary>
+</histogram>
+
 <histogram name="FirstUserAction.BackgroundTime" units="minutes"
     expires_after="2022-09-12">
 <!-- Name completed by histogram_suffixes name="FirstUserActionType" and name="FirstUserActionTypeDevice" -->
diff --git a/tools/metrics/histograms/metadata/print/OWNERS b/tools/metrics/histograms/metadata/print/OWNERS
index 5a3a767..ff41ce21 100644
--- a/tools/metrics/histograms/metadata/print/OWNERS
+++ b/tools/metrics/histograms/metadata/print/OWNERS
@@ -3,4 +3,5 @@
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
 awscreen@chromium.org
+jimmyxgong@chromium.org
 zentaro@chromium.org
diff --git a/tools/metrics/histograms/metadata/printing/OWNERS b/tools/metrics/histograms/metadata/printing/OWNERS
index 5a3a767..ff41ce21 100644
--- a/tools/metrics/histograms/metadata/printing/OWNERS
+++ b/tools/metrics/histograms/metadata/printing/OWNERS
@@ -3,4 +3,5 @@
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
 awscreen@chromium.org
+jimmyxgong@chromium.org
 zentaro@chromium.org
diff --git a/tools/metrics/histograms/metadata/scanning/OWNERS b/tools/metrics/histograms/metadata/scanning/OWNERS
index d4a1d59..4f6eba0 100644
--- a/tools/metrics/histograms/metadata/scanning/OWNERS
+++ b/tools/metrics/histograms/metadata/scanning/OWNERS
@@ -2,4 +2,7 @@
 
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
+# Primary reviewers
+jimmyxgong@chromium.org
+# Secondary reviewers
 zentaro@chromium.org
diff --git a/tools/metrics/histograms/metadata/security/histograms.xml b/tools/metrics/histograms/metadata/security/histograms.xml
index bc13c16f..ef95c64 100644
--- a/tools/metrics/histograms/metadata/security/histograms.xml
+++ b/tools/metrics/histograms/metadata/security/histograms.xml
@@ -791,7 +791,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.IsPasswordFormSubmittedInDedicatedProcess"
-    enum="SiteIsolationIsDedicatedProcess" expires_after="2022-08-07">
+    enum="SiteIsolationIsDedicatedProcess" expires_after="2023-08-07">
   <owner>alexmos@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml
index a90764c..1a54054 100644
--- a/tools/metrics/histograms/metadata/stability/histograms.xml
+++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -240,7 +240,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.ShownAfterCrashingReason"
-    enum="ShownAfterCrashingReason" expires_after="2022-12-25">
+    enum="ShownAfterCrashingReason" expires_after="2023-08-09">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
@@ -252,7 +252,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.TabMarkedForReload"
-    enum="BooleanMarkedForReload" expires_after="2022-08-18">
+    enum="BooleanMarkedForReload" expires_after="2023-08-09">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <summary>
@@ -262,7 +262,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.TabMarkedForReload.Visibility"
-    enum="FrameVisibility" expires_after="2022-11-20">
+    enum="FrameVisibility" expires_after="2023-08-09">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <summary>
@@ -273,7 +273,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.Visibility" enum="CrashVisibility"
-    expires_after="2022-12-25">
+    expires_after="2023-08-09">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <owner>lfg@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml
index c7c6311..3277719 100644
--- a/tools/metrics/histograms/metadata/uma/histograms.xml
+++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -893,7 +893,7 @@
 </histogram>
 
 <histogram name="UMA.UnsentLogs.PersistedSizeInKB" units="KB"
-    expires_after="2022-08-01">
+    expires_after="2023-08-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -905,7 +905,7 @@
 </histogram>
 
 <histogram name="UMA.UnsentLogs.SentCount" units="samples"
-    expires_after="2022-08-01">
+    expires_after="2023-08-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -916,7 +916,7 @@
 </histogram>
 
 <histogram name="UMA.UnsentLogs.UnsentCount" units="samples"
-    expires_after="2022-08-01">
+    expires_after="2023-08-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -927,7 +927,7 @@
 </histogram>
 
 <histogram name="UMA.UnsentLogs.UnsentPercentage" units="%"
-    expires_after="2022-08-01">
+    expires_after="2023-08-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index 557b750c..cca3f5d 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -468,6 +468,19 @@
   </token>
 </histogram>
 
+<histogram name="Variations.SeedFetchTimeOnFirstRun" units="ms"
+    expires_after="2023-02-09">
+  <owner>ginnyhuang@chromium.org</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The latency of fetching an initial variations seed during first run on
+    non-Android platforms. Only considers cases where an HTTP 200 result was
+    received.
+
+    The respective metric for Android's is Variations.FirstRun.SeedFetchTime.
+  </summary>
+</histogram>
+
 <histogram name="Variations.SeedFreshness" units="minutes"
     expires_after="2023-01-15">
   <owner>asvitkine@chromium.org</owner>
diff --git a/tools/metrics/histograms/update_net_trust_anchors.py b/tools/metrics/histograms/update_net_trust_anchors.py
index d34a018..3661ee5 100755
--- a/tools/metrics/histograms/update_net_trust_anchors.py
+++ b/tools/metrics/histograms/update_net_trust_anchors.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/ui/base/ime/linux/OWNERS b/ui/base/ime/linux/OWNERS
new file mode 100644
index 0000000..b641050
--- /dev/null
+++ b/ui/base/ime/linux/OWNERS
@@ -0,0 +1,2 @@
+hidehiko@chromium.org
+thomasanderson@chromium.org
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 2b69a00..7feb867 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -95,10 +95,6 @@
     "LacrosResourcesFileSharing", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-// Enable or disable multitouch for virtual keyboard on ChromeOS.
-const base::Feature kVirtualKeyboardMultitouch{
-    "VirtualKeyboardMultitouch", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Update of the virtual keyboard settings UI as described in
 // https://crbug.com/876901.
 const base::Feature kInputMethodSettingsUiUpdate = {
diff --git a/ui/base/ui_base_features.h b/ui/base/ui_base_features.h
index b190c0d..2584490 100644
--- a/ui/base/ui_base_features.h
+++ b/ui/base/ui_base_features.h
@@ -38,8 +38,6 @@
 extern const base::Feature kSystemKeyboardLock;
 COMPONENT_EXPORT(UI_BASE_FEATURES)
 extern const base::Feature kUiCompositorScrollWithLayers;
-COMPONENT_EXPORT(UI_BASE_FEATURES)
-extern const base::Feature kVirtualKeyboardMultitouch;
 
 COMPONENT_EXPORT(UI_BASE_FEATURES) bool IsUiGpuRasterizationEnabled();
 
diff --git a/ui/chromeos/shill_error.cc b/ui/chromeos/shill_error.cc
index a64e62cf..d874c4d 100644
--- a/ui/chromeos/shill_error.cc
+++ b/ui/chromeos/shill_error.cc
@@ -17,7 +17,7 @@
 namespace {
 
 const ash::NetworkState* GetNetworkState(const std::string& network_id) {
-  return chromeos::NetworkHandler::Get()
+  return ash::NetworkHandler::Get()
       ->network_state_handler()
       ->GetNetworkStateFromGuid(network_id);
 }
diff --git a/ui/gl/gl_display.cc b/ui/gl/gl_display.cc
index 9fc104a2..d17614675 100644
--- a/ui/gl/gl_display.cc
+++ b/ui/gl/gl_display.cc
@@ -5,11 +5,13 @@
 #include "ui/gl/gl_display.h"
 
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/debug/crash_logging.h"
+#include "base/export_template.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
@@ -585,7 +587,7 @@
 #else
       AddInitDisplay(init_displays, ANGLE_OPENGL);
       AddInitDisplay(init_displays, ANGLE_OPENGLES);
-#endif
+#endif  // BUILDFLAG(IS_ANDROID)
     } else {
       if (requested_renderer == kANGLEImplementationOpenGLName) {
         AddInitDisplay(init_displays, ANGLE_OPENGL);
@@ -660,11 +662,47 @@
                      supports_angle_metal, command_line, init_displays);
 }
 
-GLDisplay::GLDisplay(uint64_t system_device_id)
-    : system_device_id_(system_device_id) {}
+GLDisplay::GLDisplay(uint64_t system_device_id, DisplayPlatform type)
+    : system_device_id_(system_device_id), type_(type) {}
 
 GLDisplay::~GLDisplay() = default;
 
+template <typename GLDisplayPlatform>
+GLDisplayPlatform* GLDisplay::GetAs() {
+  bool type_checked = false;
+  switch (type_) {
+    case NONE:
+      NOTREACHED();
+      break;
+
+    case EGL:
+#if defined(USE_EGL)
+      type_checked = std::is_same<GLDisplayPlatform, GLDisplayEGL>::value;
+#endif  // defined(USE_EGL)
+      break;
+
+    case X11:
+#if defined(USE_GLX)
+      type_checked = std::is_same<GLDisplayPlatform, GLDisplayX11>::value;
+#endif  // defined(USE_GLX)
+      break;
+  }
+  if (type_checked)
+    return static_cast<GLDisplayPlatform*>(this);
+
+  return nullptr;
+}
+
+#if defined(USE_EGL)
+template EXPORT_TEMPLATE_DEFINE(GL_EXPORT)
+    GLDisplayEGL* GLDisplay::GetAs<GLDisplayEGL>();
+#endif  // defined(USE_EGL)
+
+#if defined(USE_GLX)
+template EXPORT_TEMPLATE_DEFINE(GL_EXPORT)
+    GLDisplayX11* GLDisplay::GetAs<GLDisplayX11>();
+#endif  // defined(USE_GLX)
+
 #if defined(USE_EGL)
 GLDisplayEGL::EGLGpuSwitchingObserver::EGLGpuSwitchingObserver(
     EGLDisplay display)
@@ -678,9 +716,8 @@
 }
 
 GLDisplayEGL::GLDisplayEGL(uint64_t system_device_id)
-    : GLDisplay(system_device_id) {
+    : GLDisplay(system_device_id, EGL), display_(EGL_NO_DISPLAY) {
   ext = std::make_unique<DisplayExtensionsEGL>();
-  display_ = EGL_NO_DISPLAY;
 }
 
 GLDisplayEGL::~GLDisplayEGL() = default;
@@ -709,6 +746,10 @@
   egl_android_native_fence_sync_supported_ = false;
 }
 
+bool GLDisplayEGL::IsInitialized() {
+  return display_ != EGL_NO_DISPLAY;
+}
+
 void GLDisplayEGL::SetDisplay(EGLDisplay display) {
   display_ = display;
 }
@@ -935,7 +976,7 @@
   if (!is_angle) {
     egl_surfaceless_context_supported_ = false;
   }
-#endif
+#endif  // BUILDFLAG(IS_ANDROID)
 
   if (egl_surfaceless_context_supported_) {
     // EGL_KHR_surfaceless_context is supported but ensure
@@ -976,13 +1017,13 @@
       base::SysInfo::GetAndroidHardwareEGL() != "emulation") {
     egl_android_native_fence_sync_supported_ = true;
   }
-#endif
+#endif  // BUILDFLAG(IS_ANDROID)
 }
 #endif  // defined(USE_EGL)
 
 #if defined(USE_GLX)
 GLDisplayX11::GLDisplayX11(uint64_t system_device_id)
-    : GLDisplay(system_device_id) {}
+    : GLDisplay(system_device_id, X11) {}
 
 GLDisplayX11::~GLDisplayX11() = default;
 
@@ -991,6 +1032,10 @@
 }
 
 void GLDisplayX11::Shutdown() {}
+
+bool GLDisplayX11::IsInitialized() {
+  return true;
+}
 #endif  // defined(USE_GLX)
 
 }  // namespace gl
diff --git a/ui/gl/gl_display.h b/ui/gl/gl_display.h
index 6114678..b44710c0 100644
--- a/ui/gl/gl_display.h
+++ b/ui/gl/gl_display.h
@@ -71,6 +71,12 @@
   DISPLAY_TYPE_MAX = 19,
 };
 
+enum DisplayPlatform {
+  NONE = 0,
+  EGL = 1,
+  X11 = 2,
+};
+
 GL_EXPORT void GetEGLInitDisplaysForTesting(
     bool supports_angle_d3d,
     bool supports_angle_opengl,
@@ -93,11 +99,16 @@
 
   virtual void* GetDisplay() = 0;
   virtual void Shutdown() = 0;
+  virtual bool IsInitialized() = 0;
+
+  template <typename GLDisplayPlatform>
+  GLDisplayPlatform* GetAs();
 
  protected:
-  explicit GLDisplay(uint64_t system_device_id);
+  GLDisplay(uint64_t system_device_id, DisplayPlatform type);
 
   uint64_t system_device_id_ = 0;
+  DisplayPlatform type_ = NONE;
 };
 
 #if defined(USE_EGL)
@@ -112,6 +123,7 @@
 
   EGLDisplay GetDisplay() override;
   void Shutdown() override;
+  bool IsInitialized() override;
 
   void SetDisplay(EGLDisplay display);
   EGLDisplayPlatform GetNativeDisplay() const;
@@ -169,6 +181,7 @@
 
   void* GetDisplay() override;
   void Shutdown() override;
+  bool IsInitialized() override;
 
  private:
   friend class GLDisplayManager<GLDisplayX11>;
diff --git a/ui/gl/gl_display_manager.h b/ui/gl/gl_display_manager.h
index 7325e20..54a908f 100644
--- a/ui/gl/gl_display_manager.h
+++ b/ui/gl/gl_display_manager.h
@@ -73,6 +73,11 @@
     return GetDisplay(system_device_id);
   }
 
+  bool IsEmpty() {
+    base::AutoLock auto_lock(lock_);
+    return displays_.empty();
+  }
+
  private:
   friend class base::NoDestructor<GLDisplayManager<GLDisplayPlatform>>;
 #if defined(USE_EGL)
diff --git a/ui/gl/gl_surface_egl_unittest.cc b/ui/gl/gl_surface_egl_unittest.cc
index 9bbf088..7f80b9fc 100644
--- a/ui/gl/gl_surface_egl_unittest.cc
+++ b/ui/gl/gl_surface_egl_unittest.cc
@@ -43,6 +43,11 @@
 
   void TearDown() override { GLSurfaceTestSupport::ShutdownGL(display_); }
 
+  GLDisplay* GetTestDisplay() {
+    EXPECT_NE(display_, nullptr);
+    return display_;
+  }
+
  private:
   raw_ptr<GLDisplay> display_ = nullptr;
 };
@@ -54,8 +59,8 @@
   surface_format.SetDepthBits(24);
   surface_format.SetStencilBits(8);
   surface_format.SetSamples(0);
-  scoped_refptr<GLSurface> surface =
-      init::CreateOffscreenGLSurfaceWithFormat(gfx::Size(1, 1), surface_format);
+  scoped_refptr<GLSurface> surface = init::CreateOffscreenGLSurfaceWithFormat(
+      GetTestDisplay(), gfx::Size(1, 1), surface_format);
   EGLConfig config = surface->GetConfig();
   EXPECT_TRUE(config);
 
diff --git a/ui/gl/gl_utils.cc b/ui/gl/gl_utils.cc
index 2bc87f2..a14b13ed 100644
--- a/ui/gl/gl_utils.cc
+++ b/ui/gl/gl_utils.cc
@@ -189,6 +189,23 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+GLDisplay* GetDisplay(GpuPreference gpu_preference) {
+#if defined(USE_GLX)
+  if (!GLDisplayManagerX11::GetInstance()->IsEmpty()) {
+    return GLDisplayManagerX11::GetInstance()->GetDisplay(gpu_preference);
+  }
+#endif
+#if defined(USE_EGL)
+  return GLDisplayManagerEGL::GetInstance()->GetDisplay(gpu_preference);
+#endif
+  NOTREACHED();
+  return nullptr;
+}
+
+GLDisplay* GetDefaultDisplay() {
+  return GetDisplay(GpuPreference::kDefault);
+}
+
 #if defined(USE_EGL)
 void SetGpuPreferenceEGL(GpuPreference preference, uint64_t system_device_id) {
   GLDisplayManagerEGL::GetInstance()->SetGpuPreference(preference,
diff --git a/ui/gl/gl_utils.h b/ui/gl/gl_utils.h
index 844bc41..3b2f82de 100644
--- a/ui/gl/gl_utils.h
+++ b/ui/gl/gl_utils.h
@@ -29,6 +29,7 @@
 #if defined(USE_GLX)
 class GLDisplayX11;
 #endif  // USE_GLX
+class GLDisplay;
 
 GL_EXPORT void Crash();
 GL_EXPORT void Hang();
@@ -73,6 +74,14 @@
 GL_EXPORT void SetGpuPreferenceEGL(GpuPreference preference,
                                    uint64_t system_device_id);
 
+// Query the default GLDisplay. May return either a GLDisplayEGL or
+// GLDisplayX11.
+GL_EXPORT GLDisplay* GetDefaultDisplay();
+
+// Query the GLDisplay by |gpu_preference|. May return either a GLDisplayEGL or
+// GLDisplayX11.
+GL_EXPORT GLDisplay* GetDisplay(GpuPreference gpu_preference);
+
 // Query the default GLDisplayEGL.
 GL_EXPORT GLDisplayEGL* GetDefaultDisplayEGL();
 
diff --git a/ui/gl/init/gl_factory.cc b/ui/gl/init/gl_factory.cc
index ff574eb..68afa64 100644
--- a/ui/gl/init/gl_factory.cc
+++ b/ui/gl/init/gl_factory.cc
@@ -248,8 +248,9 @@
   SetGLImplementation(kGLImplementationNone);
 }
 
-scoped_refptr<GLSurface> CreateOffscreenGLSurface(const gfx::Size& size) {
-  return CreateOffscreenGLSurfaceWithFormat(size, GLSurfaceFormat());
+scoped_refptr<GLSurface> CreateOffscreenGLSurface(gl::GLDisplay* display,
+                                                  const gfx::Size& size) {
+  return CreateOffscreenGLSurfaceWithFormat(display, size, GLSurfaceFormat());
 }
 
 void DisableANGLE() {
diff --git a/ui/gl/init/gl_factory.h b/ui/gl/init/gl_factory.h
index 218f4d9..581a7ad 100644
--- a/ui/gl/init/gl_factory.h
+++ b/ui/gl/init/gl_factory.h
@@ -91,6 +91,7 @@
 
 // Creates a GL surface that renders directly to a view.
 GL_INIT_EXPORT scoped_refptr<GLSurface> CreateViewGLSurface(
+    GLDisplay* display,
     gfx::AcceleratedWidget window);
 
 #if defined(USE_OZONE)
@@ -99,15 +100,19 @@
 // be presented as an overlay. If surfaceless mode is not supported or
 // enabled it will return a null pointer.
 GL_INIT_EXPORT scoped_refptr<GLSurface> CreateSurfacelessViewGLSurface(
+    GLDisplay* display,
     gfx::AcceleratedWidget window);
 #endif  // defined(USE_OZONE)
 
 // Creates a GL surface used for offscreen rendering.
 GL_INIT_EXPORT scoped_refptr<GLSurface> CreateOffscreenGLSurface(
+    GLDisplay* display,
     const gfx::Size& size);
 
 GL_INIT_EXPORT scoped_refptr<GLSurface> CreateOffscreenGLSurfaceWithFormat(
-    const gfx::Size& size, GLSurfaceFormat format);
+    GLDisplay* display,
+    const gfx::Size& size,
+    GLSurfaceFormat format);
 
 // Set platform dependent disabled extensions and re-initialize extension
 // bindings.
diff --git a/ui/gl/init/gl_factory_android.cc b/ui/gl/init/gl_factory_android.cc
index c508a04e..1816ea7 100644
--- a/ui/gl/init/gl_factory_android.cc
+++ b/ui/gl/init/gl_factory_android.cc
@@ -116,7 +116,8 @@
   }
 }
 
-scoped_refptr<GLSurface> CreateViewGLSurface(gfx::AcceleratedWidget window) {
+scoped_refptr<GLSurface> CreateViewGLSurface(GLDisplay* display,
+                                             gfx::AcceleratedWidget window) {
   TRACE_EVENT0("gpu", "gl::init::CreateViewGLSurface");
   CHECK_NE(kGLImplementationNone, GetGLImplementation());
   switch (GetGLImplementation()) {
@@ -124,7 +125,7 @@
     case kGLImplementationEGLANGLE:
       if (window != gfx::kNullAcceleratedWidget) {
         return InitializeGLSurface(new NativeViewGLSurfaceEGL(
-            GLSurfaceEGL::GetGLDisplayEGL(), window, nullptr));
+            display->GetAs<gl::GLDisplayEGL>(), window, nullptr));
       } else {
         return InitializeGLSurface(new GLSurfaceStub());
       }
@@ -135,20 +136,22 @@
 }
 
 scoped_refptr<GLSurface> CreateOffscreenGLSurfaceWithFormat(
-    const gfx::Size& size, GLSurfaceFormat format) {
+    GLDisplay* display,
+    const gfx::Size& size,
+    GLSurfaceFormat format) {
   TRACE_EVENT0("gpu", "gl::init::CreateOffscreenGLSurface");
   CHECK_NE(kGLImplementationNone, GetGLImplementation());
   switch (GetGLImplementation()) {
     case kGLImplementationEGLGLES2:
     case kGLImplementationEGLANGLE: {
-      if (GLSurfaceEGL::GetGLDisplayEGL()->IsEGLSurfacelessContextSupported() &&
+      GLDisplayEGL* display_egl = display->GetAs<gl::GLDisplayEGL>();
+      if (display_egl->IsEGLSurfacelessContextSupported() &&
           (size.width() == 0 && size.height() == 0)) {
         return InitializeGLSurfaceWithFormat(
-            new SurfacelessEGL(GLSurfaceEGL::GetGLDisplayEGL(), size), format);
+            new SurfacelessEGL(display_egl, size), format);
       } else {
         return InitializeGLSurfaceWithFormat(
-            new PbufferGLSurfaceEGL(GLSurfaceEGL::GetGLDisplayEGL(), size),
-            format);
+            new PbufferGLSurfaceEGL(display_egl, size), format);
       }
     }
     case kGLImplementationMockGL:
diff --git a/ui/gl/init/gl_factory_mac.cc b/ui/gl/init/gl_factory_mac.cc
index 95a53815..e308645d 100644
--- a/ui/gl/init/gl_factory_mac.cc
+++ b/ui/gl/init/gl_factory_mac.cc
@@ -107,7 +107,8 @@
   }
 }
 
-scoped_refptr<GLSurface> CreateViewGLSurface(gfx::AcceleratedWidget window) {
+scoped_refptr<GLSurface> CreateViewGLSurface(GLDisplay* display,
+                                             gfx::AcceleratedWidget window) {
   TRACE_EVENT0("gpu", "gl::init::CreateViewGLSurface");
   switch (GetGLImplementation()) {
     case kGLImplementationDesktopGL:
@@ -127,7 +128,9 @@
 }
 
 scoped_refptr<GLSurface> CreateOffscreenGLSurfaceWithFormat(
-    const gfx::Size& size, GLSurfaceFormat format) {
+    GLDisplay* display,
+    const gfx::Size& size,
+    GLSurfaceFormat format) {
   TRACE_EVENT0("gpu", "gl::init::CreateOffscreenGLSurface");
   switch (GetGLImplementation()) {
     case kGLImplementationDesktopGL:
@@ -136,16 +139,17 @@
           new NoOpGLSurface(size), format);
 #if defined(USE_EGL)
     case kGLImplementationEGLGLES2:
-    case kGLImplementationEGLANGLE:
-      if (GLSurfaceEGL::GetGLDisplayEGL()->IsEGLSurfacelessContextSupported() &&
+    case kGLImplementationEGLANGLE: {
+      GLDisplayEGL* display_egl = display->GetAs<gl::GLDisplayEGL>();
+      if (display_egl->IsEGLSurfacelessContextSupported() &&
           size.width() == 0 && size.height() == 0) {
         return InitializeGLSurfaceWithFormat(
-            new SurfacelessEGL(GLSurfaceEGL::GetGLDisplayEGL(), size), format);
+            new SurfacelessEGL(display_egl, size), format);
       } else {
         return InitializeGLSurfaceWithFormat(
-            new PbufferGLSurfaceEGL(GLSurfaceEGL::GetGLDisplayEGL(), size),
-            format);
+            new PbufferGLSurfaceEGL(display_egl, size), format);
       }
+    }
 #endif  // defined(USE_EGL)
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
diff --git a/ui/gl/init/gl_factory_ozone.cc b/ui/gl/init/gl_factory_ozone.cc
index 57b4c97..8760e21 100644
--- a/ui/gl/init/gl_factory_ozone.cc
+++ b/ui/gl/init/gl_factory_ozone.cc
@@ -58,11 +58,12 @@
   return nullptr;
 }
 
-scoped_refptr<GLSurface> CreateViewGLSurface(gfx::AcceleratedWidget window) {
+scoped_refptr<GLSurface> CreateViewGLSurface(GLDisplay* display,
+                                             gfx::AcceleratedWidget window) {
   TRACE_EVENT0("gpu", "gl::init::CreateViewGLSurface");
 
   if (HasGLOzone())
-    return GetGLOzone()->CreateViewGLSurface(window);
+    return GetGLOzone()->CreateViewGLSurface(display, window);
 
   switch (GetGLImplementation()) {
     case kGLImplementationMockGL:
@@ -76,13 +77,16 @@
 }
 
 scoped_refptr<GLSurface> CreateSurfacelessViewGLSurface(
+    GLDisplay* display,
     gfx::AcceleratedWidget window) {
   TRACE_EVENT0("gpu", "gl::init::CreateSurfacelessViewGLSurface");
-  return HasGLOzone() ? GetGLOzone()->CreateSurfacelessViewGLSurface(window)
-                      : nullptr;
+  return HasGLOzone()
+             ? GetGLOzone()->CreateSurfacelessViewGLSurface(display, window)
+             : nullptr;
 }
 
 scoped_refptr<GLSurface> CreateOffscreenGLSurfaceWithFormat(
+    GLDisplay* display,
     const gfx::Size& size,
     GLSurfaceFormat format) {
   TRACE_EVENT0("gpu", "gl::init::CreateOffscreenGLSurface");
@@ -93,7 +97,7 @@
   }
 
   if (HasGLOzone())
-    return GetGLOzone()->CreateOffscreenGLSurface(size);
+    return GetGLOzone()->CreateOffscreenGLSurface(display, size);
 
   switch (GetGLImplementation()) {
     case kGLImplementationMockGL:
diff --git a/ui/gl/init/gl_factory_win.cc b/ui/gl/init/gl_factory_win.cc
index f61749a..ea69bdba 100644
--- a/ui/gl/init/gl_factory_win.cc
+++ b/ui/gl/init/gl_factory_win.cc
@@ -59,13 +59,14 @@
   }
 }
 
-scoped_refptr<GLSurface> CreateViewGLSurface(gfx::AcceleratedWidget window) {
+scoped_refptr<GLSurface> CreateViewGLSurface(GLDisplay* display,
+                                             gfx::AcceleratedWidget window) {
   TRACE_EVENT0("gpu", "gl::init::CreateViewGLSurface");
   switch (GetGLImplementation()) {
     case kGLImplementationEGLANGLE: {
       DCHECK_NE(window, gfx::kNullAcceleratedWidget);
       return InitializeGLSurface(base::MakeRefCounted<NativeViewGLSurfaceEGL>(
-          GLSurfaceEGL::GetGLDisplayEGL(), window,
+          display->GetAs<gl::GLDisplayEGL>(), window,
           std::make_unique<VSyncProviderWin>(window)));
     }
     case kGLImplementationMockGL:
@@ -78,19 +79,22 @@
 }
 
 scoped_refptr<GLSurface> CreateOffscreenGLSurfaceWithFormat(
-    const gfx::Size& size, GLSurfaceFormat format) {
+    GLDisplay* display,
+    const gfx::Size& size,
+    GLSurfaceFormat format) {
   TRACE_EVENT0("gpu", "gl::init::CreateOffscreenGLSurface");
   switch (GetGLImplementation()) {
-    case kGLImplementationEGLANGLE:
-      if (GLSurfaceEGL::GetGLDisplayEGL()->IsEGLSurfacelessContextSupported() &&
+    case kGLImplementationEGLANGLE: {
+      GLDisplayEGL* display_egl = display->GetAs<gl::GLDisplayEGL>();
+      if (display_egl->IsEGLSurfacelessContextSupported() &&
           size.width() == 0 && size.height() == 0) {
         return InitializeGLSurfaceWithFormat(
-            new SurfacelessEGL(GLSurfaceEGL::GetGLDisplayEGL(), size), format);
+            new SurfacelessEGL(display_egl, size), format);
       } else {
         return InitializeGLSurfaceWithFormat(
-            new PbufferGLSurfaceEGL(GLSurfaceEGL::GetGLDisplayEGL(), size),
-            format);
+            new PbufferGLSurfaceEGL(display_egl, size), format);
       }
+    }
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
       return new GLSurfaceStub;
@@ -121,7 +125,7 @@
   switch (implementation) {
     case kGLImplementationEGLANGLE:
       return InitializeExtensionSettingsOneOffEGL(
-          static_cast<GLDisplayEGL*>(display));
+          display->GetAs<GLDisplayEGL>());
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
       return true;
diff --git a/ui/gl/test/gl_image_test_template.h b/ui/gl/test/gl_image_test_template.h
index 3e1ae06..e5eb4bcb 100644
--- a/ui/gl/test/gl_image_test_template.h
+++ b/ui/gl/test/gl_image_test_template.h
@@ -75,7 +75,7 @@
   void SetUp() override {
     auto prefered_impl = delegate_.GetPreferedGLImplementation();
     display_ = GLImageTestSupport::InitializeGL(prefered_impl);
-    surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
+    surface_ = gl::init::CreateOffscreenGLSurface(display_, gfx::Size());
     context_ =
         gl::init::CreateGLContext(nullptr, surface_.get(), GLContextAttribs());
     context_->MakeCurrent(surface_.get());
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc
index f175dc87..7e7bb78 100644
--- a/ui/ozone/common/gl_ozone_egl.cc
+++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -86,6 +86,7 @@
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEGL::CreateSurfacelessViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget window) {
   // This will usually not be implemented by the platform specific version.
   return nullptr;
diff --git a/ui/ozone/common/gl_ozone_egl.h b/ui/ozone/common/gl_ozone_egl.h
index 0560d835..6aca58a 100644
--- a/ui/ozone/common/gl_ozone_egl.h
+++ b/ui/ozone/common/gl_ozone_egl.h
@@ -49,10 +49,13 @@
       gl::GLSurface* compatible_surface,
       const gl::GLContextAttribs& attribs) override;
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override = 0;
   scoped_refptr<gl::GLSurface> CreateSurfacelessViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override;
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override = 0;
 
  protected:
diff --git a/ui/ozone/common/gl_surface_egl_readback.cc b/ui/ozone/common/gl_surface_egl_readback.cc
index 8d14021..bf711f2 100644
--- a/ui/ozone/common/gl_surface_egl_readback.cc
+++ b/ui/ozone/common/gl_surface_egl_readback.cc
@@ -19,8 +19,8 @@
 
 }  // namespace
 
-GLSurfaceEglReadback::GLSurfaceEglReadback()
-    : PbufferGLSurfaceEGL(GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size(1, 1)),
+GLSurfaceEglReadback::GLSurfaceEglReadback(gl::GLDisplayEGL* display)
+    : PbufferGLSurfaceEGL(display, gfx::Size(1, 1)),
       task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
 
 bool GLSurfaceEglReadback::Resize(const gfx::Size& size,
diff --git a/ui/ozone/common/gl_surface_egl_readback.h b/ui/ozone/common/gl_surface_egl_readback.h
index d0ad9790e..44620ad1 100644
--- a/ui/ozone/common/gl_surface_egl_readback.h
+++ b/ui/ozone/common/gl_surface_egl_readback.h
@@ -22,7 +22,7 @@
 // there is no FBO implementation for Ozone.
 class GLSurfaceEglReadback : public gl::PbufferGLSurfaceEGL {
  public:
-  GLSurfaceEglReadback();
+  explicit GLSurfaceEglReadback(gl::GLDisplayEGL* display);
 
   GLSurfaceEglReadback(const GLSurfaceEglReadback&) = delete;
   GLSurfaceEglReadback& operator=(const GLSurfaceEglReadback&) = delete;
diff --git a/ui/ozone/demo/simple_renderer_factory.cc b/ui/ozone/demo/simple_renderer_factory.cc
index da58010..6dcad2d5 100644
--- a/ui/ozone/demo/simple_renderer_factory.cc
+++ b/ui/ozone/demo/simple_renderer_factory.cc
@@ -9,6 +9,7 @@
 
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/ozone/demo/gl_renderer.h"
@@ -35,12 +36,13 @@
 const char kEnableVulkan[] = "enable-vulkan";
 #endif
 
-scoped_refptr<gl::GLSurface> CreateGLSurface(gfx::AcceleratedWidget widget) {
+scoped_refptr<gl::GLSurface> CreateGLSurface(gl::GLDisplay* display,
+                                             gfx::AcceleratedWidget widget) {
   scoped_refptr<gl::GLSurface> surface;
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableSurfaceless))
-    surface = gl::init::CreateSurfacelessViewGLSurface(widget);
+    surface = gl::init::CreateSurfacelessViewGLSurface(display, widget);
   if (!surface)
-    surface = gl::init::CreateViewGLSurface(widget);
+    surface = gl::init::CreateViewGLSurface(display, widget);
   return surface;
 }
 
@@ -48,7 +50,12 @@
 
 SimpleRendererFactory::SimpleRendererFactory() {}
 
-SimpleRendererFactory::~SimpleRendererFactory() {}
+SimpleRendererFactory::~SimpleRendererFactory() {
+  if (display_) {
+    gl::init::ShutdownGL(display_, false);
+    display_ = nullptr;
+  }
+}
 
 bool SimpleRendererFactory::Initialize() {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -65,9 +72,13 @@
     }
   }
 #endif
-  if (!command_line->HasSwitch(kDisableGpu) &&
-      gl::init::InitializeGLOneOff(/*system_device_id=*/0)) {
-    type_ = GL;
+  if (!command_line->HasSwitch(kDisableGpu)) {
+    display_ = gl::init::InitializeGLOneOff(/*system_device_id=*/0);
+    if (display_) {
+      type_ = GL;
+    } else {
+      type_ = SOFTWARE;
+    }
   } else {
     type_ = SOFTWARE;
   }
@@ -84,7 +95,7 @@
       surface_factory_ozone->CreatePlatformWindowSurface(widget);
   switch (type_) {
     case GL: {
-      scoped_refptr<gl::GLSurface> surface = CreateGLSurface(widget);
+      scoped_refptr<gl::GLSurface> surface = CreateGLSurface(display_, widget);
       if (!surface)
         LOG(FATAL) << "Failed to create GL surface";
       if (surface->IsSurfaceless()) {
diff --git a/ui/ozone/demo/simple_renderer_factory.h b/ui/ozone/demo/simple_renderer_factory.h
index eb1ef76..94a1b23 100644
--- a/ui/ozone/demo/simple_renderer_factory.h
+++ b/ui/ozone/demo/simple_renderer_factory.h
@@ -14,6 +14,10 @@
 #include "gpu/vulkan/vulkan_implementation.h"
 #endif
 
+namespace gl {
+class GLDisplay;
+};
+
 namespace ui {
 
 class SimpleRendererFactory : public RendererFactory {
@@ -43,6 +47,7 @@
 #endif
 
   RendererType type_ = SOFTWARE;
+  gl::GLDisplay* display_ = nullptr;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/demo/skia/skia_renderer_factory.cc b/ui/ozone/demo/skia/skia_renderer_factory.cc
index bbad1df..812e50db 100644
--- a/ui/ozone/demo/skia/skia_renderer_factory.cc
+++ b/ui/ozone/demo/skia/skia_renderer_factory.cc
@@ -9,6 +9,7 @@
 
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/ozone/demo/skia/skia_gl_renderer.h"
@@ -22,12 +23,13 @@
 
 const char kDisableSurfaceless[] = "disable-surfaceless";
 
-scoped_refptr<gl::GLSurface> CreateGLSurface(gfx::AcceleratedWidget widget) {
+scoped_refptr<gl::GLSurface> CreateGLSurface(gl::GLDisplay* display,
+                                             gfx::AcceleratedWidget widget) {
   scoped_refptr<gl::GLSurface> surface;
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableSurfaceless))
-    surface = gl::init::CreateSurfacelessViewGLSurface(widget);
+    surface = gl::init::CreateSurfacelessViewGLSurface(display, widget);
   if (!surface)
-    surface = gl::init::CreateViewGLSurface(widget);
+    surface = gl::init::CreateViewGLSurface(display, widget);
   return surface;
 }
 
@@ -35,10 +37,16 @@
 
 SkiaRendererFactory::SkiaRendererFactory() {}
 
-SkiaRendererFactory::~SkiaRendererFactory() {}
+SkiaRendererFactory::~SkiaRendererFactory() {
+  if (display_) {
+    gl::init::ShutdownGL(display_, false);
+    display_ = nullptr;
+  }
+}
 
 bool SkiaRendererFactory::Initialize() {
-  if (!gl::init::InitializeGLOneOff(/*system_device_id=*/0)) {
+  display_ = gl::init::InitializeGLOneOff(/*system_device_id=*/0);
+  if (!display_) {
     LOG(FATAL) << "Failed to initialize GL";
   }
 
@@ -52,7 +60,7 @@
       OzonePlatform::GetInstance()->GetSurfaceFactoryOzone();
   auto window_surface =
       surface_factory_ozone->CreatePlatformWindowSurface(widget);
-  scoped_refptr<gl::GLSurface> gl_surface = CreateGLSurface(widget);
+  scoped_refptr<gl::GLSurface> gl_surface = CreateGLSurface(display_, widget);
   if (!gl_surface)
     LOG(FATAL) << "Failed to create GL surface";
   if (gl_surface->IsSurfaceless()) {
diff --git a/ui/ozone/demo/skia/skia_renderer_factory.h b/ui/ozone/demo/skia/skia_renderer_factory.h
index 96688375..372382d 100644
--- a/ui/ozone/demo/skia/skia_renderer_factory.h
+++ b/ui/ozone/demo/skia/skia_renderer_factory.h
@@ -11,6 +11,10 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/demo/renderer_factory.h"
 
+namespace gl {
+class GLDisplay;
+};
+
 namespace ui {
 
 class Renderer;
@@ -32,6 +36,9 @@
   bool Initialize() override;
   std::unique_ptr<Renderer> CreateRenderer(gfx::AcceleratedWidget widget,
                                            const gfx::Size& size) override;
+
+ private:
+  gl::GLDisplay* display_ = nullptr;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/cast/gl_ozone_egl_cast.cc b/ui/ozone/platform/cast/gl_ozone_egl_cast.cc
index 779f213..42a05a82 100644
--- a/ui/ozone/platform/cast/gl_ozone_egl_cast.cc
+++ b/ui/ozone/platform/cast/gl_ozone_egl_cast.cc
@@ -87,18 +87,21 @@
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEglCast::CreateViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget widget) {
   // Verify requested widget dimensions match our current display size.
   DCHECK_EQ(static_cast<int>(widget >> 16), display_size_.width());
   DCHECK_EQ(static_cast<int>(widget & 0xffff), display_size_.height());
 
-  return gl::InitializeGLSurface(new GLSurfaceCast(widget, this));
+  return gl::InitializeGLSurface(
+      new GLSurfaceCast(display->GetAs<gl::GLDisplayEGL>(), widget, this));
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEglCast::CreateOffscreenGLSurface(
+    gl::GLDisplay* display,
     const gfx::Size& size) {
   return gl::InitializeGLSurface(
-      new gl::PbufferGLSurfaceEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+      new gl::PbufferGLSurfaceEGL(display->GetAs<gl::GLDisplayEGL>(), size));
 }
 
 gl::EGLDisplayPlatform GLOzoneEglCast::GetNativeDisplay() {
diff --git a/ui/ozone/platform/cast/gl_ozone_egl_cast.h b/ui/ozone/platform/cast/gl_ozone_egl_cast.h
index 0fe5369..b5e77f7 100644
--- a/ui/ozone/platform/cast/gl_ozone_egl_cast.h
+++ b/ui/ozone/platform/cast/gl_ozone_egl_cast.h
@@ -32,8 +32,10 @@
 
   // GLOzoneEGL implementation:
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget widget) override;
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override;
   gl::EGLDisplayPlatform GetNativeDisplay() override;
   bool LoadGLES2Bindings(
diff --git a/ui/ozone/platform/cast/gl_surface_cast.cc b/ui/ozone/platform/cast/gl_surface_cast.cc
index 80eccd2..0db51672 100644
--- a/ui/ozone/platform/cast/gl_surface_cast.cc
+++ b/ui/ozone/platform/cast/gl_surface_cast.cc
@@ -43,10 +43,11 @@
 
 namespace ui {
 
-GLSurfaceCast::GLSurfaceCast(gfx::AcceleratedWidget widget,
+GLSurfaceCast::GLSurfaceCast(gl::GLDisplayEGL* display,
+                             gfx::AcceleratedWidget widget,
                              GLOzoneEglCast* parent)
     : NativeViewGLSurfaceEGL(
-          GLSurfaceEGL::GetGLDisplayEGL(),
+          display,
           parent->GetNativeWindow(),
           std::make_unique<gfx::FixedVSyncProvider>(base::TimeTicks(),
                                                     GetVSyncInterval())),
diff --git a/ui/ozone/platform/cast/gl_surface_cast.h b/ui/ozone/platform/cast/gl_surface_cast.h
index 4727050..663d321 100644
--- a/ui/ozone/platform/cast/gl_surface_cast.h
+++ b/ui/ozone/platform/cast/gl_surface_cast.h
@@ -20,7 +20,9 @@
 
 class GLSurfaceCast : public gl::NativeViewGLSurfaceEGL {
  public:
-  GLSurfaceCast(gfx::AcceleratedWidget widget, GLOzoneEglCast* parent);
+  GLSurfaceCast(gl::GLDisplayEGL* display,
+                gfx::AcceleratedWidget widget,
+                GLOzoneEglCast* parent);
 
   GLSurfaceCast(const GLSurfaceCast&) = delete;
   GLSurfaceCast& operator=(const GLSurfaceCast&) = delete;
diff --git a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
index fce88d6..648759c 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
@@ -133,23 +133,26 @@
   }
 
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     return nullptr;
   }
 
   scoped_refptr<gl::GLSurface> CreateSurfacelessViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     return gl::InitializeGLSurface(new GbmSurfaceless(
-        surface_factory_, drm_thread_proxy_->CreateDrmWindowProxy(window),
-        window));
+        surface_factory_, display->GetAs<gl::GLDisplayEGL>(),
+        drm_thread_proxy_->CreateDrmWindowProxy(window), window));
   }
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override {
     DCHECK_EQ(size.width(), 0);
     DCHECK_EQ(size.height(), 0);
     return gl::InitializeGLSurface(
-        new gl::SurfacelessEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+        new gl::SurfacelessEGL(display->GetAs<gl::GLDisplayEGL>(), size));
   }
 
  protected:
diff --git a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
index 60b3bfa..e12811f 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
@@ -38,16 +38,16 @@
 }  // namespace
 
 GbmSurfaceless::GbmSurfaceless(GbmSurfaceFactory* surface_factory,
+                               gl::GLDisplayEGL* display,
                                std::unique_ptr<DrmWindowProxy> window,
                                gfx::AcceleratedWidget widget)
-    : SurfacelessEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size()),
+    : SurfacelessEGL(display, gfx::Size()),
       surface_factory_(surface_factory),
       window_(std::move(window)),
       widget_(widget),
       has_implicit_external_sync_(
-          GetGLDisplayEGL()->ext->b_EGL_ARM_implicit_external_sync),
-      has_image_flush_external_(
-          GetGLDisplayEGL()->ext->b_EGL_EXT_image_flush_external) {
+          display->ext->b_EGL_ARM_implicit_external_sync),
+      has_image_flush_external_(display->ext->b_EGL_EXT_image_flush_external) {
   surface_factory_->RegisterSurface(window_->widget(), this);
   supports_plane_gpu_fences_ = window_->SupportsGpuFences();
   unsubmitted_frames_.push_back(std::make_unique<PendingFrame>());
diff --git a/ui/ozone/platform/drm/gpu/gbm_surfaceless.h b/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
index 0025fa45..558ab79c 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
+++ b/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
@@ -33,6 +33,7 @@
 class GbmSurfaceless : public gl::SurfacelessEGL {
  public:
   GbmSurfaceless(GbmSurfaceFactory* surface_factory,
+                 gl::GLDisplayEGL* display,
                  std::unique_ptr<DrmWindowProxy> window,
                  gfx::AcceleratedWidget widget);
 
diff --git a/ui/ozone/platform/flatland/flatland_surface_factory.cc b/ui/ozone/platform/flatland/flatland_surface_factory.cc
index b2691601..e02430a 100644
--- a/ui/ozone/platform/flatland/flatland_surface_factory.cc
+++ b/ui/ozone/platform/flatland/flatland_surface_factory.cc
@@ -44,18 +44,20 @@
 
   // GLOzone:
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     // GL rendering to Flatland views is not supported. This function is
     // used only for unittests. Return an off-screen surface, so the tests pass.
     // TODO(crbug.com/1271760): Use Vulkan in unittests and remove this hack.
     return gl::InitializeGLSurface(base::MakeRefCounted<gl::SurfacelessEGL>(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size(100, 100)));
+        display->GetAs<gl::GLDisplayEGL>(), gfx::Size(100, 100)));
   }
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override {
     return gl::InitializeGLSurface(base::MakeRefCounted<gl::SurfacelessEGL>(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+        display->GetAs<gl::GLDisplayEGL>(), size));
   }
 
   gl::EGLDisplayPlatform GetNativeDisplay() override {
diff --git a/ui/ozone/platform/headless/headless_surface_factory.cc b/ui/ozone/platform/headless/headless_surface_factory.cc
index fbcf7a52..9b0fce0 100644
--- a/ui/ozone/platform/headless/headless_surface_factory.cc
+++ b/ui/ozone/platform/headless/headless_surface_factory.cc
@@ -107,8 +107,8 @@
 
 class FileGLSurface : public GLSurfaceEglReadback {
  public:
-  explicit FileGLSurface(const base::FilePath& location)
-      : location_(location) {}
+  FileGLSurface(gl::GLDisplayEGL* display, const base::FilePath& location)
+      : GLSurfaceEglReadback(display), location_(location) {}
 
   FileGLSurface(const FileGLSurface&) = delete;
   FileGLSurface& operator=(const FileGLSurface&) = delete;
@@ -188,16 +188,19 @@
 
   // GLOzone:
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     return gl::InitializeGLSurface(base::MakeRefCounted<FileGLSurface>(
+        display->GetAs<gl::GLDisplayEGL>(),
         GetPathForWidget(base_path_, window)));
   }
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override {
     return gl::InitializeGLSurface(
         base::MakeRefCounted<gl::PbufferGLSurfaceEGL>(
-            gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+            display->GetAs<gl::GLDisplayEGL>(), size));
   }
 
  protected:
diff --git a/ui/ozone/platform/scenic/scenic_surface_factory.cc b/ui/ozone/platform/scenic/scenic_surface_factory.cc
index 6e39541d..98740640 100644
--- a/ui/ozone/platform/scenic/scenic_surface_factory.cc
+++ b/ui/ozone/platform/scenic/scenic_surface_factory.cc
@@ -56,18 +56,20 @@
 
   // GLOzone:
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     // GL rendering to Flatland views is not supported. This function is
     // used only for unittests. Return an off-screen surface, so the tests pass.
     // TODO(crbug.com/1271760): Use Vulkan in unittests and remove this hack.
     return gl::InitializeGLSurface(base::MakeRefCounted<gl::SurfacelessEGL>(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size(100, 100)));
+        display->GetAs<gl::GLDisplayEGL>(), gfx::Size(100, 100)));
   }
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override {
     return gl::InitializeGLSurface(base::MakeRefCounted<gl::SurfacelessEGL>(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+        display->GetAs<gl::GLDisplayEGL>(), size));
   }
 
   gl::EGLDisplayPlatform GetNativeDisplay() override {
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
index 94f2571..44630f4 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
@@ -111,9 +111,10 @@
 }
 
 GbmSurfacelessWayland::GbmSurfacelessWayland(
+    gl::GLDisplayEGL* display,
     WaylandBufferManagerGpu* buffer_manager,
     gfx::AcceleratedWidget widget)
-    : SurfacelessEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size()),
+    : SurfacelessEGL(display, gfx::Size()),
       buffer_manager_(buffer_manager),
       widget_(widget),
       solid_color_buffers_holder_(std::make_unique<SolidColorBufferHolder>()),
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
index 9821ea3..ec69bd0 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
@@ -30,7 +30,8 @@
 class GbmSurfacelessWayland : public gl::SurfacelessEGL,
                               public WaylandSurfaceGpu {
  public:
-  GbmSurfacelessWayland(WaylandBufferManagerGpu* buffer_manager,
+  GbmSurfacelessWayland(gl::GLDisplayEGL* display,
+                        WaylandBufferManagerGpu* buffer_manager,
                         gfx::AcceleratedWidget widget);
 
   GbmSurfacelessWayland(const GbmSurfacelessWayland&) = delete;
diff --git a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
index ba537ea..8452279ad 100644
--- a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
@@ -27,9 +27,12 @@
 GLSurfaceEglReadbackWayland::PixelBuffer::~PixelBuffer() = default;
 
 GLSurfaceEglReadbackWayland::GLSurfaceEglReadbackWayland(
+    gl::GLDisplayEGL* display,
     gfx::AcceleratedWidget widget,
     WaylandBufferManagerGpu* buffer_manager)
-    : widget_(widget), buffer_manager_(buffer_manager) {
+    : GLSurfaceEglReadback(display),
+      widget_(widget),
+      buffer_manager_(buffer_manager) {
   buffer_manager_->RegisterSurface(widget_, this);
 }
 
diff --git a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h
index 6a48c48..df7d6a92 100644
--- a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.h
@@ -33,7 +33,8 @@
 class GLSurfaceEglReadbackWayland : public GLSurfaceEglReadback,
                                     public WaylandSurfaceGpu {
  public:
-  GLSurfaceEglReadbackWayland(gfx::AcceleratedWidget widget,
+  GLSurfaceEglReadbackWayland(gl::GLDisplayEGL* display,
+                              gfx::AcceleratedWidget widget,
                               WaylandBufferManagerGpu* buffer_manager);
   GLSurfaceEglReadbackWayland(const GLSurfaceEglReadbackWayland&) = delete;
   GLSurfaceEglReadbackWayland& operator=(const GLSurfaceEglReadbackWayland&) =
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
index 59ab6ab..0cbfdb8 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
@@ -60,12 +60,15 @@
       GLuint texture_id) override;
 
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget widget) override;
 
   scoped_refptr<gl::GLSurface> CreateSurfacelessViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override;
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override;
 
  protected:
@@ -95,6 +98,7 @@
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget widget) {
   // Only EGLGLES2 is supported with surfaceless view gl.
   if ((gl::GetGLImplementation() != gl::kGLImplementationEGLGLES2) ||
@@ -112,22 +116,23 @@
   if (!egl_window)
     return nullptr;
   return gl::InitializeGLSurface(new GLSurfaceWayland(
-      gl::GLSurfaceEGL::GetGLDisplayEGL(), std::move(egl_window), window));
+      display->GetAs<gl::GLDisplayEGL>(), std::move(egl_window), window));
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateSurfacelessViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget window) {
   if (gl::IsSoftwareGLImplementation(gl::GetGLImplementationParts())) {
     return gl::InitializeGLSurface(
-        base::MakeRefCounted<GLSurfaceEglReadbackWayland>(window,
-                                                          buffer_manager_));
+        base::MakeRefCounted<GLSurfaceEglReadbackWayland>(
+            display->GetAs<gl::GLDisplayEGL>(), window, buffer_manager_));
   } else {
 #if defined(WAYLAND_GBM)
   // If there is a gbm device available, use surfaceless gl surface.
   if (!buffer_manager_->GetGbmDevice())
     return nullptr;
-  return gl::InitializeGLSurface(
-      new GbmSurfacelessWayland(buffer_manager_, window));
+  return gl::InitializeGLSurface(new GbmSurfacelessWayland(
+      display->GetAs<gl::GLDisplayEGL>(), buffer_manager_, window));
 #else
   return nullptr;
 #endif
@@ -135,14 +140,15 @@
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateOffscreenGLSurface(
+    gl::GLDisplay* display,
     const gfx::Size& size) {
-  if (gl::GLSurfaceEGL::GetGLDisplayEGL()->IsEGLSurfacelessContextSupported() &&
+  if (display->GetAs<gl::GLDisplayEGL>()->IsEGLSurfacelessContextSupported() &&
       size.width() == 0 && size.height() == 0) {
     return gl::InitializeGLSurface(
-        new gl::SurfacelessEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+        new gl::SurfacelessEGL(display->GetAs<gl::GLDisplayEGL>(), size));
   } else {
     return gl::InitializeGLSurface(
-        new gl::PbufferGLSurfaceEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+        new gl::PbufferGLSurfaceEGL(display->GetAs<gl::GLDisplayEGL>(), size));
   }
 }
 
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
index 8224a73b..b8d5fc3 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
@@ -23,6 +23,7 @@
 #include "ui/gfx/overlay_plane_data.h"
 #include "ui/gfx/overlay_priority_hint.h"
 #include "ui/gl/gl_image_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_surface_factory.h"
@@ -237,7 +238,8 @@
 
   auto* gl_ozone = surface_factory_->GetGLOzone(
       gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
-  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(
+      gl::GetDefaultDisplay(), widget_);
   EXPECT_TRUE(gl_surface);
   gl_surface->SetRelyOnImplicitSync();
   static_cast<ui::GbmSurfacelessWayland*>(gl_surface.get())
@@ -529,7 +531,8 @@
 
   auto* gl_ozone = surface_factory_->GetGLOzone(
       gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
-  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(
+      gl::GetDefaultDisplay(), widget_);
   EXPECT_TRUE(gl_surface);
   gl_surface->SetRelyOnImplicitSync();
   static_cast<ui::GbmSurfacelessWayland*>(gl_surface.get())
@@ -864,7 +867,8 @@
   auto* gl_ozone = surface_factory_->GetGLOzone(
       gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
   EXPECT_TRUE(gl_ozone);
-  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(
+      gl::GetDefaultDisplay(), widget_);
   EXPECT_FALSE(gl_surface);
 
   // Now, set gbm.
@@ -872,20 +876,23 @@
 
   // It's still impossible to create the device if supports_dmabuf is false.
   EXPECT_FALSE(buffer_manager_gpu_->GetGbmDevice());
-  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(gl::GetDefaultDisplay(),
+                                                        widget_);
   EXPECT_FALSE(gl_surface);
 
   // Now set supports_dmabuf.
   buffer_manager_gpu_->supports_dmabuf_ = true;
   EXPECT_TRUE(buffer_manager_gpu_->GetGbmDevice());
-  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(gl::GetDefaultDisplay(),
+                                                        widget_);
   EXPECT_TRUE(gl_surface);
 
   // Reset gbm now. WaylandConnectionProxy can reset it when zwp is not
   // available. And factory must behave the same way as previously.
   buffer_manager_gpu_->gbm_device_ = nullptr;
   EXPECT_FALSE(buffer_manager_gpu_->GetGbmDevice());
-  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(gl::GetDefaultDisplay(),
+                                                        widget_);
   EXPECT_FALSE(gl_surface);
 }
 
@@ -904,7 +911,8 @@
 
   auto* gl_ozone = surface_factory_->GetGLOzone(
       gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));
-  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(widget_);
+  auto gl_surface = gl_ozone->CreateSurfacelessViewGLSurface(
+      gl::GetDefaultDisplay(), widget_);
   EXPECT_TRUE(gl_surface);
   gl_surface->SetRelyOnImplicitSync();
   static_cast<ui::GbmSurfacelessWayland*>(gl_surface.get())
diff --git a/ui/ozone/platform/x11/gl_ozone_glx.cc b/ui/ozone/platform/x11/gl_ozone_glx.cc
index 06dc614..23ff67c 100644
--- a/ui/ozone/platform/x11/gl_ozone_glx.cc
+++ b/ui/ozone/platform/x11/gl_ozone_glx.cc
@@ -120,16 +120,19 @@
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneGLX::CreateViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget window) {
   return gl::InitializeGLSurface(new gl::GLSurfaceGLXX11(window));
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneGLX::CreateSurfacelessViewGLSurface(
+    gl::GLDisplay* display,
     gfx::AcceleratedWidget window) {
   return nullptr;
 }
 
 scoped_refptr<gl::GLSurface> GLOzoneGLX::CreateOffscreenGLSurface(
+    gl::GLDisplay* display,
     const gfx::Size& size) {
   return gl::InitializeGLSurface(new gl::UnmappedNativeViewGLSurfaceGLX(size));
 }
diff --git a/ui/ozone/platform/x11/gl_ozone_glx.h b/ui/ozone/platform/x11/gl_ozone_glx.h
index e518299d..05f3b43b 100644
--- a/ui/ozone/platform/x11/gl_ozone_glx.h
+++ b/ui/ozone/platform/x11/gl_ozone_glx.h
@@ -44,10 +44,13 @@
       gl::GLSurface* compatible_surface,
       const gl::GLContextAttribs& attribs) override;
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override;
   scoped_refptr<gl::GLSurface> CreateSurfacelessViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override;
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override;
 };
 
diff --git a/ui/ozone/platform/x11/gl_surface_egl_readback_x11.cc b/ui/ozone/platform/x11/gl_surface_egl_readback_x11.cc
index c76733f..7118a30 100644
--- a/ui/ozone/platform/x11/gl_surface_egl_readback_x11.cc
+++ b/ui/ozone/platform/x11/gl_surface_egl_readback_x11.cc
@@ -18,8 +18,10 @@
 
 }
 
-GLSurfaceEglReadbackX11::GLSurfaceEglReadbackX11(gfx::AcceleratedWidget window)
-    : window_(static_cast<x11::Window>(window)),
+GLSurfaceEglReadbackX11::GLSurfaceEglReadbackX11(gl::GLDisplayEGL* display,
+                                                 gfx::AcceleratedWidget window)
+    : GLSurfaceEglReadback(display),
+      window_(static_cast<x11::Window>(window)),
       connection_(x11::Connection::Get()) {}
 
 bool GLSurfaceEglReadbackX11::Initialize(gl::GLSurfaceFormat format) {
diff --git a/ui/ozone/platform/x11/gl_surface_egl_readback_x11.h b/ui/ozone/platform/x11/gl_surface_egl_readback_x11.h
index 0f85f5c78f..63cc4211 100644
--- a/ui/ozone/platform/x11/gl_surface_egl_readback_x11.h
+++ b/ui/ozone/platform/x11/gl_surface_egl_readback_x11.h
@@ -14,7 +14,8 @@
 // GLSurface implementation that copies pixels from readback to an XWindow.
 class GLSurfaceEglReadbackX11 : public GLSurfaceEglReadback {
  public:
-  explicit GLSurfaceEglReadbackX11(gfx::AcceleratedWidget window);
+  GLSurfaceEglReadbackX11(gl::GLDisplayEGL* display,
+                          gfx::AcceleratedWidget window);
 
   GLSurfaceEglReadbackX11(const GLSurfaceEglReadbackX11&) = delete;
   GLSurfaceEglReadbackX11& operator=(const GLSurfaceEglReadbackX11&) = delete;
diff --git a/ui/ozone/platform/x11/x11_surface_factory.cc b/ui/ozone/platform/x11/x11_surface_factory.cc
index 72107d0..b19cfb1 100644
--- a/ui/ozone/platform/x11/x11_surface_factory.cc
+++ b/ui/ozone/platform/x11/x11_surface_factory.cc
@@ -65,21 +65,23 @@
   }
 
   scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) override {
     if (is_swiftshader_) {
       return gl::InitializeGLSurface(
-          base::MakeRefCounted<GLSurfaceEglReadbackX11>(window));
+          base::MakeRefCounted<GLSurfaceEglReadbackX11>(
+              display->GetAs<gl::GLDisplayEGL>(), window));
     } else {
       switch (gl::GetGLImplementation()) {
         case gl::kGLImplementationEGLGLES2:
           DCHECK(window != gfx::kNullAcceleratedWidget);
           return gl::InitializeGLSurface(new gl::NativeViewGLSurfaceEGLX11GLES2(
-              gl::GLSurfaceEGL::GetGLDisplayEGL(),
+              display->GetAs<gl::GLDisplayEGL>(),
               static_cast<x11::Window>(window)));
         case gl::kGLImplementationEGLANGLE:
           DCHECK(window != gfx::kNullAcceleratedWidget);
           return gl::InitializeGLSurface(new gl::NativeViewGLSurfaceEGLX11(
-              gl::GLSurfaceEGL::GetGLDisplayEGL(),
+              display->GetAs<gl::GLDisplayEGL>(),
               static_cast<x11::Window>(window)));
         default:
           NOTREACHED();
@@ -89,15 +91,15 @@
   }
 
   scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) override {
-    if (gl::GLSurfaceEGL::GetGLDisplayEGL()
-            ->IsEGLSurfacelessContextSupported() &&
-        size.width() == 0 && size.height() == 0) {
-      return InitializeGLSurface(
-          new gl::SurfacelessEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+    gl::GLDisplayEGL* egl_display = display->GetAs<gl::GLDisplayEGL>();
+    if (egl_display->IsEGLSurfacelessContextSupported() && size.width() == 0 &&
+        size.height() == 0) {
+      return InitializeGLSurface(new gl::SurfacelessEGL(egl_display, size));
     } else {
-      return InitializeGLSurface(new gl::PbufferGLSurfaceEGL(
-          gl::GLSurfaceEGL::GetGLDisplayEGL(), size));
+      return InitializeGLSurface(
+          new gl::PbufferGLSurfaceEGL(egl_display, size));
     }
   }
 
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index b059e31..a466ffe 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -536,8 +536,10 @@
     req.y = new_bounds_in_pixels.y();
   }
 
-  if (origin_changed || size_changed)
+  if (origin_changed || size_changed) {
+    bounds_change_in_flight_ = true;
     connection_->ConfigureWindow(req);
+  }
 
   // Assume that the resize will go through as requested, which should be the
   // case if we're running without a window manager.  If there's a window
@@ -666,15 +668,16 @@
   bool origin_changed = bounds_in_pixels_.origin() != new_bounds_px.origin();
   bounds_in_pixels_ = new_bounds_px;
 
-  // If there is a restore in flight, then set a flag to ignore the single
-  // configure event (hopefully) coming from that restore.  This prevents any
-  // in-flight restore requests from changing the bounds in a way that conflicts
-  // with the `bounds_in_pixels_` setting above.  This is not perfect, and if
-  // there is some other in-flight bounds change for some reason, or if the
-  // ordering of events from the WM behaves differently, this will not prevent
-  // the issue.  See: http://crbug.com/1227451
-  ignore_next_configure_ = restore_in_flight_;
-
+  // If there is a restore and/or bounds change in flight, then set a flag to
+  // ignore the next one or two configure events (hopefully) coming from those
+  // requests. This prevents any in-flight restore requests from changing the
+  // bounds in a way that conflicts with the `bounds_in_pixels_` setting above.
+  // This is not perfect, and if there is some other in-flight bounds change for
+  // some reason, or if the ordering of events from the WM behaves differently,
+  // this will not prevent the issue.  See: http://crbug.com/1227451
+  ignore_next_configures_ = restore_in_flight_ ? 1 : 0;
+  if (bounds_change_in_flight_)
+    ignore_next_configures_++;
   // This must be the final call in this function, as `this` may be deleted
   // during the observation of this event.
   platform_window_delegate_->OnBoundsChanged({origin_changed});
@@ -2266,15 +2269,24 @@
     pending_counter_value_ = 0;
   }
 
-  // During a Restore() -> ToggleFullscreen() sequence, ignore the configure
-  // event from the restore if we're waiting on fullscreen.  After
+  // During a Restore() -> ToggleFullscreen() or Restore() -> SetBounds() ->
+  // ToggleFullscreen() sequence, ignore the configure events from the Restore
+  // and SetBounds requests, if we're waiting on fullscreen.  After
   // OnXWindowStateChanged unsets this flag, there will be a configuration event
   // that will set the bounds to the final fullscreen bounds.
-  if (ignore_next_configure_) {
-    ignore_next_configure_ = false;
+  if (ignore_next_configures_ > 0) {
+    ignore_next_configures_--;
     return;
   }
 
+  // Note: This OnConfigureEvent might not necessarily correspond to a previous
+  // SetBounds request. Due to limitations in X11 there isn't a way to
+  // match events to its original request. For now, we assume that the next
+  // OnConfigureEvent event after a SetBounds (ConfigureWindow) request is from
+  // that request. This would break in some scenarios (for example calling
+  // SetBounds more than once quickly). See crbug.com/1227451.
+  bounds_change_in_flight_ = false;
+
   // It's possible that the X window may be resized by some other means than
   // from within aura (e.g. the X window manager can change the size). Make
   // sure the root window size is maintained properly.
diff --git a/ui/ozone/platform/x11/x11_window.h b/ui/ozone/platform/x11/x11_window.h
index f033a9f0..28ed150d3 100644
--- a/ui/ozone/platform/x11/x11_window.h
+++ b/ui/ozone/platform/x11/x11_window.h
@@ -490,10 +490,14 @@
   // cross-display fullscreening, there is a Restore() (called by BrowserView)
   // that may cause configuration bounds updates that make this window appear to
   // temporarily be on a different screen than its destination screen.  This
-  // restore only happens if the window is maximized.
-  bool ignore_next_configure_ = false;
+  // restore only happens if the window is maximized. The integer represents how
+  // many events to ignore.
+  int ignore_next_configures_ = 0;
   // True between Restore() and the next OnXWindowStateChanged().
   bool restore_in_flight_ = false;
+  // True between SetBoundsInPixels (when the bounds actually change) and the
+  // next OnConfigureEvent.
+  bool bounds_change_in_flight_ = false;
 
   base::CancelableOnceClosure delayed_resize_task_;
 
diff --git a/ui/ozone/public/gl_ozone.h b/ui/ozone/public/gl_ozone.h
index c490e0ec..31f243b 100644
--- a/ui/ozone/public/gl_ozone.h
+++ b/ui/ozone/public/gl_ozone.h
@@ -91,6 +91,7 @@
 
   // Creates a GL surface that renders directly to a view.
   virtual scoped_refptr<gl::GLSurface> CreateViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) = 0;
 
   // Creates a GL surface that renders directly into a window with surfaceless
@@ -99,10 +100,12 @@
   // unsupported.
   // TODO(spang): Consider deprecating this and using OverlaySurface for GL.
   virtual scoped_refptr<gl::GLSurface> CreateSurfacelessViewGLSurface(
+      gl::GLDisplay* display,
       gfx::AcceleratedWidget window) = 0;
 
   // Creates a GL surface used for offscreen rendering.
   virtual scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
+      gl::GLDisplay* display,
       const gfx::Size& size) = 0;
 };
 
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index f8a56b5..5f0cfe0 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -165,7 +165,7 @@
 
 checked_in_dts_files = [
   "cr_elements/cr_button/cr_button.m.d.ts",
-  "cr_elements/cr_checkbox/cr_checkbox.m.d.ts",
+  "cr_elements/cr_checkbox/cr_checkbox.d.ts",
   "cr_elements/cr_container_shadow_behavior.m.d.ts",
   "cr_elements/cr_dialog/cr_dialog.m.d.ts",
   "cr_elements/cr_icon_button/cr_icon_button.d.ts",
diff --git a/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts b/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
index ace147e..32cb43e 100644
--- a/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.ts
@@ -9,7 +9,7 @@
  *  - edit the trust level of an already existing certificate authority.
  */
 import '../../cr_elements/cr_button/cr_button.m.js';
-import '../../cr_elements/cr_checkbox/cr_checkbox.m.js';
+import '../../cr_elements/cr_checkbox/cr_checkbox.js';
 import '../../cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import './certificate_shared.css.js';
@@ -17,7 +17,7 @@
 import {PaperSpinnerLiteElement} from 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {CrCheckboxElement} from '../../cr_elements/cr_checkbox/cr_checkbox.m.js';
+import {CrCheckboxElement} from '../../cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrDialogElement} from '../../cr_elements/cr_dialog/cr_dialog.m.js';
 import {I18nMixin} from '../../js/i18n_mixin.js';
 import {loadTimeData} from '../../js/load_time_data.m.js';
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/BUILD.gn b/ui/webui/resources/cr_components/chromeos/smb_shares/BUILD.gn
index a5aeb3a..8d90e43b 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/BUILD.gn
@@ -19,7 +19,7 @@
   deps = [
     ":smb_browser_proxy",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
-    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
+    "//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox",
     "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
     "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
     "//ui/webui/resources/js:cr.m",
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
index 20c0b55..9d1af7dba 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
@@ -10,7 +10,7 @@
  */
 
 import '../../../cr_elements/cr_button/cr_button.m.js';
-import '../../../cr_elements/cr_checkbox/cr_checkbox.m.js';
+import '../../../cr_elements/cr_checkbox/cr_checkbox.js';
 import '../../../cr_elements/cr_dialog/cr_dialog.m.js';
 import '../../../cr_elements/cr_input/cr_input.m.js';
 import '../../../cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js';
diff --git a/ui/webui/resources/cr_components/history_clusters/cluster.html b/ui/webui/resources/cr_components/history_clusters/cluster.html
index ed08663..df5732b 100644
--- a/ui/webui/resources/cr_components/history_clusters/cluster.html
+++ b/ui/webui/resources/cr_components/history_clusters/cluster.html
@@ -7,6 +7,10 @@
     padding-bottom: var(--cluster-padding-vertical);
   }
 
+  :host([in-side-panel]) {
+    padding-bottom: 0;
+  }
+
   :host-context(.focus-outline-visible):host(:focus) #container {
     box-shadow: inset 0 0 0 2px var(--cr-focus-outline-color);
   }
@@ -17,16 +21,20 @@
     margin-bottom: var(--cluster-padding-vertical);
   }
 
+  :host([in-side-panel]) #container {
+    border-bottom: 3px solid var(--cr-separator-color);
+  }
+
   /* We need an inner container div to apply spacing between clusters. This is
      because iron-list ignores the margin on the host element. */
-  #container {
+  :host(:not([in-side-panel])) #container {
     background-color: var(--cr-card-background-color);
     border-radius: var(--cr-card-border-radius);
     box-shadow: var(--cr-card-shadow);
     padding: var(--cluster-padding-vertical) 0;
   }
 
-  #label-row {
+  .label-row {
     align-items: center;
     display: flex;
     flex-grow: 1;
@@ -37,12 +45,20 @@
     user-select: none;
   }
 
-  #label {
+  .label {
     color: var(--cr-primary-text-color);
     font-size: 1rem;  /* 16px */
     font-weight: 500;
   }
 
+  #label-and-timestamp {
+    align-items: center;
+    display: flex;
+    flex-direction: column;
+    flex-shrink: 0;
+    row-gap: 6px;
+  }
+
   #related-searches {
     display: flex;
     flex-wrap: wrap;
@@ -99,8 +115,18 @@
     on-open-all-visits="onOpenAllVisits_"
     on-remove-all-visits="onRemoveAllVisits_"
     on-remove-visit="onRemoveVisit_">
-  <div id="label-row">
-    <div id="label"></div>
+  <!-- In the side panel the label and timestamp should be stacked on the
+       left, while outside the side panel the timestamp and the menu should
+       be side by side on the right. -->
+  <div class="label-row" hidden="[[!inSidePanel]]">
+    <div id="label-and-timestamp">
+      <div id="labelSidePanel" class="label"></div>
+      <div class="timestamp">[[cluster.visits.0.relativeDate]]</div>
+    </div>
+    <menu-container></menu-container>
+  </div>
+  <div class="label-row" hidden="[[inSidePanel]]">
+    <div id="label" class="label"></div>
     <div class="timestamp-and-menu">
       <div class="timestamp">[[cluster.visits.0.relativeDate]]</div>
       <menu-container></menu-container>
diff --git a/ui/webui/resources/cr_components/history_clusters/cluster.ts b/ui/webui/resources/cr_components/history_clusters/cluster.ts
index 0287cfb..0b2f7693 100644
--- a/ui/webui/resources/cr_components/history_clusters/cluster.ts
+++ b/ui/webui/resources/cr_components/history_clusters/cluster.ts
@@ -37,6 +37,7 @@
 interface HistoryClusterElement {
   $: {
     label: HTMLElement,
+    labelSidePanel: HTMLElement,
     container: HTMLElement,
   };
 }
@@ -66,6 +67,15 @@
       },
 
       /**
+       * Whether the cluster is in the side panel.
+       */
+      inSidePanel: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('inSidePanel'),
+        reflectToAttribute: true,
+      },
+
+      /**
        * The current query for which related clusters are requested and shown.
        */
       query: String,
@@ -113,6 +123,7 @@
 
   cluster: Cluster;
   index: number;
+  inSidePanel: boolean;
   query: string;
   private callbackRouter_: PageCallbackRouter;
   private expanded_: boolean;
@@ -300,8 +311,9 @@
       return 'no_label';
     }
 
+    const label = this.inSidePanel ? this.$.labelSidePanel : this.$.label;
     insertHighlightedTextWithMatchesIntoElement(
-        this.$.label, this.cluster.label!, this.cluster.labelMatchPositions);
+        label, this.cluster.label!, this.cluster.labelMatchPositions);
     return this.cluster.label!;
   }
 
diff --git a/ui/webui/resources/cr_components/history_clusters/clusters.html b/ui/webui/resources/cr_components/history_clusters/clusters.html
index 2cde804..51d4060 100644
--- a/ui/webui/resources/cr_components/history_clusters/clusters.html
+++ b/ui/webui/resources/cr_components/history_clusters/clusters.html
@@ -13,6 +13,11 @@
     padding: var(--first-cluster-padding-top) var(--cluster-padding-horizontal) 0;
   }
 
+ :host([in-side-panel]) #clusters {
+    min-width: 0;
+    padding: 0 0 0;
+ }
+
   #placeholder {
     align-items: center;
     color: var(--md-loading-message-color);
diff --git a/ui/webui/resources/cr_components/history_clusters/clusters.ts b/ui/webui/resources/cr_components/history_clusters/clusters.ts
index 1f24556..8cbd434 100644
--- a/ui/webui/resources/cr_components/history_clusters/clusters.ts
+++ b/ui/webui/resources/cr_components/history_clusters/clusters.ts
@@ -68,6 +68,15 @@
   static get properties() {
     return {
       /**
+       * Whether the clusters are in the side panel.
+       */
+      inSidePanel: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('inSidePanel'),
+        reflectToAttribute: true,
+      },
+
+      /**
        * The current query for which related clusters are requested and shown.
        */
       query: {
@@ -113,6 +122,7 @@
   // Properties
   //============================================================================
 
+  inSidePanel: boolean;
   query: string;
   private callbackRouter_: PageCallbackRouter;
   private headerText_: string;
diff --git a/ui/webui/resources/cr_components/history_clusters/shared_vars.css b/ui/webui/resources/cr_components/history_clusters/shared_vars.css
index 6146827..05ffceb 100644
--- a/ui/webui/resources/cr_components/history_clusters/shared_vars.css
+++ b/ui/webui/resources/cr_components/history_clusters/shared_vars.css
@@ -31,6 +31,11 @@
 
 /* Sizes: */
 html {
+  --card-max-width: 960px;
+  --card-min-width: 550px;
+  --card-padding-between: 16px;
+  --card-padding-side: 24px;
+  --first-card-padding-top: 24px;
   --cluster-max-width: var(--card-max-width);
   --cluster-min-width: var(--card-min-width);
   --cluster-padding-horizontal: var(--card-padding-side);
diff --git a/ui/webui/resources/cr_elements/BUILD.gn b/ui/webui/resources/cr_elements/BUILD.gn
index 425ff12..5a15887 100644
--- a/ui/webui/resources/cr_elements/BUILD.gn
+++ b/ui/webui/resources/cr_elements/BUILD.gn
@@ -86,8 +86,6 @@
       "cr_actionable_row_style.html",
       "cr_button/cr_button.html",
       "cr_button/cr_button.js",
-      "cr_checkbox/cr_checkbox.html",
-      "cr_checkbox/cr_checkbox.js",
       "cr_container_shadow_behavior.html",
       "cr_container_shadow_behavior.js",
       "cr_dialog/cr_dialog.html",
@@ -139,7 +137,7 @@
       "action_link_css.m.js",
       "cr_actionable_row_style.m.js",
       "cr_button/cr_button.m.js",
-      "cr_checkbox/cr_checkbox.m.js",
+      "cr_checkbox/cr_checkbox.js",
       "cr_container_shadow_behavior.m.js",
       "cr_dialog/cr_dialog.m.js",
       "cr_icon_button/cr_icon_button.js",
@@ -175,7 +173,6 @@
   group("closure_compile") {
     deps = [
       ":cr_elements_resources",
-      "cr_checkbox:closure_compile",
       "cr_dialog:closure_compile",
       "cr_input:closure_compile",
       "cr_lottie:closure_compile",
@@ -293,7 +290,7 @@
       ":shared_style_css_module",
       ":shared_vars_css_module",
       "cr_button:cr_button_module",
-      "cr_checkbox:cr_checkbox_module",
+      "cr_checkbox:web_components",
       "cr_dialog:cr_dialog_module",
       "cr_icon_button:web_components",
       "cr_input:polymer3_elements",
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/BUILD.gn b/ui/webui/resources/cr_elements/cr_checkbox/BUILD.gn
index f7f9dee..ee1c18e 100644
--- a/ui/webui/resources/cr_elements/cr_checkbox/BUILD.gn
+++ b/ui/webui/resources/cr_elements/cr_checkbox/BUILD.gn
@@ -3,35 +3,20 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
+import("//tools/polymer/html_to_js.gni")
 
-js_type_check("closure_compile") {
-  uses_legacy_modules = true
-  deps = [ ":cr_checkbox" ]
-}
-
-js_library("cr_checkbox") {
-  deps = [ "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-ripple-behavior-extracted" ]
-}
-
-polymer_modulizer("cr_checkbox") {
-  js_file = "cr_checkbox.js"
-  html_file = "cr_checkbox.html"
-  html_type = "dom-module"
+html_to_js("web_components") {
+  js_files = [ "cr_checkbox.js" ]
 }
 
 js_type_check("closure_compile_module") {
   is_polymer3 = true
-  deps = [ ":cr_checkbox.m" ]
+  deps = [ ":cr_checkbox" ]
 }
 
-js_library("cr_checkbox.m") {
-  sources = [
-    "$root_gen_dir/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.m.js",
-  ]
+js_library("cr_checkbox") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/paper-behaviors:paper-ripple-behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":cr_checkbox_module" ]
 }
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.m.d.ts b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.d.ts
similarity index 100%
rename from ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.m.d.ts
rename to ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.d.ts
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.html b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.html
index 844b1f0..66cbbbb 100644
--- a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.html
+++ b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.html
@@ -1,26 +1,3 @@
-<link rel="import" href="../../html/polymer.html">
-
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="../shared_vars_css.html">
-
-<!--
-List of customizable styles:
-
-  --cr-checkbox-border-size
-  --cr-checkbox-checked-box-background-color
-  --cr-checkbox-checked-box-color
-  --cr-checkbox-label-color
-  --cr-checkbox-label-padding-start
-  --cr-checkbox-mark-color
-  --cr-checkbox-ripple-checked-color
-  --cr-checkbox-ripple-size
-  --cr-checkbox-ripple-unchecked-color
-  --cr-checkbox-size
-  --cr-checkbox-unchecked-box-color
--->
-<dom-module id="cr-checkbox">
-  <template>
     <style>
       :host {
         -webkit-tap-highlight-color: transparent;
@@ -172,6 +149,3 @@
       <slot></slot>
     </div>
     <div id="ariaDescription" aria-hidden="true">[[ariaDescription]]</div>
-  </template>
-  <script src="cr_checkbox.js"></script>
-</dom-module>
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
index 04709bc..a2d559df 100644
--- a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
+++ b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
@@ -8,12 +8,33 @@
  * interaction. By default it assumes there will be child(ren) passed in to be
  * used as labels. If no label will be provided, a .no-label class should be
  * added to hide the spacing between the checkbox and the label container.
+ *
+ * List of customizable styles:
+ *  --cr-checkbox-border-size
+ *  --cr-checkbox-checked-box-background-color
+ *  --cr-checkbox-checked-box-color
+ *  --cr-checkbox-label-color
+ *  --cr-checkbox-label-padding-start
+ *  --cr-checkbox-mark-color
+ *  --cr-checkbox-ripple-checked-color
+ *  --cr-checkbox-ripple-size
+ *  --cr-checkbox-ripple-unchecked-color
+ *  --cr-checkbox-size
+ *  --cr-checkbox-unchecked-box-color
  */
+import '//resources/polymer/v3_0/paper-styles/color.js';
+import '../shared_vars_css.m.js';
+
+import {PaperRippleBehavior} from '//resources/polymer/v3_0/paper-behaviors/paper-ripple-behavior.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
   is: 'cr-checkbox',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [
-    Polymer.PaperRippleBehavior,
+    PaperRippleBehavior,
   ],
 
   properties: {
@@ -155,11 +176,10 @@
   // customize the element's ripple
   _createRipple() {
     this._rippleContainer = this.$.checkbox;
-    const ripple = Polymer.PaperRippleBehavior._createRipple();
+    const ripple = PaperRippleBehavior._createRipple();
     ripple.id = 'ink';
     ripple.setAttribute('recenters', '');
     ripple.classList.add('circle', 'toggle-ink');
     return ripple;
   },
 });
-/* #ignore */ console.warn('crbug/1173575, non-JS module files deprecated.');