diff --git a/DEPS b/DEPS
index b006624..8fef72a3 100644
--- a/DEPS
+++ b/DEPS
@@ -297,7 +297,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '5d0618b59df269774607d703e977d4e671c125e5',
+  'skia_revision': '3fbcfbbd908e05e6511003355df6b593205641bf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -305,11 +305,11 @@
   # 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': '4330a827bac71d1928b02a93984a1b996bcfa0a7',
+  'angle_revision': '9a25828113d57cf95b0c9f583e904af151a2edde',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '1c3dfde53353d5e13ca25eb52aa208d450b5e980',
+  'swiftshader_revision': 'c21aa26e0256a3efed81740d6215acb393b61d9e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -324,7 +324,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:9.20220819.1.1',
+  'fuchsia_version': 'version:9.20220819.2.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -368,7 +368,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '72946313ec4f9773088a8258f7509fbeaf47465e',
+  'catapult_revision': '9bfc1aede5c2b3c736e89ae1af93660807a16654',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # 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': '39530caaf4faf099e939a27a574a1e6a837d138e',
+  'devtools_frontend_revision': '34b7dd793876ff02ee2dda54161a529a60ec5dfc',
   # 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.
@@ -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': 'd92f1d47573427e6417e29a3e82ea7d4c34fe0b5',
+  'nearby_revision': '5b7bb37d41f45635fcb848841524cb18664336dd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -480,7 +480,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       'db722166934ebc79a6e65e5fef9a6eae21eacb77',
+  'libcxx_revision':       '8b1c50618df7677fb1bd4bdf40d15a55b1733293',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:0bcd37bd2b83f1a9ee17088037ebdfe6eab6d31a',
@@ -873,7 +873,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'sGVhzaqwgT697qIGKjfghO3mclJdO4I-L5nQoJqYb3sC',
+          'version': 'AD0RPrNJPAmLU42D6TRmyl_oyaX9DwtzHkISlAWK7u8C',
         },
       ],
       'dep_type': 'cipd',
@@ -1338,7 +1338,7 @@
     Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + '41cdffd71c9948f63c7ad36e1fb0ff519aa7a37e',
 
   'src/third_party/icu':
-    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'b3070c52557323463e6b9827e2343e60e1b91f85',
+    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '31c77cbfff890d173237f51b3c5930416b3d5b24',
 
   'src/third_party/icu4j': {
       'packages': [
@@ -1613,7 +1613,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'qvL35O3yU1ZbOWHVZBedmVtdaav1qKquii4RJyUh-PgC',
+              'version': '2P7CTTsDUzoP3f8LtGNRdtwC48KAMmV-hPoNhGAwiKMC',
           },
       ],
       'condition': 'checkout_android',
@@ -1693,7 +1693,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@41bdd557a6ae1523a6c5a4067942ad59fe62210f',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@691fb8dbe91e834e87f0e1dbe23ffbe22bfd4fa2',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1732,7 +1732,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'fef5f14679a5ec7185835be9142220bd8b79db6c',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '79c96ded88372967b3ca89c415e4b34d4ba391bd',
+    Var('webrtc_git') + '/src.git' + '@' + 'f7f9791cc740ecf855261e28090651db98bb3d37',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -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@f20197a770e5f1e30545d4bc852a5f05beaad445',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@31585af7148cabee01eef21c8ef70c46f6fbab21',
     'condition': 'checkout_src_internal',
   },
 
@@ -1846,7 +1846,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'zCrKLiPCz5bh2lVC4vnvgOsqADfZs68OXYymyBpbb-0C',
+        'version': 'p6yXpwzNA-pohu1ZVgaTtuoVemyUR6Nyj0zEPx-EDuEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/WATCHLISTS b/WATCHLISTS
index 72507f5..c10e86a 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2555,12 +2555,14 @@
     'diagnostics_mojo': ['ashleydp+watch-diagnostics_mojo@google.com',
                          'gavindodd+watch-diagnostics_mojo@google.com',
                          'michaelcheco+watch-diagnostics_mojo@google.com',
-                         'zentaro+watch-diagnostics_mojo@chromium.org'],
+                         'zentaro+watch-diagnostics_mojo@chromium.org',
+                         'dpad+watch-diagnostics_mojo@google.com'],
     'diagnostics_ui': ['gavinwill+diagnostics-watch@chromium.org',
                        'michaelcheco+diagnostics-watch@google.com',
                        'zentaro+diagnostics-watch@chromium.org',
                        'gavindodd+diagnostics-watch@google.com',
-                       'ashleydp+diagnostics-watch@google.com'],
+                       'ashleydp+diagnostics-watch@google.com',
+                       'dpad+diagnostics-watch@google.com'],
     'discardable_memory': ['thiabaud+watch-discardable-memory@google.com'],
     'disk_cache': ['gavinp+disk@chromium.org'],
     'dom_storage': ['dmurph+watchingdomstorage@chromium.org'],
@@ -2817,7 +2819,8 @@
     'remoting': ['chromoting-reviews@chromium.org'],
     'rgb_kbd': ['michaelcheco+watch-rgb-kbd@google.com',
                 'jimmyxgong+watch-rgb-kbd@chromium.org',
-                'zentaro+watch-rgb-kbd@chromium.org'],
+                'zentaro+watch-rgb-kbd@chromium.org',
+                'dpad+watch-rgb-kbd@google.com'],
     'rlz_id': ['gab+watch@chromium.org',
                'robertshield+watch@chromium.org'],
     'runtime_enabled_features': ['jmedley+watch@chromium.org'],
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 173d150..32a0d5f 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2658,11 +2658,7 @@
     "frame/caption_buttons/frame_size_button_unittest.cc",
     "frame/default_frame_header_unittest.cc",
     "frame/non_client_frame_view_ash_unittest.cc",
-    "glanceables/glanceables_controller_unittest.cc",
     "glanceables/glanceables_unittests.cc",
-    "glanceables/glanceables_up_next_view_unittest.cc",
-    "glanceables/glanceables_view_unittest.cc",
-    "glanceables/glanceables_weather_view_unittest.cc",
     "glanceables/glanceables_welcome_label_unittest.cc",
     "highlighter/highlighter_controller_unittest.cc",
     "highlighter/highlighter_gesture_util_unittest.cc",
diff --git a/ash/components/phonehub/util/histogram_util.cc b/ash/components/phonehub/util/histogram_util.cc
index 35d747e3..e55b0005 100644
--- a/ash/components/phonehub/util/histogram_util.cc
+++ b/ash/components/phonehub/util/histogram_util.cc
@@ -61,9 +61,14 @@
     case proto::MessageType::INITIATE_CAMERA_ROLL_ITEM_TRANSFER_REQUEST:
       return "PhoneHub.TaskCompletion.InitiateCameraRollItemTransfer.Result";
 
+    case proto::MessageType::FEATURE_SETUP_REQUEST:
+      [[fallthrough]];
+    case proto::MessageType::FEATURE_SETUP_RESPONSE:
+      return "PhoneHub.TaskCompletion.FeatureSetup.Result";
+
     default:
       // Note that PROVIDE_CROS_STATE, PHONE_STATUS_SNAPSHOT and
-      // PHONE_STATUS_UPDATE message types are not logged as part of this
+      // PHONE_STATUS_UPDATE message types are not logged as part of these
       // metrics.
       return std::string();
   }
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index ede7c22..5ced17f 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -897,6 +897,11 @@
     "HoldingSpaceInProgressNotificationSuppression",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables holding space icon to be permanently displayed with extended file
+// expiration to increase predictability of the feature.
+const base::Feature kHoldingSpacePredictability{
+    "HoldingSpacePredictability", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables rebranding of holding space to convey the relationship with
 // Files to simplify feature comprehension.
 const base::Feature kHoldingSpaceRebrand{"HoldingSpaceRebrand",
@@ -2060,6 +2065,10 @@
       kHoldingSpaceInProgressDownloadsNotificationSuppression);
 }
 
+bool IsHoldingSpacePredictabilityEnabled() {
+  return base::FeatureList::IsEnabled(kHoldingSpacePredictability);
+}
+
 bool IsHoldingSpaceRebrandEnabled() {
   return base::FeatureList::IsEnabled(kHoldingSpaceRebrand);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 48b030ce..e25a669b 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -372,6 +372,8 @@
 extern const base::Feature
     kHoldingSpaceInProgressDownloadsNotificationSuppression;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kHoldingSpacePredictability;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kHoldingSpaceRebrand;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kHoldingSpaceSuggestions;
@@ -771,6 +773,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHideShelfControlsInTabletModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsHoldingSpaceInProgressDownloadsNotificationSuppressionEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpacePredictabilityEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpaceRebrandEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpaceSuggestionsEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHostnameSettingEnabled();
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index 6ea4e70..2746a09 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -470,6 +470,12 @@
   if (!chromeos::features::IsDarkLightModeEnabled())
     return;
 
+  if (highlight_border_overlay_ ||
+      !GetWidget()->GetNativeWindow()->GetProperty(
+          chromeos::kShouldHaveHighlightBorderOverlay)) {
+    return;
+  }
+
   highlight_border_overlay_ =
       std::make_unique<HighlightBorderOverlay>(GetWidget());
 }
diff --git a/ash/glanceables/glanceables_controller.h b/ash/glanceables/glanceables_controller.h
index 2fb9b05c..030cada 100644
--- a/ash/glanceables/glanceables_controller.h
+++ b/ash/glanceables/glanceables_controller.h
@@ -39,9 +39,6 @@
   // Triggers a session restore.
   void RestoreSession();
 
-  views::Widget* widget_for_test() { return widget_.get(); }
-  GlanceablesView* view_for_test() { return view_; }
-
  private:
   friend class GlanceablesTest;
 
diff --git a/ash/glanceables/glanceables_controller_unittest.cc b/ash/glanceables/glanceables_controller_unittest.cc
deleted file mode 100644
index fd415c9..0000000
--- a/ash/glanceables/glanceables_controller_unittest.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/glanceables/glanceables_controller.h"
-
-#include "ash/constants/ash_features.h"
-#include "ash/glanceables/glanceables_view.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "base/test/scoped_feature_list.h"
-
-namespace ash {
-namespace {
-
-// Use a "no session" test so the glanceables widget is not automatically
-// created at the start of the test.
-// TODO(crbug.com/1353119): Once glanceables are shown by code in the
-// chrome/browser/ash layer, switch this to AshTestBase.
-class GlanceablesControllerTest : public NoSessionAshTestBase {
- protected:
-  base::test::ScopedFeatureList feature_list_{features::kGlanceables};
-};
-
-TEST_F(GlanceablesControllerTest, CreateUi) {
-  GlanceablesController* controller = Shell::Get()->glanceables_controller();
-  ASSERT_TRUE(controller);
-
-  controller->CreateUi();
-
-  // A fullscreen widget was created.
-  views::Widget* widget = controller->widget_for_test();
-  ASSERT_TRUE(widget);
-  EXPECT_TRUE(widget->IsFullscreen());
-
-  // The controller's view is the widget's contents view.
-  views::View* view = controller->view_for_test();
-  EXPECT_TRUE(view);
-  EXPECT_EQ(view, widget->GetContentsView());
-}
-
-TEST_F(GlanceablesControllerTest, DestroyUi) {
-  auto* controller = Shell::Get()->glanceables_controller();
-  ASSERT_TRUE(controller);
-
-  controller->CreateUi();
-  controller->DestroyUi();
-
-  EXPECT_FALSE(controller->widget_for_test());
-  EXPECT_FALSE(controller->view_for_test());
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/glanceables/glanceables_unittests.cc b/ash/glanceables/glanceables_unittests.cc
index 4ffbb03..bede21ea 100644
--- a/ash/glanceables/glanceables_unittests.cc
+++ b/ash/glanceables/glanceables_unittests.cc
@@ -2,18 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/ambient/ambient_controller.h"
+#include "ash/ambient/model/ambient_weather_model.h"
 #include "ash/constants/ash_features.h"
 #include "ash/glanceables/glanceables_controller.h"
 #include "ash/glanceables/glanceables_restore_view.h"
+#include "ash/glanceables/glanceables_up_next_view.h"
 #include "ash/glanceables/glanceables_view.h"
+#include "ash/glanceables/glanceables_weather_view.h"
+#include "ash/glanceables/glanceables_welcome_label.h"
 #include "ash/glanceables/test_glanceables_delegate.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/events/test/test_event.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/test/button_test_api.h"
 
 namespace ash {
+namespace {
+
+AmbientWeatherModel* GetWeatherModel() {
+  return Shell::Get()->ambient_controller()->GetAmbientWeatherModel();
+}
+
+}  // namespace
 
 // Unified test suite for the glanceables controller, views, etc.
 //
@@ -30,12 +45,33 @@
   void SetUp() override {
     NoSessionAshTestBase::SetUp();
     controller_ = Shell::Get()->glanceables_controller();
+    DCHECK(controller_);
   }
 
   TestGlanceablesDelegate* GetTestDelegate() {
     return static_cast<TestGlanceablesDelegate*>(controller_->delegate_.get());
   }
 
+  views::Widget* GetWidget() { return controller_->widget_.get(); }
+
+  GlanceablesView* GetGlanceablesView() { return controller_->view_; }
+
+  GlanceablesWelcomeLabel* GetWelcomeLabel() {
+    return controller_->view_->welcome_label_;
+  }
+
+  views::ImageView* GetWeatherIcon() {
+    return controller_->view_->weather_view_->icon_;
+  }
+
+  views::Label* GetWeatherTemperature() {
+    return controller_->view_->weather_view_->temperature_;
+  }
+
+  GlanceablesUpNextView* GetUpNextView() {
+    return controller_->view_->up_next_view_;
+  }
+
   GlanceablesRestoreView* GetRestoreView() {
     return controller_->view_->restore_view_;
   }
@@ -45,6 +81,71 @@
   base::test::ScopedFeatureList feature_list_{features::kGlanceables};
 };
 
+TEST_F(GlanceablesTest, CreateAndDestroyUi) {
+  controller_->CreateUi();
+
+  // A fullscreen widget was created.
+  views::Widget* widget = GetWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsFullscreen());
+
+  // The controller's view is the widget's contents view.
+  views::View* view = GetGlanceablesView();
+  EXPECT_TRUE(view);
+  EXPECT_EQ(view, widget->GetContentsView());
+
+  controller_->DestroyUi();
+
+  // Widget and glanceables view are destroyed.
+  EXPECT_FALSE(GetWidget());
+  EXPECT_FALSE(GetGlanceablesView());
+}
+
+TEST_F(GlanceablesTest, GlanceablesViewCreatesChildViews) {
+  controller_->CreateUi();
+
+  GlanceablesView* view = GetGlanceablesView();
+  ASSERT_TRUE(view);
+  EXPECT_TRUE(GetWelcomeLabel());
+  EXPECT_TRUE(GetWeatherIcon());
+  EXPECT_TRUE(GetWeatherTemperature());
+  EXPECT_TRUE(GetUpNextView());
+  EXPECT_TRUE(GetRestoreView());
+}
+
+TEST_F(GlanceablesTest, WeatherViewShowsWeather) {
+  controller_->CreateUi();
+
+  // Icon starts blank.
+  views::ImageView* icon = GetWeatherIcon();
+  EXPECT_TRUE(icon->GetImage().isNull());
+
+  // Trigger a weather update. Use an image the same size as the icon view's
+  // image so the image won't be resized and we can compare backing objects.
+  gfx::Rect image_bounds = icon->GetImageBounds();
+  gfx::ImageSkia weather_image =
+      gfx::test::CreateImageSkia(image_bounds.width(), image_bounds.height());
+  GetWeatherModel()->UpdateWeatherInfo(weather_image, 72.0f,
+                                       /*show_celsius=*/false);
+
+  // The view reflects the new weather.
+  EXPECT_EQ(weather_image.GetBackingObject(),
+            icon->GetImage().GetBackingObject());
+  EXPECT_EQ(u"72° F", GetWeatherTemperature()->GetText());
+}
+
+TEST_F(GlanceablesTest, UpNextViewRendersCorrectly) {
+  controller_->CreateUi();
+
+  // Events list contains rendered event items inside.
+  const auto& items = GetUpNextView()->events_list_items_views_for_test();
+  EXPECT_EQ(items.size(), 5u);
+  for (const auto& item : items) {
+    EXPECT_EQ(std::get<0>(item)->GetText(), u"James / Artsiom");
+    EXPECT_EQ(std::get<1>(item)->GetText(), u"2:00 – 2:30pm");
+  }
+}
+
 TEST_F(GlanceablesTest, ClickOnSessionRestore) {
   controller_->CreateUi();
 
diff --git a/ash/glanceables/glanceables_up_next_view_unittest.cc b/ash/glanceables/glanceables_up_next_view_unittest.cc
deleted file mode 100644
index 7bbb2ab..0000000
--- a/ash/glanceables/glanceables_up_next_view_unittest.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/glanceables/glanceables_up_next_view.h"
-
-#include "ash/test/ash_test_base.h"
-#include "ui/views/controls/label.h"
-
-namespace ash {
-namespace {
-
-class GlanceablesUpNextViewTest : public NoSessionAshTestBase {
- public:
-  void SetUp() override {
-    NoSessionAshTestBase::SetUp();
-    up_next_view_ = std::make_unique<GlanceablesUpNextView>();
-  }
-
- protected:
-  std::unique_ptr<GlanceablesUpNextView> up_next_view_;
-};
-
-TEST_F(GlanceablesUpNextViewTest, RendersCorrectly) {
-  // Events list contains rendered event items inside.
-  const auto& items = up_next_view_->events_list_items_views_for_test();
-  EXPECT_EQ(items.size(), 5u);
-  for (const auto& item : items) {
-    EXPECT_EQ(std::get<0>(item)->GetText(), u"James / Artsiom");
-    EXPECT_EQ(std::get<1>(item)->GetText(), u"2:00 – 2:30pm");
-  }
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/glanceables/glanceables_view.h b/ash/glanceables/glanceables_view.h
index f149d1e..9ea58a1 100644
--- a/ash/glanceables/glanceables_view.h
+++ b/ash/glanceables/glanceables_view.h
@@ -32,10 +32,6 @@
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
   void OnThemeChanged() override;
 
-  GlanceablesWelcomeLabel* welcome_label_for_test() { return welcome_label_; }
-  GlanceablesWeatherView* weather_view_for_test() { return weather_view_; }
-  GlanceablesUpNextView* up_next_view_for_test() { return up_next_view_; }
-
  private:
   friend class GlanceablesTest;
 
diff --git a/ash/glanceables/glanceables_view_unittest.cc b/ash/glanceables/glanceables_view_unittest.cc
deleted file mode 100644
index d865f70..0000000
--- a/ash/glanceables/glanceables_view_unittest.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/glanceables/glanceables_view.h"
-
-#include "ash/constants/ash_features.h"
-#include "ash/glanceables/glanceables_controller.h"
-#include "ash/glanceables/glanceables_welcome_label.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "base/test/scoped_feature_list.h"
-
-namespace ash {
-namespace {
-
-// Use a "no session" test so the glanceables widget is not automatically
-// created at the start of the test.
-// TODO(crbug.com/1353119): Once glanceables are shown by code in the
-// chrome/browser/ash layer, switch this to AshTestBase.
-class GlanceablesViewTest : public NoSessionAshTestBase {
- protected:
-  base::test::ScopedFeatureList feature_list_{features::kGlanceables};
-};
-
-TEST_F(GlanceablesViewTest, Basics) {
-  GlanceablesController* controller = Shell::Get()->glanceables_controller();
-  ASSERT_TRUE(controller);
-  controller->CreateUi();
-
-  GlanceablesView* view = controller->view_for_test();
-  ASSERT_TRUE(view);
-
-  // Welcome label was created.
-  GlanceablesWelcomeLabel* welcome_label = view->welcome_label_for_test();
-  ASSERT_TRUE(welcome_label);
-  EXPECT_FALSE(welcome_label->GetText().empty());
-
-  // "Up next" widget was created.
-  EXPECT_TRUE(view->up_next_view_for_test());
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/glanceables/glanceables_weather_view.h b/ash/glanceables/glanceables_weather_view.h
index 9853543..3630ee53 100644
--- a/ash/glanceables/glanceables_weather_view.h
+++ b/ash/glanceables/glanceables_weather_view.h
@@ -29,10 +29,9 @@
   // AmbientWeatherModelObserver:
   void OnWeatherInfoUpdated() override;
 
-  views::ImageView* icon_for_test() { return icon_; }
-  views::Label* temperature_for_test() { return temperature_; }
-
  private:
+  friend class GlanceablesTest;
+
   views::ImageView* icon_ = nullptr;
   views::Label* temperature_ = nullptr;
 };
diff --git a/ash/glanceables/glanceables_weather_view_unittest.cc b/ash/glanceables/glanceables_weather_view_unittest.cc
deleted file mode 100644
index e07e12b..0000000
--- a/ash/glanceables/glanceables_weather_view_unittest.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/glanceables/glanceables_weather_view.h"
-
-#include "ash/ambient/ambient_controller.h"
-#include "ash/ambient/model/ambient_weather_model.h"
-#include "ash/constants/ash_features.h"
-#include "ash/glanceables/glanceables_controller.h"
-#include "ash/glanceables/glanceables_view.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "base/test/scoped_feature_list.h"
-#include "ui/gfx/image/image_unittest_util.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-
-namespace ash {
-namespace {
-
-AmbientWeatherModel* GetWeatherModel() {
-  return Shell::Get()->ambient_controller()->GetAmbientWeatherModel();
-}
-
-// Use a "no session" test so the glanceables widget is not automatically
-// created at the start of the test.
-// TODO(crbug.com/1353119): Once glanceables are shown by code in the
-// chrome/browser/ash layer, switch this to AshTestBase.
-class GlanceablesWeatherViewTest : public NoSessionAshTestBase {
- protected:
-  base::test::ScopedFeatureList feature_list_{features::kGlanceables};
-};
-
-TEST_F(GlanceablesWeatherViewTest, Basics) {
-  GlanceablesController* controller = Shell::Get()->glanceables_controller();
-  ASSERT_TRUE(controller);
-  controller->CreateUi();
-
-  GlanceablesWeatherView* view =
-      controller->view_for_test()->weather_view_for_test();
-  ASSERT_TRUE(view);
-
-  // Icon starts blank.
-  views::ImageView* icon = view->icon_for_test();
-  EXPECT_TRUE(icon->GetImage().isNull());
-
-  // Trigger a weather update. Use an image the same size as the icon view's
-  // image so the image won't be resized and we can compare backing objects.
-  gfx::Rect image_bounds = icon->GetImageBounds();
-  gfx::ImageSkia weather_image =
-      gfx::test::CreateImageSkia(image_bounds.width(), image_bounds.height());
-  GetWeatherModel()->UpdateWeatherInfo(weather_image, 72.0f,
-                                       /*show_celsius=*/false);
-
-  // The view reflects the new weather.
-  EXPECT_EQ(weather_image.GetBackingObject(),
-            icon->GetImage().GetBackingObject());
-  EXPECT_EQ(u"72° F", view->temperature_for_test()->GetText());
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/glanceables/glanceables_welcome_label.cc b/ash/glanceables/glanceables_welcome_label.cc
index 0b9387d1..2ffcf7f 100644
--- a/ash/glanceables/glanceables_welcome_label.cc
+++ b/ash/glanceables/glanceables_welcome_label.cc
@@ -47,8 +47,7 @@
 
   const auto account_id = session_controller->GetActiveAccountId();
   if (account_id.empty()) {
-    // Prevents failures in `GlanceablesViewTest` and
-    // `GlanceablesControllerTest`.
+    // Prevents failures in `GlanceablesTest`.
     // TODO(crbug.com/1353119): Remove this after switching to `AshTestBase`.
     return u"";
   }
diff --git a/ash/glanceables/glanceables_welcome_label_unittest.cc b/ash/glanceables/glanceables_welcome_label_unittest.cc
index 6fb0cae..c44efa5 100644
--- a/ash/glanceables/glanceables_welcome_label_unittest.cc
+++ b/ash/glanceables/glanceables_welcome_label_unittest.cc
@@ -13,6 +13,10 @@
 namespace ash {
 namespace {
 
+// TODO(crbug.com/crbug.com/1353119): Move this to the GlanceablesTest suite
+// after that suite switches to AshTestBase. These tests only pass because this
+// suite is not enabling the Glanceables feature flag. When the flag is enabled
+// the simulated login causes a weather fetch, which crashes.
 class GlanceablesWelcomeLabelTest : public NoSessionAshTestBase {
  public:
   void SetUp() override {
diff --git a/ash/quick_pair/repository/fast_pair/device_id_map.cc b/ash/quick_pair/repository/fast_pair/device_id_map.cc
index dcbc116..717057c5 100644
--- a/ash/quick_pair/repository/fast_pair/device_id_map.cc
+++ b/ash/quick_pair/repository/fast_pair/device_id_map.cc
@@ -24,10 +24,8 @@
   registry->RegisterDictionaryPref(kDeviceIdMapPref);
 }
 
-DeviceIdMap::DeviceIdMap() {
-  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
-      &DeviceIdMap::OnGetAdapter, weak_ptr_factory_.GetWeakPtr()));
-}
+DeviceIdMap::DeviceIdMap(scoped_refptr<device::BluetoothAdapter> adapter)
+    : bluetooth_adapter_(adapter) {}
 
 DeviceIdMap::~DeviceIdMap() = default;
 
@@ -182,11 +180,6 @@
   }
 }
 
-void DeviceIdMap::OnGetAdapter(
-    scoped_refptr<device::BluetoothAdapter> adapter) {
-  bluetooth_adapter_ = adapter;
-}
-
 absl::optional<const std::string> DeviceIdMap::GetDeviceIdForAddress(
     const std::string& address) {
   if (!bluetooth_adapter_) {
diff --git a/ash/quick_pair/repository/fast_pair/device_id_map.h b/ash/quick_pair/repository/fast_pair/device_id_map.h
index 8a53ff9..595a9cf 100644
--- a/ash/quick_pair/repository/fast_pair/device_id_map.h
+++ b/ash/quick_pair/repository/fast_pair/device_id_map.h
@@ -32,7 +32,7 @@
   // Registers preferences used by this class in the provided |registry|.
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 
-  DeviceIdMap();
+  explicit DeviceIdMap(scoped_refptr<device::BluetoothAdapter> adapter);
   DeviceIdMap(const DeviceIdMap&) = delete;
   DeviceIdMap& operator=(const DeviceIdMap&) = delete;
   ~DeviceIdMap();
@@ -74,7 +74,6 @@
   // Loads device ID -> model ID records persisted in prefs to
   // device_id_to_model_id_.
   void LoadPersistedRecordsFromPrefs();
-  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
 
   // Used to lazily load saved records from prefs.
   bool loaded_records_from_prefs_ = false;
diff --git a/ash/quick_pair/repository/fast_pair/device_id_map_unittest.cc b/ash/quick_pair/repository/fast_pair/device_id_map_unittest.cc
index f20d7fb..c49a2d7b 100644
--- a/ash/quick_pair/repository/fast_pair/device_id_map_unittest.cc
+++ b/ash/quick_pair/repository/fast_pair/device_id_map_unittest.cc
@@ -64,7 +64,7 @@
     device_ = base::MakeRefCounted<Device>(kTestModelId, kTestBLEAddress,
                                            Protocol::kFastPairInitial);
     device_->set_classic_address(kTestClassicAddress);
-    device_id_map_ = std::make_unique<DeviceIdMap>();
+    device_id_map_ = std::make_unique<DeviceIdMap>(adapter_);
   }
 
  protected:
@@ -286,7 +286,7 @@
 
   // A new/restarted DeviceIdMap instance should load persisted ID records
   // from prefs.
-  DeviceIdMap new_device_id_map;
+  DeviceIdMap new_device_id_map(adapter_);
   absl::optional<const std::string> model_id =
       new_device_id_map.GetModelIdForDeviceId(kTestBLEDeviceId);
   EXPECT_TRUE(model_id);
diff --git a/ash/quick_pair/repository/fast_pair/saved_device_registry.cc b/ash/quick_pair/repository/fast_pair/saved_device_registry.cc
index 45d9c4c..31e62e2 100644
--- a/ash/quick_pair/repository/fast_pair/saved_device_registry.cc
+++ b/ash/quick_pair/repository/fast_pair/saved_device_registry.cc
@@ -7,9 +7,12 @@
 #include "ash/quick_pair/common/logging.h"
 #include "ash/quick_pair/common/quick_pair_browser_delegate.h"
 #include "base/base64.h"
+#include "base/containers/contains.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "device/bluetooth//bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
 
 namespace {
 
@@ -20,7 +23,10 @@
 namespace ash {
 namespace quick_pair {
 
-SavedDeviceRegistry::SavedDeviceRegistry() = default;
+SavedDeviceRegistry::SavedDeviceRegistry(
+    scoped_refptr<device::BluetoothAdapter> adapter)
+    : adapter_(adapter) {}
+
 SavedDeviceRegistry::~SavedDeviceRegistry() = default;
 
 void SavedDeviceRegistry::RegisterProfilePrefs(PrefRegistrySimple* registry) {
@@ -120,9 +126,15 @@
     return false;
   }
 
+  if (!has_updated_saved_devices_registry_) {
+    QP_LOG(INFO) << __func__
+                 << ": checking for changes to the registry by cross checking "
+                    "the adapter before continuing";
+    RemoveDevicesIfRemovedFromDifferentUser(pref_service);
+  }
+
   const base::Value::Dict& saved_devices =
       pref_service->GetValueDict(kFastPairSavedDevicesPref);
-
   std::string encoded_key = base::Base64Encode(account_key);
   for (const auto it : saved_devices) {
     const std::string* value = it.second.GetIfString();
@@ -134,5 +146,36 @@
   return false;
 }
 
+void SavedDeviceRegistry::RemoveDevicesIfRemovedFromDifferentUser(
+    PrefService* pref_service) {
+  DCHECK(pref_service);
+
+  // Set of currently paired devices, stored by Bluetooth address, used to
+  // cross reference the registry for any devices that need to be removed.
+  std::set<std::string> paired_devices;
+  for (device::BluetoothDevice* device : adapter_->GetDevices()) {
+    if (device->IsPaired())
+      paired_devices.insert(device->GetAddress());
+  }
+
+  // Iterate over the list of devices in the registry, and if there are any in
+  // the registry that are no longer paired to the adapter (determined by mac
+  // address), remove them from the registry.
+  const base::Value::Dict& saved_devices =
+      pref_service->GetValueDict(kFastPairSavedDevicesPref);
+  for (const auto it : saved_devices) {
+    const std::string& mac_address = it.first;
+    if (!base::Contains(paired_devices, mac_address)) {
+      DictionaryPrefUpdate update(pref_service, kFastPairSavedDevicesPref);
+      update->RemoveKey(it.first);
+      QP_LOG(VERBOSE) << __func__
+                      << ": removed device from registry at address= "
+                      << mac_address;
+    }
+  }
+
+  has_updated_saved_devices_registry_ = true;
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/repository/fast_pair/saved_device_registry.h b/ash/quick_pair/repository/fast_pair/saved_device_registry.h
index 7a2c438..4bcf287 100644
--- a/ash/quick_pair/repository/fast_pair/saved_device_registry.h
+++ b/ash/quick_pair/repository/fast_pair/saved_device_registry.h
@@ -9,10 +9,16 @@
 #include <string>
 #include <vector>
 
+#include "base/memory/scoped_refptr.h"
 #include "base/values.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class PrefRegistrySimple;
+class PrefService;
+
+namespace device {
+class BluetoothAdapter;
+}  // namespace device
 
 namespace ash {
 namespace quick_pair {
@@ -22,7 +28,7 @@
 // as a lookup key, and user prefs for storage.
 class SavedDeviceRegistry {
  public:
-  SavedDeviceRegistry();
+  explicit SavedDeviceRegistry(scoped_refptr<device::BluetoothAdapter> adapter);
   SavedDeviceRegistry(const SavedDeviceRegistry&) = delete;
   SavedDeviceRegistry& operator=(const SavedDeviceRegistry&) = delete;
   ~SavedDeviceRegistry();
@@ -49,6 +55,25 @@
 
   // Checks if the account key is in the registry.
   bool IsAccountKeySavedToRegistry(const std::vector<uint8_t>& account_key);
+
+ private:
+  // Cross check the list of devices in the registry with the devices paired
+  // to the BluetoothAdapter to see if any devices need to be removed from the
+  // registry if they are no longer paired to the adapter. This can happen if
+  // a primary user pairs and saves a device to their account, logs out, then a
+  // secondary user logs in, forgets the device from the Bluetooth pairing
+  // menu. When the primary user logs back in, we need to reflect the device is
+  // no longer paired locally in the registry.
+  void RemoveDevicesIfRemovedFromDifferentUser(PrefService* pref_service);
+
+  // Flags cross checking the registry with the devices paired to the adapter
+  // to see if any devices need to be removed from the registry (see
+  // |RemoveDevicesIfRemovedFromDifferentUser|). This only needs to happen once
+  // per session, which is why it is flagged. Everything else following during
+  // the user session will be immediately reflected here.
+  bool has_updated_saved_devices_registry_ = false;
+
+  scoped_refptr<device::BluetoothAdapter> adapter_;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/repository/fast_pair/saved_device_registry_unittest.cc b/ash/quick_pair/repository/fast_pair/saved_device_registry_unittest.cc
index 07729761..237fa76 100644
--- a/ash/quick_pair/repository/fast_pair/saved_device_registry_unittest.cc
+++ b/ash/quick_pair/repository/fast_pair/saved_device_registry_unittest.cc
@@ -9,6 +9,8 @@
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/prefs/testing_pref_service.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -31,6 +33,26 @@
 
 class SavedDeviceRegistryTest : public testing::Test {
  public:
+  SavedDeviceRegistryTest()
+      : adapter_(base::MakeRefCounted<
+                 testing::NiceMock<device::MockBluetoothAdapter>>()),
+        bluetooth_device1_(/*adapter=*/adapter_.get(),
+                           /*bluetooth_class=*/0,
+                           /*name=*/"Test name 1",
+                           /*address=*/kFirstSavedMacAddress,
+                           /*initially_paired=*/true,
+                           /*connected=*/true),
+        bluetooth_device2_(/*adapter=*/adapter_.get(),
+                           /*bluetooth_class=*/0,
+                           /*name=*/"Test name 1",
+                           /*address=*/kSecondSavedMacAddress,
+                           /*initially_paired=*/true,
+                           /*connected=*/true) {
+    ON_CALL(bluetooth_device1_, IsPaired).WillByDefault(testing::Return(true));
+    ON_CALL(bluetooth_device2_, IsPaired).WillByDefault(testing::Return(true));
+    ON_CALL(*adapter_, GetDevices).WillByDefault(testing::Return(device_list_));
+  }
+
   void SetUp() override {
     pref_service_ = std::make_unique<TestingPrefServiceSimple>();
     SavedDeviceRegistry::RegisterProfilePrefs(pref_service_->registry());
@@ -39,10 +61,17 @@
     ON_CALL(*browser_delegate_, GetActivePrefService())
         .WillByDefault(testing::Return(pref_service_.get()));
 
-    saved_device_registry_ = std::make_unique<SavedDeviceRegistry>();
+    saved_device_registry_ =
+        std::make_unique<SavedDeviceRegistry>(adapter_);
+    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
   }
 
  protected:
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
+  testing::NiceMock<device::MockBluetoothDevice> bluetooth_device1_;
+  testing::NiceMock<device::MockBluetoothDevice> bluetooth_device2_;
+  device::BluetoothAdapter::ConstDeviceList device_list_{&bluetooth_device1_,
+                                                         &bluetooth_device2_};
   std::unique_ptr<MockQuickPairBrowserDelegate> browser_delegate_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
   std::unique_ptr<SavedDeviceRegistry> saved_device_registry_;
@@ -165,5 +194,30 @@
   EXPECT_FALSE(saved_device_registry_->DeleteAccountKey(kAccountKey1));
 }
 
+TEST_F(SavedDeviceRegistryTest, IsAccountKeySavedToRegistry_DeviceRemoved) {
+  // Simulate a user saving devices to their account.
+  saved_device_registry_->SaveAccountKey(kFirstSavedMacAddress, kAccountKey1);
+  saved_device_registry_->SaveAccountKey(kSecondSavedMacAddress, kAccountKey2);
+
+  // Destroy the object to simulate a user's session ending.
+  saved_device_registry_.reset();
+
+  // Simulate a device being removed from the bluetooth adapter. This is meant
+  // to replicate the circumstances of a second user forgetting a device.
+  device::BluetoothAdapter::ConstDeviceList device_list{&bluetooth_device2_};
+  ON_CALL(*adapter_, GetDevices()).WillByDefault(testing::Return(device_list));
+
+  // Create a new object to simulate a new user session.
+  saved_device_registry_ =
+      std::make_unique<SavedDeviceRegistry>(adapter_.get());
+
+  // We expect |kAccountKey1| to be removed from the registry since it is no
+  // longer paired.
+  EXPECT_FALSE(
+      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey1));
+  EXPECT_TRUE(
+      saved_device_registry_->IsAccountKeySavedToRegistry(kAccountKey2));
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl.cc b/ash/quick_pair/repository/fast_pair_repository_impl.cc
index 2adf196..f7dd2d6 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl.cc
+++ b/ash/quick_pair/repository/fast_pair_repository_impl.cc
@@ -22,21 +22,30 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/bluetooth_device.h"
 
 namespace ash {
 namespace quick_pair {
 
 FastPairRepositoryImpl::FastPairRepositoryImpl()
-    : FastPairRepository(),
-      device_metadata_fetcher_(std::make_unique<DeviceMetadataFetcher>()),
+    : device_metadata_fetcher_(std::make_unique<DeviceMetadataFetcher>()),
       footprints_fetcher_(std::make_unique<FootprintsFetcherImpl>()),
       image_decoder_(std::make_unique<FastPairImageDecoderImpl>()),
-      device_id_map_(std::make_unique<DeviceIdMap>()),
       device_image_store_(
           std::make_unique<DeviceImageStore>(image_decoder_.get())),
-      saved_device_registry_(std::make_unique<SavedDeviceRegistry>()),
-      footprints_last_updated_(base::Time::UnixEpoch()) {}
+      footprints_last_updated_(base::Time::UnixEpoch()) {
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &FastPairRepositoryImpl::OnGetAdapter, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FastPairRepositoryImpl::OnGetAdapter(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  adapter_ = adapter;
+  device_id_map_ = std::make_unique<DeviceIdMap>(adapter_);
+  saved_device_registry_ = std::make_unique<SavedDeviceRegistry>(adapter_);
+}
 
 FastPairRepositoryImpl::FastPairRepositoryImpl(
     std::unique_ptr<DeviceMetadataFetcher> device_metadata_fetcher,
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl.h b/ash/quick_pair/repository/fast_pair_repository_impl.h
index 3e7235a3..da52944 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl.h
+++ b/ash/quick_pair/repository/fast_pair_repository_impl.h
@@ -10,6 +10,7 @@
 #include "ash/quick_pair/repository/fast_pair_repository.h"
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/time/time.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -20,6 +21,7 @@
 }  // namespace chromeos
 
 namespace device {
+class BluetoothAdapter;
 class BluetoothDevice;
 }  // namespace device
 
@@ -132,6 +134,12 @@
       GetSavedDevicesCallback callback,
       absl::optional<nearby::fastpair::UserReadDevicesResponse> user_devices);
 
+  // Internal method called by BluetoothAdapterFactory to provide the adapter
+  // object.
+  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+
+  scoped_refptr<device::BluetoothAdapter> adapter_;
+
   std::unique_ptr<DeviceMetadataFetcher> device_metadata_fetcher_;
   std::unique_ptr<FootprintsFetcher> footprints_fetcher_;
   std::unique_ptr<FastPairImageDecoder> image_decoder_;
diff --git a/ash/quick_pair/repository/fast_pair_repository_impl_unittest.cc b/ash/quick_pair/repository/fast_pair_repository_impl_unittest.cc
index fd61a2e0..4b3da373 100644
--- a/ash/quick_pair/repository/fast_pair_repository_impl_unittest.cc
+++ b/ash/quick_pair/repository/fast_pair_repository_impl_unittest.cc
@@ -41,9 +41,8 @@
 constexpr char kInvalidModelId[] = "666";
 constexpr char kTestModelId[] = "test_model_id";
 constexpr char kTestDeviceId[] = "test_ble_device_id";
-constexpr char kTestBLEAddress[] = "test_ble_address";
-constexpr char kTestClassicAddress[] = "test_classic_address";
-constexpr char kFirstSavedMacAddress[] = "00:11:22:33:44";
+constexpr char kTestBLEAddress[] = "00:11:22:33:45";
+constexpr char kTestClassicAddress[] = "00:11:22:33:44";
 const std::vector<uint8_t> kAccountKey1{0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
                                         0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB,
                                         0xCC, 0xDD, 0xEE, 0xFF};
@@ -85,6 +84,11 @@
         .WillByDefault(Return(kTestDeviceId));
     ON_CALL(classic_bluetooth_device_, GetIdentifier)
         .WillByDefault(Return(kTestDeviceId));
+    ON_CALL(ble_bluetooth_device_, IsPaired)
+        .WillByDefault(testing::Return(true));
+    ON_CALL(classic_bluetooth_device_, IsPaired)
+        .WillByDefault(testing::Return(true));
+    ON_CALL(*adapter_, GetDevices).WillByDefault(testing::Return(device_list_));
     ON_CALL(*adapter_, GetDevice(kTestBLEAddress))
         .WillByDefault(Return(&ble_bluetooth_device_));
     ON_CALL(*adapter_, GetDevice(kTestClassicAddress))
@@ -114,14 +118,15 @@
     ON_CALL(*image_decoder_, DecodeImage(_, _, _))
         .WillByDefault(RunOnceCallback<2>(test_image_));
 
-    auto device_id_map = std::make_unique<DeviceIdMap>();
+    auto device_id_map = std::make_unique<DeviceIdMap>(adapter_.get());
     device_id_map_ = device_id_map.get();
 
     auto device_image_store =
         std::make_unique<DeviceImageStore>(image_decoder_);
     device_image_store_ = device_image_store.get();
 
-    auto saved_device_registry = std::make_unique<SavedDeviceRegistry>();
+    auto saved_device_registry =
+        std::make_unique<SavedDeviceRegistry>(adapter_.get());
     saved_device_registry_ = saved_device_registry.get();
 
     fast_pair_repository_ = std::make_unique<FastPairRepositoryImpl>(
@@ -180,6 +185,8 @@
   scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
   testing::NiceMock<device::MockBluetoothDevice> ble_bluetooth_device_;
   testing::NiceMock<device::MockBluetoothDevice> classic_bluetooth_device_;
+  device::BluetoothAdapter::ConstDeviceList device_list_{
+      &ble_bluetooth_device_, &classic_bluetooth_device_};
   scoped_refptr<Device> device_;
   gfx::Image test_image_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
@@ -594,7 +601,7 @@
 }
 
 TEST_F(FastPairRepositoryImplTest, IsAccountKeyPairedLocally) {
-  saved_device_registry_->SaveAccountKey(kFirstSavedMacAddress, kAccountKey1);
+  saved_device_registry_->SaveAccountKey(kTestClassicAddress, kAccountKey1);
   EXPECT_TRUE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey1));
   EXPECT_FALSE(fast_pair_repository_->IsAccountKeyPairedLocally(kAccountKey2));
 }
diff --git a/ash/webui/shortcut_customization_ui/resources/BUILD.gn b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
index 40e3af2..6123054 100644
--- a/ash/webui/shortcut_customization_ui/resources/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
@@ -23,12 +23,12 @@
 ]
 
 web_component_files = [
-  "accelerator_edit_dialog.js",
+  "accelerator_edit_dialog.ts",
   "accelerator_edit_view.js",
   "accelerator_row.js",
   "accelerator_subsection.js",
   "accelerator_view.js",
-  "input_key.js",
+  "input_key.ts",
   "shortcut_customization_app.js",
   "shortcut_input.js",
   "shortcuts_page.js",
@@ -36,7 +36,11 @@
 
 html_files = []
 foreach(f, web_component_files) {
-  html_files += [ string_replace(f, ".js", ".html") ]
+  if (get_path_info(f, "extension") == "ts") {
+    html_files += [ string_replace(f, ".ts", ".html") ]
+  } else {
+    html_files += [ string_replace(f, ".js", ".html") ]
+  }
 }
 
 icons_html_files = [ "icons.html" ]
diff --git a/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.js b/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.js
deleted file mode 100644
index 59cdcc9..0000000
--- a/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import './accelerator_edit_view.js';
-import './shortcut_customization_shared.css.js';
-import 'chrome://resources/cr_elements/cr_button/cr_button.js';
-import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
-import 'chrome://resources/cr_elements/cr_input/cr_input.js';
-
-import {flush, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {getTemplate} from './accelerator_edit_dialog.html.js';
-import {ViewState} from './accelerator_view.js';
-import {AcceleratorInfo, AcceleratorSource} from './shortcut_types.js';
-
-/**
- * @fileoverview
- * 'accelerator-edit-dialog' is a dialog that displays the accelerators for
- * a given shortcut. Allows users to edit the accelerators.
- * TODO(jimmyxgong): Implement editing accelerators.
- */
-export class AcceleratorEditDialogElement extends PolymerElement {
-  static get is() {
-    return 'accelerator-edit-dialog';
-  }
-
-  static get properties() {
-    return {
-      description: {
-        type: String,
-        value: '',
-      },
-
-      /** @type {!Array<!AcceleratorInfo>} */
-      acceleratorInfos: {
-        type: Array,
-        value: () => {},
-      },
-
-      /** @private */
-      pendingNewAcceleratorState_: {
-        type: Number,
-        value: ViewState.VIEW,
-      },
-
-      action: {
-        type: Number,
-        value: 0,
-      },
-
-      /** @type {!AcceleratorSource} */
-      source: {
-        type: Number,
-        value: 0,
-      },
-
-      /** @protected */
-      isAcceleratorCapturing_: {
-        type: Boolean,
-        value: false,
-      },
-    };
-  }
-
-  /** @override */
-  constructor() {
-    super();
-
-    /**
-     * Event callback for 'accelerator-capturing-started'.
-     * @private {!Function}
-     */
-    this.onAcceleratorCapturingStarted_ = () => {
-      this.isAcceleratorCapturing_ = true;
-    };
-
-    /**
-     * Event callback for 'accelerator-capturing-ended'.
-     * @private {!Function}
-     */
-    this.onAcceleratorCapturingEnded_ = () => {
-      this.isAcceleratorCapturing_ = false;
-    };
-  }
-
-  /** @override */
-  connectedCallback() {
-    super.connectedCallback();
-    this.$.editDialog.showModal();
-
-    window.addEventListener(
-        'accelerator-capturing-started', this.onAcceleratorCapturingStarted_);
-    window.addEventListener(
-        'accelerator-capturing-ended', this.onAcceleratorCapturingEnded_);
-  }
-
-  /** @override */
-  disconnectedCallback() {
-    super.disconnectedCallback();
-    window.removeEventListener(
-        'accelerator-capturing-started', this.onAcceleratorCapturingStarted_);
-    window.removeEventListener(
-        'accelerator-capturing-ended', this.onAcceleratorCapturingEnded_);
-  }
-
-  /**
-   * @param {!Array<AcceleratorInfo>} updatedAccels
-   */
-  updateDialogAccelerators(updatedAccels) {
-    this.set('acceleratorInfos', []);
-    this.shadowRoot.querySelector('#viewList').render();
-    this.acceleratorInfos = updatedAccels;
-  }
-
-  /** @protected */
-  onDoneButtonClicked_() {
-    this.$.editDialog.close();
-  }
-
-  /** @protected */
-  onDialogClose_() {
-    this.dispatchEvent(new CustomEvent('edit-dialog-closed',
-        {bubbles: true, composed: true}));
-  }
-
-  /** @protected */
-  onAddAcceleratorClicked_() {
-    this.pendingNewAcceleratorState_ = ViewState.ADD;
-
-    // Flush the dom so that the AcceleratorEditView is ready to be focused.
-    flush();
-    const editView = this.$.editDialog.querySelector('#pendingAccelerator');
-    const accelItem = editView.shadowRoot.querySelector('#acceleratorItem');
-    accelItem.shadowRoot.querySelector('#container').focus();
-  }
-
-  /**
-   * @return {boolean}
-   * @protected
-   */
-  showAddButton_() {
-    // If the state is VIEW, no new pending accelerators are being added.
-    return this.pendingNewAcceleratorState_ === ViewState.VIEW;
-  }
-
-  /** @protected */
-  onRestoreDefaultButtonClicked_() {
-    // TODO(jimmyxgong): Implement this function.
-  }
-
-  static get template() {
-    return getTemplate();
-  }
-}
-
-customElements.define(AcceleratorEditDialogElement.is,
-                      AcceleratorEditDialogElement);
diff --git a/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.ts b/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.ts
new file mode 100644
index 0000000..891e82e3
--- /dev/null
+++ b/ash/webui/shortcut_customization_ui/resources/accelerator_edit_dialog.ts
@@ -0,0 +1,177 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './accelerator_edit_view.js';
+import './shortcut_customization_shared.css.js';
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import 'chrome://resources/cr_elements/cr_input/cr_input.js';
+
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {DomRepeat, flush, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './accelerator_edit_dialog.html.js';
+import {ViewState} from './accelerator_view.js';
+import {AcceleratorInfo, AcceleratorSource} from './shortcut_types.js';
+
+export interface AcceleratorEditDialogElement {
+  $: {
+    editDialog: CrDialogElement,
+  };
+}
+
+declare global {
+  interface HTMLElementEventMap {
+    'accelerator-capturing-started': CustomEvent<void>;
+    'accelerator-capturing-ended': CustomEvent<void>;
+  }
+}
+
+/**
+ * @fileoverview
+ * 'accelerator-edit-dialog' is a dialog that displays the accelerators for
+ * a given shortcut. Allows users to edit the accelerators.
+ * TODO(jimmyxgong): Implement editing accelerators.
+ */
+export class AcceleratorEditDialogElement extends PolymerElement {
+  static get is() {
+    return 'accelerator-edit-dialog';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      description: {
+        type: String,
+        value: '',
+      },
+
+      acceleratorInfos: {
+        type: Array,
+        value: () => {},
+      },
+
+      pendingNewAcceleratorState_: {
+        type: Number,
+        value: ViewState.VIEW,
+      },
+
+      action: {
+        type: Number,
+        value: 0,
+      },
+
+      source: {
+        type: Number,
+        value: 0,
+      },
+
+      isAcceleratorCapturing_: {
+        type: Boolean,
+        value: false,
+      },
+    };
+  }
+
+  description: string;
+  acceleratorInfos: AcceleratorInfo[];
+  action: number;
+  source: AcceleratorSource;
+  protected isAcceleratorCapturing_: boolean;
+  private pendingNewAcceleratorState_: number;
+  private acceleratorCapturingStartedListener_ = () =>
+      this.onAcceleratorCapturingStarted_();
+  private acceleratorCapturingEndedListener_ = () =>
+      this.onAcceleratorCapturingEnded_();
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.$.editDialog.showModal();
+
+    window.addEventListener(
+        'accelerator-capturing-started',
+        this.acceleratorCapturingStartedListener_);
+    window.addEventListener(
+        'accelerator-capturing-ended', this.acceleratorCapturingEndedListener_);
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    window.removeEventListener(
+        'accelerator-capturing-started',
+        this.acceleratorCapturingStartedListener_);
+    window.removeEventListener(
+        'accelerator-capturing-ended', this.acceleratorCapturingEndedListener_);
+  }
+
+  private getViewList_(): DomRepeat {
+    const viewList = this.shadowRoot!.querySelector('#viewList') as DomRepeat;
+    assert(viewList);
+    return viewList;
+  }
+
+  updateDialogAccelerators(updatedAccels: AcceleratorInfo[]) {
+    this.set('acceleratorInfos', []);
+    this.getViewList_().render();
+    this.acceleratorInfos = updatedAccels;
+  }
+
+  protected onDoneButtonClicked_() {
+    this.$.editDialog.close();
+  }
+
+  protected onDialogClose_() {
+    this.dispatchEvent(
+        new CustomEvent('edit-dialog-closed', {bubbles: true, composed: true}));
+  }
+
+  private onAcceleratorCapturingStarted_() {
+    this.isAcceleratorCapturing_ = true;
+  }
+
+  private onAcceleratorCapturingEnded_() {
+    this.isAcceleratorCapturing_ = false;
+  }
+
+  private focusAcceleratorItemContainer_() {
+    const editView = this.$.editDialog.querySelector('#pendingAccelerator');
+    assert(editView);
+    const accelItem = editView.shadowRoot!.querySelector('#acceleratorItem');
+    assert(accelItem);
+    const container =
+        accelItem.shadowRoot!.querySelector<HTMLElement>('#container');
+    assert(container);
+    container!.focus();
+  }
+
+  protected onAddAcceleratorClicked_() {
+    this.pendingNewAcceleratorState_ = ViewState.ADD;
+
+    // Flush the dom so that the AcceleratorEditView is ready to be focused.
+    flush();
+    this.focusAcceleratorItemContainer_();
+  }
+
+  protected showAddButton_(): boolean {
+    // If the state is VIEW, no new pending accelerators are being added.
+    return this.pendingNewAcceleratorState_ === ViewState.VIEW;
+  }
+
+  protected onRestoreDefaultButtonClicked_() {
+    // TODO(jimmyxgong): Implement this function.
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'accelerator-edit-dialog': AcceleratorEditDialogElement;
+  }
+}
+
+customElements.define(
+    AcceleratorEditDialogElement.is, AcceleratorEditDialogElement);
diff --git a/ash/webui/shortcut_customization_ui/resources/input_key.js b/ash/webui/shortcut_customization_ui/resources/input_key.ts
similarity index 84%
rename from ash/webui/shortcut_customization_ui/resources/input_key.js
rename to ash/webui/shortcut_customization_ui/resources/input_key.ts
index f5aa434..11a0874 100644
--- a/ash/webui/shortcut_customization_ui/resources/input_key.js
+++ b/ash/webui/shortcut_customization_ui/resources/input_key.ts
@@ -11,13 +11,12 @@
 
 /**
  * Refers to the state of an 'input-key' item.
- * @enum {string}
  */
-export const KeyInputState = {
-  NOT_SELECTED: 'not-selected',
-  MODIFIER_SELECTED: 'modifier-selected',
-  ALPHANUMERIC_SELECTED: 'alpha-numeric-selected',
-};
+export enum KeyInputState {
+  NOT_SELECTED = 'not-selected',
+  MODIFIER_SELECTED = 'modifier-selected',
+  ALPHANUMERIC_SELECTED = 'alpha-numeric-selected',
+}
 
 /**
  * @fileoverview
@@ -44,6 +43,9 @@
     };
   }
 
+  key: string;
+  keyState: KeyInputState;
+
   static get template() {
     return getTemplate();
   }
diff --git a/ash/wm/desks/templates/saved_desk_grid_view.cc b/ash/wm/desks/templates/saved_desk_grid_view.cc
index 4cd8e80..f75d991 100644
--- a/ash/wm/desks/templates/saved_desk_grid_view.cc
+++ b/ash/wm/desks/templates/saved_desk_grid_view.cc
@@ -181,7 +181,7 @@
     AnimateGridItems(new_grid_items);
 }
 
-void SavedDeskGridView::DeleteTemplates(const std::vector<std::string>& uuids,
+void SavedDeskGridView::DeleteTemplates(const std::vector<base::GUID>& uuids,
                                         bool delete_animation) {
   OverviewHighlightController* highlight_controller =
       Shell::Get()
@@ -190,12 +190,11 @@
           ->highlight_controller();
   DCHECK(highlight_controller);
 
-  for (const std::string& uuid : uuids) {
-    auto iter =
-        std::find_if(grid_items_.begin(), grid_items_.end(),
-                     [uuid](SavedDeskItemView* grid_item) {
-                       return uuid == grid_item->uuid().AsLowercaseString();
-                     });
+  for (const base::GUID& uuid : uuids) {
+    auto iter = std::find_if(grid_items_.begin(), grid_items_.end(),
+                             [&uuid](SavedDeskItemView* grid_item) {
+                               return uuid == grid_item->uuid();
+                             });
 
     if (iter == grid_items_.end())
       continue;
diff --git a/ash/wm/desks/templates/saved_desk_grid_view.h b/ash/wm/desks/templates/saved_desk_grid_view.h
index c3f0abc..1bab0965 100644
--- a/ash/wm/desks/templates/saved_desk_grid_view.h
+++ b/ash/wm/desks/templates/saved_desk_grid_view.h
@@ -61,7 +61,7 @@
   // shuffle `grid_items_` to their final positions. If `delete_animation` is
   // false, then deleted items will simply disappear (shuffled items will still
   // animate).
-  void DeleteTemplates(const std::vector<std::string>& uuids,
+  void DeleteTemplates(const std::vector<base::GUID>& uuids,
                        bool delete_animation);
 
   // Returns true if a template name is being modified using an item view's
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index d1361a5..c0a13da 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -339,12 +339,12 @@
   saved_desk_util::GetSavedDeskDialogController()->ShowReplaceDialog(
       root_window, name_view_->GetText(), type,
       base::BindOnce(&SavedDeskItemView::ReplaceTemplate,
-                     weak_ptr_factory_.GetWeakPtr(), uuid.AsLowercaseString()),
+                     weak_ptr_factory_.GetWeakPtr(), uuid),
       base::BindOnce(&SavedDeskItemView::RevertTemplateName,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void SavedDeskItemView::ReplaceTemplate(const std::string& uuid) {
+void SavedDeskItemView::ReplaceTemplate(const base::GUID& uuid) {
   // Make sure we delete the template we are replacing first, so that we don't
   // get template name collisions. Passing `nullopt` as `record_for_type` since
   // we only record the delete operation when the user specifically deletes an
@@ -707,8 +707,8 @@
 }
 
 void SavedDeskItemView::OnDeleteTemplate() {
-  saved_desk_util::GetSavedDeskPresenter()->DeleteEntry(
-      desk_template_->uuid().AsLowercaseString(), desk_template_->type());
+  saved_desk_util::GetSavedDeskPresenter()->DeleteEntry(desk_template_->uuid(),
+                                                        desk_template_->type());
 }
 
 void SavedDeskItemView::OnDeleteButtonPressed() {
diff --git a/ash/wm/desks/templates/saved_desk_item_view.h b/ash/wm/desks/templates/saved_desk_item_view.h
index b62d701..53fe80b 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.h
+++ b/ash/wm/desks/templates/saved_desk_item_view.h
@@ -98,7 +98,7 @@
                               const base::GUID& uuid);
   // Rename current saved desk with new name, delete old saved desk with same
   // name by uuid. Used for callback functions for Replace Dialog.
-  void ReplaceTemplate(const std::string& uuid);
+  void ReplaceTemplate(const base::GUID& uuid);
   void RevertTemplateName();
 
   // This allows us to update an existing template view. Currently, this
diff --git a/ash/wm/desks/templates/saved_desk_library_view.cc b/ash/wm/desks/templates/saved_desk_library_view.cc
index 97c545e..5c420e4a 100644
--- a/ash/wm/desks/templates/saved_desk_library_view.cc
+++ b/ash/wm/desks/templates/saved_desk_library_view.cc
@@ -374,9 +374,8 @@
   Layout();
 }
 
-void SavedDeskLibraryView::DeleteTemplates(
-    const std::vector<std::string>& uuids,
-    bool delete_animation) {
+void SavedDeskLibraryView::DeleteTemplates(const std::vector<base::GUID>& uuids,
+                                           bool delete_animation) {
   if (desk_template_grid_view_)
     desk_template_grid_view_->DeleteTemplates(uuids, delete_animation);
   if (save_and_recall_grid_view_)
@@ -432,7 +431,7 @@
       .SetOpacity(item_layer, 0.0f);
 
   // Delete the existing saved desk item without animation.
-  DeleteTemplates({uuid.AsLowercaseString()}, /*delete_animation=*/false);
+  DeleteTemplates({uuid}, /*delete_animation=*/false);
 }
 
 void SavedDeskLibraryView::OnFeedbackButtonPressed() {
diff --git a/ash/wm/desks/templates/saved_desk_library_view.h b/ash/wm/desks/templates/saved_desk_library_view.h
index e1c3f37..16b8d376 100644
--- a/ash/wm/desks/templates/saved_desk_library_view.h
+++ b/ash/wm/desks/templates/saved_desk_library_view.h
@@ -64,7 +64,7 @@
 
   // Delete all templates identified by `uuids`. If `delete_animation` is false,
   // then the respective item views will just disappear instead of fading out.
-  void DeleteTemplates(const std::vector<std::string>& uuids,
+  void DeleteTemplates(const std::vector<base::GUID>& uuids,
                        bool delete_animation);
 
   // This performs the launch animation for Save & Recall. The `DeskItemView`
diff --git a/ash/wm/desks/templates/saved_desk_presenter.cc b/ash/wm/desks/templates/saved_desk_presenter.cc
index cebb30f..920533f 100644
--- a/ash/wm/desks/templates/saved_desk_presenter.cc
+++ b/ash/wm/desks/templates/saved_desk_presenter.cc
@@ -346,7 +346,7 @@
 }
 
 void SavedDeskPresenter::DeleteEntry(
-    const std::string& uuid,
+    const base::GUID& uuid,
     absl::optional<DeskTemplateType> record_for_type) {
   weak_ptr_factory_.InvalidateWeakPtrs();
   GetDeskModel()->DeleteEntry(
@@ -450,7 +450,16 @@
 
 void SavedDeskPresenter::EntriesRemovedRemotely(
     const std::vector<std::string>& uuids) {
-  RemoveUIEntries(uuids);
+  // TODO(crbug.com/1352667): We want the backend to provide this as
+  // vector<base::GUID>. Until it does, we'll convert manually here.
+  std::vector<base::GUID> typed_uuids;
+  for (const std::string& uuid_str : uuids) {
+    base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
+    if (uuid.is_valid())
+      typed_uuids.push_back(uuid);
+  }
+
+  RemoveUIEntries(typed_uuids);
 }
 
 void SavedDeskPresenter::GetAllEntries(const base::GUID& item_to_focus,
@@ -489,7 +498,7 @@
 }
 
 void SavedDeskPresenter::OnDeleteEntry(
-    const std::string& uuid,
+    const base::GUID& uuid,
     absl::optional<DeskTemplateType> record_for_type,
     desks_storage::DeskModel::DeleteEntryStatus status) {
   if (status != desks_storage::DeskModel::DeleteEntryStatus::kOk)
@@ -518,7 +527,7 @@
   // store the template ID here since we're about to move the desk template.
   const auto saved_desk_type = saved_desk->type();
   const auto saved_desk_creation_time = saved_desk->created_time();
-  const std::string uuid = saved_desk->uuid().AsLowercaseString();
+  const base::GUID uuid = saved_desk->uuid();
 
   auto* overview_controller = Shell::Get()->overview_controller();
   if (saved_desk_type == DeskTemplateType::kSaveAndRecall) {
@@ -533,7 +542,7 @@
       DCHECK(mini_view);
 
       SavedDeskLibraryView* library = overview_grid->GetSavedDeskLibraryView();
-      library->AnimateDeskLaunch(saved_desk->uuid(), mini_view);
+      library->AnimateDeskLaunch(uuid, mini_view);
     }
   }
 
@@ -696,8 +705,7 @@
     std::move(on_update_ui_closure_for_testing_).Run();
 }
 
-void SavedDeskPresenter::RemoveUIEntries(
-    const std::vector<std::string>& uuids) {
+void SavedDeskPresenter::RemoveUIEntries(const std::vector<base::GUID>& uuids) {
   if (uuids.empty())
     return;
 
diff --git a/ash/wm/desks/templates/saved_desk_presenter.h b/ash/wm/desks/templates/saved_desk_presenter.h
index 7c7e3f0c..c4e7070 100644
--- a/ash/wm/desks/templates/saved_desk_presenter.h
+++ b/ash/wm/desks/templates/saved_desk_presenter.h
@@ -62,7 +62,7 @@
 
   // Calls the DeskModel to delete the saved desk with the provided `uuid`. Will
   // record histogram if `record_for_type` is specified.
-  void DeleteEntry(const std::string& uuid,
+  void DeleteEntry(const base::GUID& uuid,
                    absl::optional<DeskTemplateType> record_for_type);
 
   // Launches `saved_desk` into a new desk. `delay` is the time between each app
@@ -104,7 +104,7 @@
 
   // Callback after deleting an entry. Will then call `RemoveUIEntries` to
   // update the UI by removing the deleted saved desk.
-  void OnDeleteEntry(const std::string& uuid,
+  void OnDeleteEntry(const base::GUID& uuid,
                      absl::optional<DeskTemplateType> record_for_type,
                      desks_storage::DeskModel::DeleteEntryStatus status);
 
@@ -120,7 +120,7 @@
   // Helper functions for updating the UI.
   void AddOrUpdateUIEntries(
       const std::vector<const DeskTemplate*>& new_entries);
-  void RemoveUIEntries(const std::vector<std::string>& uuids);
+  void RemoveUIEntries(const std::vector<base::GUID>& uuids);
 
   // Returns a copy of a duplicated name to be stored.  This function works by
   // taking the name to be duplicated and adding a "(1)" to it. If the name
diff --git a/ash/wm/desks/templates/saved_desk_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
index 67ee450..0a6ecf5 100644
--- a/ash/wm/desks/templates/saved_desk_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -177,11 +177,10 @@
   void DeleteEntry(const base::GUID& uuid) {
     base::RunLoop loop;
     desk_model()->DeleteEntry(
-        uuid.AsLowercaseString(),
-        base::BindLambdaForTesting(
-            [&](desks_storage::DeskModel::DeleteEntryStatus status) {
-              loop.Quit();
-            }));
+        uuid, base::BindLambdaForTesting(
+                  [&](desks_storage::DeskModel::DeleteEntryStatus status) {
+                    loop.Quit();
+                  }));
     loop.Run();
   }
 
@@ -3217,7 +3216,7 @@
   auto* dialog_controller = saved_desk_util::GetSavedDeskDialogController();
   auto callback = base::BindLambdaForTesting([&]() {
     item_view->name_view()->SetText(base::UTF8ToUTF16(name_1));
-    item_view->ReplaceTemplate(uuid_1.AsLowercaseString());
+    item_view->ReplaceTemplate(uuid_1);
   });
 
   dialog_controller->ShowReplaceDialog(
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
index 05d3589..2c0a988 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
@@ -8,6 +8,7 @@
 #include "base/callback_forward.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_view.h"
 #include "ui/aura/window.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/background.h"
 #include "ui/views/layout/box_layout.h"
 
@@ -25,6 +26,8 @@
 // The contents view of the multitask menu.
 class TabletModeMultitaskMenuView : public views::View {
  public:
+  METADATA_HEADER(TabletModeMultitaskMenuView);
+
   TabletModeMultitaskMenuView(aura::Window* window,
                               base::RepeatingClosure hide_menu) {
     SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
@@ -50,6 +53,9 @@
   ~TabletModeMultitaskMenuView() override = default;
 };
 
+BEGIN_METADATA(TabletModeMultitaskMenuView, View)
+END_METADATA
+
 TabletModeMultitaskMenu::TabletModeMultitaskMenu(aura::Window* window)
     : window_(window) {
   DCHECK(window_);
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
index 28307ba..39e6bab 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
@@ -49,7 +49,8 @@
 void TabletModeMultitaskMenuEventHandler::OnGestureEvent(
     ui::GestureEvent* event) {
   aura::Window* active_window = window_util::GetActiveWindow();
-  if (!active_window || active_window != event->target() ||
+  if (!active_window ||
+      !active_window->Contains(static_cast<aura::Window*>(event->target())) ||
       !WindowState::Get(active_window)->CanResize()) {
     return;
   }
diff --git a/build/android/pylib/local/emulator/avd.py b/build/android/pylib/local/emulator/avd.py
index 76078fc..ae69c591 100644
--- a/build/android/pylib/local/emulator/avd.py
+++ b/build/android/pylib/local/emulator/avd.py
@@ -16,6 +16,7 @@
 
 from google.protobuf import text_format  # pylint: disable=import-error
 
+from devil.android import apk_helper
 from devil.android import device_utils
 from devil.android import settings
 from devil.android.sdk import adb_wrapper
@@ -120,9 +121,13 @@
         curr_min_sdk_version = entry['min_sdk']
 
     if not min_sdk_found:
-      logging.error('No suitable apk file found that suits the minimum sdk.')
+      logging.error('No suitable apk file found that suits the minimum sdk %d.',
+                    min_sdk)
       return None
 
+    logging.info('Found apk file for mininum sdk %d: %r with version %r',
+                 min_sdk, min_sdk_found['file_name'],
+                 min_sdk_found['version_name'])
     return os.path.join(apk_dir, min_sdk_found['file_name'])
 
 
@@ -378,11 +383,14 @@
 
       if not additional_apks:
         additional_apks = []
-      for apk_package in self._config.additional_apk:
-        apk_dir = os.path.join(COMMON_CIPD_ROOT, apk_package.dest_path)
-        for f in os.listdir(apk_dir):
-          if os.path.isfile(f) and f.endswith('.apk'):
-            additional_apks.append(os.path.join(apk_dir, f))
+      for pkg in self._config.additional_apk:
+        apk_dir = os.path.join(COMMON_CIPD_ROOT, pkg.dest_path)
+        apk_file = _FindMinSdkFile(apk_dir, self._config.min_sdk)
+        # Some of these files come from chrome internal, so may not be
+        # available to non-internal permissioned users.
+        if os.path.exists(apk_file):
+          logging.info('Adding additional apk for install: %s', apk_file)
+          additional_apks.append(apk_file)
 
       if not privileged_apk_tuples:
         privileged_apk_tuples = []
@@ -422,9 +430,18 @@
       if additional_apks:
         for apk in additional_apks:
           instance.device.Install(apk, allow_downgrade=True, reinstall=True)
+          package_name = apk_helper.GetPackageName(apk)
+          package_version = instance.device.GetApplicationVersion(package_name)
+          logging.info('The version for package %r on the device is %r',
+                       package_name, package_version)
 
       if privileged_apk_tuples:
         system_app.InstallPrivilegedApps(instance.device, privileged_apk_tuples)
+        for apk, _ in privileged_apk_tuples:
+          package_name = apk_helper.GetPackageName(apk)
+          package_version = instance.device.GetApplicationVersion(package_name)
+          logging.info('The version for package %r on the device is %r',
+                       package_name, package_version)
 
       # Always disable the network to prevent built-in system apps from
       # updating themselves, which could take over package manager and
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index a6bd91c..8181a9d 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "db722166934ebc79a6e65e5fef9a6eae21eacb77"
+  libcxx_revision = "8b1c50618df7677fb1bd4bdf40d15a55b1733293"
 }
diff --git a/cc/trees/layer_tree_host_pixeltest_filters.cc b/cc/trees/layer_tree_host_pixeltest_filters.cc
index 4bd1a34..6331433 100644
--- a/cc/trees/layer_tree_host_pixeltest_filters.cc
+++ b/cc/trees/layer_tree_host_pixeltest_filters.cc
@@ -597,7 +597,7 @@
   if (renderer_type() == viz::RendererType::kSkiaVk) {
     pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(false);
   }
-#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH) || \
+#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) || \
     defined(_MIPS_ARCH_LOONGSON) || defined(ARCH_CPU_ARM64)
 #if BUILDFLAG(IS_WIN)
   // Windows has 153 pixels off by at most 2: crbug.com/225027
@@ -608,7 +608,7 @@
     percentage_pixels_large_error = 0.415f;  // 166px / (200*200)
     large_error_allowed = 1;
   }
-#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
+#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
   // There's a 1 pixel error on MacOS and ChromeOS
   float percentage_pixels_large_error = 0.0025f;  // 1px / (200*200)
   int large_error_allowed = 1;
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
index fd79a9d..5c1a267 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
@@ -29,27 +29,27 @@
     private static final String TAG = "StartSurfaceConfig";
     public static final StringCachedFieldTrialParameter START_SURFACE_VARIATION =
             new StringCachedFieldTrialParameter(
-                    ChromeFeatureList.START_SURFACE_ANDROID, "start_surface_variation", "");
+                    ChromeFeatureList.START_SURFACE_ANDROID, "start_surface_variation", "single");
     public static final BooleanCachedFieldTrialParameter START_SURFACE_EXCLUDE_MV_TILES =
             new BooleanCachedFieldTrialParameter(
                     ChromeFeatureList.START_SURFACE_ANDROID, "exclude_mv_tiles", false);
     public static final BooleanCachedFieldTrialParameter
             START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB =
                     new BooleanCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
-                            "hide_switch_when_no_incognito_tabs", false);
+                            "hide_switch_when_no_incognito_tabs", true);
 
     public static final BooleanCachedFieldTrialParameter START_SURFACE_LAST_ACTIVE_TAB_ONLY =
             new BooleanCachedFieldTrialParameter(
-                    ChromeFeatureList.START_SURFACE_ANDROID, "show_last_active_tab_only", false);
+                    ChromeFeatureList.START_SURFACE_ANDROID, "show_last_active_tab_only", true);
     public static final BooleanCachedFieldTrialParameter START_SURFACE_OPEN_NTP_INSTEAD_OF_START =
             new BooleanCachedFieldTrialParameter(
-                    ChromeFeatureList.START_SURFACE_ANDROID, "open_ntp_instead_of_start", false);
+                    ChromeFeatureList.START_SURFACE_ANDROID, "open_ntp_instead_of_start", true);
 
     private static final String TAB_COUNT_BUTTON_ON_START_SURFACE_PARAM =
             "tab_count_button_on_start_surface";
     public static final BooleanCachedFieldTrialParameter TAB_COUNT_BUTTON_ON_START_SURFACE =
             new BooleanCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
-                    TAB_COUNT_BUTTON_ON_START_SURFACE_PARAM, false);
+                    TAB_COUNT_BUTTON_ON_START_SURFACE_PARAM, true);
 
     private static final String SHOW_TABS_IN_MRU_ORDER_PARAM = "show_tabs_in_mru_order";
     public static final BooleanCachedFieldTrialParameter SHOW_TABS_IN_MRU_ORDER =
@@ -101,21 +101,21 @@
 
     private static final String SIGNIN_PROMO_NTP_COUNT_LIMIT_PARAM = "signin_promo_NTP_count_limit";
     public static final IntCachedFieldTrialParameter SIGNIN_PROMO_NTP_COUNT_LIMIT =
-            new IntCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
-                    SIGNIN_PROMO_NTP_COUNT_LIMIT_PARAM, Integer.MAX_VALUE);
+            new IntCachedFieldTrialParameter(
+                    ChromeFeatureList.START_SURFACE_ANDROID, SIGNIN_PROMO_NTP_COUNT_LIMIT_PARAM, 5);
 
     private static final String SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS_PARAM =
             "signin_promo_NTP_since_first_time_shown_limit_hours";
     public static final IntCachedFieldTrialParameter
             SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS =
                     new IntCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
-                            SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS_PARAM, -1);
+                            SIGNIN_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS_PARAM, 336);
 
     private static final String SIGNIN_PROMO_NTP_RESET_AFTER_HOURS_PARAM =
             "signin_promo_NTP_reset_after_hours";
     public static final IntCachedFieldTrialParameter SIGNIN_PROMO_NTP_RESET_AFTER_HOURS =
             new IntCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
-                    SIGNIN_PROMO_NTP_RESET_AFTER_HOURS_PARAM, -1);
+                    SIGNIN_PROMO_NTP_RESET_AFTER_HOURS_PARAM, 672);
 
     private static final String IS_DOODLE_SUPPORTED_PARAM = "is_doodle_supported";
     public static final BooleanCachedFieldTrialParameter IS_DOODLE_SUPPORTED =
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
index b846efa..dcb8aaf 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
@@ -57,6 +57,7 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
@@ -103,6 +104,7 @@
     ChromeFeatureList.START_SURFACE_ANDROID + "<Study", ChromeFeatureList.INSTANT_START})
 @Restriction({Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE,
     UiRestriction.RESTRICTION_TYPE_PHONE})
+@DoNotBatch(reason = "This test suite tests startup behaviours and thus can't be batched.")
 public class InstantStartTabSwitcherTest {
     // clang-format on
     private static final String SHADOW_VIEW_TAG = "TabListViewShadow";
@@ -212,7 +214,7 @@
     // clang-format off
     @EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
-        INSTANT_START_TEST_BASE_PARAMS})
+        INSTANT_START_TEST_BASE_PARAMS + "/show_last_active_tab_only/false"})
     @DisableIf.Build(message = "Flaky. See https://crbug.com/1091311",
         sdk_is_greater_than = Build.VERSION_CODES.O)
     public void renderTabGroups() throws IOException {
@@ -264,7 +266,7 @@
     // clang-format off
     @EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
-        INSTANT_START_TEST_BASE_PARAMS})
+        INSTANT_START_TEST_BASE_PARAMS + "/show_last_active_tab_only/false"})
     @DisableIf.Build(message = "Flaky. See https://crbug.com/1091311",
         sdk_is_greater_than = Build.VERSION_CODES.O)
     public void renderTabGroups_ThemeRefactor() throws IOException {
@@ -303,7 +305,7 @@
     @MediumTest
     // clang-format off
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
-        INSTANT_START_TEST_BASE_PARAMS})
+        INSTANT_START_TEST_BASE_PARAMS + "/show_last_active_tab_only/false"})
     public void testSingleAsHomepage_CloseTabInCarouselTabSwitcher()
             throws IOException, ExecutionException {
         // clang-format on
@@ -381,9 +383,10 @@
 
     @Test
     @MediumTest
-    // clang-format off
     @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION,
-        INSTANT_START_TEST_BASE_PARAMS})
+            INSTANT_START_TEST_BASE_PARAMS
+                    + "/show_last_active_tab_only/false/open_ntp_instead_of_start/false"})
+    // clang-format off
     public void testSingleAsHomepage_Landscape_TabSize() {
         // clang-format on
         StartSurfaceTestUtils.startMainActivityFromLauncher(mActivityTestRule);
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index 0278aa6..1dfadd5 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -45,6 +45,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.build.BuildConfig;
@@ -103,6 +104,7 @@
         ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.INSTANT_START})
 @Restriction({Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE,
     UiRestriction.RESTRICTION_TYPE_PHONE})
+@DoNotBatch(reason = "InstantStartTest tests startup behaviours and thus can't be batched.")
 public class InstantStartTest {
     // clang-format on
     private static final String IMMEDIATE_RETURN_PARAMS = "force-fieldtrial-params=Study.Group:"
@@ -486,7 +488,8 @@
     public void testShowLastTabWhenHomepageDisabledNoImmediateReturn() throws IOException {
         // clang-format on
         Assert.assertTrue(ChromeFeatureList.sInstantStart.isEnabled());
-        Assert.assertEquals(-1, ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getValue());
+        Assert.assertEquals(ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getDefaultValue(),
+                ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getValue());
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> HomepageManager.getInstance().setPrefHomepageEnabled(false));
@@ -506,7 +509,8 @@
           throws IOException {
         // clang-format on
         Assert.assertFalse(ChromeFeatureList.sInstantStart.isEnabled());
-        Assert.assertEquals(-1, ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getValue());
+        Assert.assertEquals(ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getDefaultValue(),
+                ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS.getValue());
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> HomepageManager.getInstance().setPrefHomepageEnabled(false));
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
index 085c7e5a..fc7a63f 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
@@ -13,6 +13,7 @@
 import static org.hamcrest.CoreMatchers.allOf;
 
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_BASE_PARAMS;
+import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_SINGLE_ENABLED_PARAMS;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.sClassParamsForStartSurfaceTest;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 import static org.chromium.ui.test.util.ViewUtils.waitForView;
@@ -155,7 +156,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_BackButton() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -197,7 +198,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisableIf.
         Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "Flaky, see crbug.com/1246457")
     public void testShow_SingleAsHomepage_BackButtonWithTabSwitcher() {
@@ -209,7 +210,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/show_last_active_tab_only/true"})
+    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "open_ntp_instead_of_start/false"})
     @DisableIf.
         Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "Flaky, see crbug.com/1246457")
     public void testShow_SingleAsHomepageV2_BackButtonWithTabSwitcher() {
@@ -280,7 +281,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testOpenRecentTabOnStartAndTapBackButtonReturnToStartSurface()
             throws ExecutionException {
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -310,9 +311,8 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
+    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "open_ntp_instead_of_start/false"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS
-        + "/show_last_active_tab_only/true/tab_count_button_on_start_surface/true"})
     public void testUserActionLoggedWhenBackToStartSurfaceHomePage() throws ExecutionException {
         // clang-format on
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -339,7 +339,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisabledTest(message = "https://crbug.com/1246457")
     @DisableFeatures({ChromeFeatureList.BACK_GESTURE_REFACTOR})
     public void testSwipeBackOnStartSurfaceHomePage() throws ExecutionException {
@@ -350,7 +350,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisabledTest(message = "https://crbug.com/1246457")
     @EnableFeatures({ChromeFeatureList.BACK_GESTURE_REFACTOR})
     public void testSwipeBackOnStartSurfaceHomePage_BackGestureRefactor()
@@ -361,7 +361,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisabledTest(message = "https://crbug.com/1246457")
     public void testSwipeBackOnTabOfLaunchTypeStartSurface() throws ExecutionException {
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -384,7 +384,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testBackButtonOnIncognitoTabOpenedFromStart() throws ExecutionException {
         // This is a test for crbug.com/1315915 to make sure when clicking back button on the
         // incognito tab opened from Start, the non-incognito homepage should show.
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
index 15fe2ef..871d8cd1 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
@@ -11,7 +11,7 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_BASE_PARAMS;
+import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_SINGLE_ENABLED_PARAMS;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.sClassParamsForStartSurfaceTest;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 
@@ -150,7 +150,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testTapMVTilesInSingleSurface() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -176,7 +176,7 @@
     @Feature({"StartSurface"})
     // Disable feed placeholder animation because it causes waitForSnackbar() to time out.
     @CommandLineFlags.
-    Add({START_SURFACE_TEST_BASE_PARAMS, FeedPlaceholderLayout.DISABLE_ANIMATION_SWITCH})
+    Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS, FeedPlaceholderLayout.DISABLE_ANIMATION_SWITCH})
     public void testDismissTileWithContextMenuAndUndo() throws Exception {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -208,7 +208,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testOpenTileInNewTabWithContextMenu() throws ExecutionException {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -232,7 +232,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testOpenTileInIncognitoTabWithContextMenu() throws ExecutionException {
         Assume.assumeFalse("https://crbug.com/1210554", mUseInstantStart && mImmediateReturn);
         if (!mImmediateReturn) {
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
index e01cfa46..cc835071 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
@@ -81,8 +81,7 @@
                     new ParameterSet().value(false, true).name("NoInstant_Return"),
                     new ParameterSet().value(true, true).name("Instant_Return"));
 
-    private static final String BASE_PARAMS =
-            "force-fieldtrial-params=Study.Group:start_surface_variation";
+    private static final String BASE_PARAMS = "force-fieldtrial-params=Study.Group:";
 
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@@ -124,7 +123,7 @@
     @LargeTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({BASE_PARAMS + "/single/tab_count_button_on_start_surface/true"})
+    @CommandLineFlags.Add({BASE_PARAMS + "tab_count_button_on_start_surface/true"})
     @DisabledTest(message = "https://crbug.com/1263910")
     public void testShow_SingleAsHomepage_NoTabs() throws TimeoutException {
         // clang-format on
@@ -156,8 +155,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({BASE_PARAMS + "/single/exclude_mv_tiles/true" +
-        "/show_last_active_tab_only/true/open_ntp_instead_of_start/true"})
+    @CommandLineFlags.Add({BASE_PARAMS + "exclude_mv_tiles/true"})
     @DisabledTest(message = "https://crbug.com/1263910")
     public void testShow_SingleAsHomepage_SingleTabSwitcher_NoTabs() {
         // clang-format on
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
index 6a60a3d..8dd9dca 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
@@ -20,6 +20,7 @@
 
 import static org.chromium.chrome.browser.feed.FeedPlaceholderLayout.DISABLE_ANIMATION_SWITCH;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_BASE_PARAMS;
+import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_SINGLE_ENABLED_PARAMS;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.sClassParamsForStartSurfaceTest;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 import static org.chromium.ui.test.util.ViewUtils.waitForView;
@@ -145,7 +146,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsTabSwitcher() {
         if (mImmediateReturn) {
             StartSurfaceTestUtils.waitForOverviewVisible(mLayoutChangedCallbackHelper,
@@ -174,7 +175,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_CloseAllTabsShouldHideTabSwitcher() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -195,7 +196,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface", "TabGroup"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
     @FlakyTest(message = "https://crbug.com/1232695")
     public void testCreateTabWithinTabGroup() throws Exception {
@@ -263,7 +264,8 @@
     @LargeTest
     @Feature({"StartSurface"})
     @FlakyTest(message = "https://crbug.com/1295839")
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/show_tabs_in_mru_order/true"})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS + "/show_tabs_in_mru_order/true"})
     public void test_CarouselTabSwitcherShowTabsInMRUOrder() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -307,7 +309,8 @@
     @Test
     @LargeTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/show_tabs_in_mru_order/true"})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS + "/show_tabs_in_mru_order/true"})
     public void testShow_GridTabSwitcher_AlwaysShowTabsInCreationOrder() {
         tabSwitcher_AlwaysShowTabsInGridTabSwitcherInCreationOrderImpl();
     }
@@ -317,8 +320,8 @@
     @Feature({"StartSurface"})
     @EnableFeatures(ChromeFeatureList.TAB_GROUPS_ANDROID)
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS +
-        "/show_tabs_in_mru_order/true/show_last_active_tab_only/true",
+    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS
+        + "show_tabs_in_mru_order/true/open_ntp_instead_of_start/false",
         DISABLE_ANIMATION_SWITCH})
     public void testShowV2_GridTabSwitcher_AlwaysShowTabsInCreationOrder() {
         // clang-format on
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index bd9bed3..e6026393 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -28,6 +28,7 @@
 
 import static org.chromium.chrome.features.start_surface.StartSurfaceMediator.FEED_VISIBILITY_CONSISTENCY;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_BASE_PARAMS;
+import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_SINGLE_ENABLED_PARAMS;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.sClassParamsForStartSurfaceTest;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 import static org.chromium.ui.test.util.ViewUtils.waitForStableView;
@@ -179,7 +180,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -210,7 +211,8 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS + "/hide_switch_when_no_incognito_tabs/false"})
     public void testShow_SingleAsHomepage_NoIncognitoSwitch() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -256,9 +258,10 @@
     @Test
     @LargeTest
     @Feature({"StartSurface"})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_BASE_PARAMS + "exclude_mv_tiles/true/show_last_active_tab_only/false"
+            + "/open_ntp_instead_of_start/false/tab_count_button_on_start_surface/false"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS +
-        "/exclude_mv_tiles/true/hide_switch_when_no_incognito_tabs/true"})
     public void testShow_SingleAsHomepage_NoMVTiles() {
         // clang-format on
         if (!mImmediateReturn) {
@@ -302,9 +305,9 @@
     @Test
     @LargeTest
     @Feature({"StartSurface"})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_BASE_PARAMS + "exclude_mv_tiles/true/open_ntp_instead_of_start/false"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/exclude_mv_tiles/true" +
-        "/hide_switch_when_no_incognito_tabs/true/show_last_active_tab_only/true"})
     public void testShow_SingleAsHomepage_SingleTabNoMVTiles() {
         // clang-format on
         Assume.assumeFalse("https://crbug.com/1205642, https://crbug.com/1214303",
@@ -350,7 +353,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisableIf.
         Build(sdk_is_less_than = Build.VERSION_CODES.N, message = "Flaky, see crbug.com/1246457")
     public void testShow_SingleAsHomepage_FromResumeShowStart() throws Exception {
@@ -395,7 +398,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisabledTest(message = "crbug.com/1170673 - NoInstant_NoReturn version is flaky")
     public void testSearchInSingleSurface() {
         if (!mImmediateReturn) {
@@ -429,7 +432,8 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/open_ntp_instead_of_start/true"})
+    @CommandLineFlags.
+    Add({START_SURFACE_TEST_BASE_PARAMS + "hide_switch_when_no_incognito_tabs/false"})
     public void testCreateNewTab_OpenNTPInsteadOfStart() {
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         StartSurfaceTestUtils.waitForTabModel(cta);
@@ -458,7 +462,6 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/open_ntp_instead_of_start/true"})
     public void testHomeButton_OpenNTPInsteadOfStart() {
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         StartSurfaceTestUtils.waitForTabModel(cta);
@@ -493,15 +496,15 @@
     @Test
     @MediumTest
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
-    // clang-format off
+
     @EnableFeatures({ChromeFeatureList.TAB_SWITCHER_ON_RETURN + "<Study",
             ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID,
             ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
-    @CommandLineFlags.Add({
-            START_SURFACE_TEST_BASE_PARAMS + "/show_last_active_tab_only/true",
+    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "open_ntp_instead_of_start/false",
             // Disable feed placeholder animation because it causes waitForDeferredStartup() to time
             // out.
             FeedPlaceholderLayout.DISABLE_ANIMATION_SWITCH})
+    // clang-format off
     public void startSurfaceRecordHistogramsTest_SingleTab() {
         // clang-format on
         startSurfaceRecordHistogramsTest(true);
@@ -514,7 +517,7 @@
     @EnableFeatures({ChromeFeatureList.TAB_SWITCHER_ON_RETURN + "<Study",
         ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID,
         ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS,
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS,
         // Disable feed placeholder animation because it causes waitForDeferredStartup() to time
         // out.
         FeedPlaceholderLayout.DISABLE_ANIMATION_SWITCH})
@@ -592,7 +595,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_VoiceSearchButtonShown() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -609,7 +612,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_BottomSheet() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -657,7 +660,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_ResetScrollPosition() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -689,7 +692,7 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void singleAsHomepage_PressHomeButtonWillKeepTab() {
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
@@ -725,7 +728,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.O_MR1, supported_abis_includes = "x86",
             message = "Flaky, see crbug.com/1258154")
     public void testNotShowIncognitoHomepage() {
@@ -761,7 +764,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS})
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void test_DoNotLoadLastSelectedTabOnStartup() {
         // clang-format on
         doTestNotLoadLastSelectedTabOnStartupImpl();
@@ -771,7 +774,6 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
-    @CommandLineFlags.Add({START_SURFACE_TEST_BASE_PARAMS + "/show_last_active_tab_only/true"})
     public void test_DoNotLoadLastSelectedTabOnStartupV2() {
         // clang-format on
         doTestNotLoadLastSelectedTabOnStartupImpl();
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
index eff6aecd..48b824c 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
@@ -98,10 +98,12 @@
 public class StartSurfaceTestUtils {
     public static final String INSTANT_START_TEST_BASE_PARAMS =
             "force-fieldtrial-params=Study.Group:"
-            + ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS_PARAM + "/0"
-            + "/start_surface_variation/single";
+            + ReturnToChromeUtil.TAB_SWITCHER_ON_RETURN_MS_PARAM + "/0";
+    public static final String START_SURFACE_TEST_SINGLE_ENABLED_PARAMS =
+            "force-fieldtrial-params=Study.Group:"
+            + "show_last_active_tab_only/false/open_ntp_instead_of_start/false";
     public static final String START_SURFACE_TEST_BASE_PARAMS =
-            "force-fieldtrial-params=Study.Group:start_surface_variation/single";
+            "force-fieldtrial-params=Study.Group:";
     public static List<ParameterSet> sClassParamsForStartSurfaceTest =
             Arrays.asList(new ParameterSet().value(false, false).name("NoInstant_NoReturn"),
                     new ParameterSet().value(true, false).name("Instant_NoReturn"),
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
index 7f08a96..2b46e4c2 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
@@ -52,6 +52,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyAllTabsHaveThumbnail;
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabStripFaviconCount;
 import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabSwitcherCardCount;
+import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.START_SURFACE_TEST_SINGLE_ENABLED_PARAMS;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createTabStateFile;
 import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
@@ -131,8 +132,6 @@
     // clang-format on
     private static final String CUSTOMIZED_TITLE1 = "wfh tips";
     private static final String CUSTOMIZED_TITLE2 = "wfh funs";
-    private static final String START_SURFACE_BASE_PARAMS =
-            "force-fieldtrial-params=Study.Group:start_surface_variation";
     private static final String TAB_GROUP_LAUNCH_POLISH_PARAMS =
             "force-fieldtrial-params=Study.Group:enable_launch_polish/true";
 
@@ -928,7 +927,8 @@
     @MediumTest
     @DisableIf.Device(type = UiDisableIf.TABLET)
     @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
-    @CommandLineFlags.Add({"force-fieldtrials=Study/Group", START_SURFACE_BASE_PARAMS + "/single"})
+    @CommandLineFlags.
+    Add({"force-fieldtrials=Study/Group", START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testDialogSetup_WithStartSurface() throws Exception {
         // Create a tab group with 2 tabs.
         finishActivity(mActivityTestRule.getActivity());
@@ -966,11 +966,14 @@
     @Test
     @MediumTest
     @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID + "<Study"})
-    @CommandLineFlags.Add({"force-fieldtrials=Study/Group", START_SURFACE_BASE_PARAMS + "/single"})
+    @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
+            START_SURFACE_TEST_SINGLE_ENABLED_PARAMS + "/hide_switch_when_no_incognito_tabs/false"})
     @DisableIf.
     Build(sdk_is_greater_than = VERSION_CODES.M, message = "crbug.com/1119899, crbug.com/1131545")
     @DisableIf.Device(type = UiDisableIf.TABLET)
+    // clang-format off
     public void testUndoClosureInDialog_WithStartSurface() throws Exception {
+        // clang-format on
         // Create a tab group with 2 tabs.
         finishActivity(mActivityTestRule.getActivity());
         createThumbnailBitmapAndWriteToFile(0);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 99a01b1..7a1bf2d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -85,6 +85,7 @@
                 add(ChromeFeatureList.sCctResizableAllowResizeByUserGesture);
                 add(ChromeFeatureList.sCctResizableForFirstParties);
                 add(ChromeFeatureList.sCctResizableForThirdParties);
+                add(ChromeFeatureList.sCctResizableWindowAboveNavbar);
                 add(ChromeFeatureList.sCctToolbarCustomizations);
                 add(ChromeFeatureList.sCloseTabSuggestions);
                 add(ChromeFeatureList.sCommandLineOnNonRooted);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index e783539..af4b81724 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -285,6 +285,16 @@
     }
 
     @Override
+    protected Supplier<Integer> getBaseHeightProvider() {
+        if (mIntentDataProvider.get().isPartialHeightCustomTab()
+                && !ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled()) {
+            return () -> mActivity.findViewById(R.id.coordinator).getHeight();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
     protected boolean canDrawOutsideScreen() {
         return mCustomTabHeightStrategy.canDrawOutsideScreen();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
index c554151..9dbc1a1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
@@ -23,11 +23,13 @@
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.GestureDetector;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
@@ -44,6 +46,7 @@
 import androidx.core.view.WindowInsetsControllerCompat;
 import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
 
+import org.chromium.base.Consumer;
 import org.chromium.base.MathUtils;
 import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
@@ -99,6 +102,7 @@
     private final OnResizedCallback mOnResizedCallback;
     private final AnimatorListener mSpinnerFadeoutAnimatorListener;
     private final int mCachedHandleHeight;
+    private boolean mWindowAboveNavbar;
     private @Px int mInitialHeight;
     private ValueAnimator mAnimator;
     private int mShadowOffset;
@@ -108,6 +112,7 @@
     // ContentFrame + CoordinatorLayout - CompositorViewHolder
     //              + NavigationBar
     //              + Spinner
+    // When CCT_RESIZABLE_WINDOW_ABOVE_NAVBAR is disabled, We resize inner contents view.
     // Not just CompositorViewHolder but also CoordinatorLayout is resized because many UI
     // components such as BottomSheet, InfoBar, Snackbar are child views of CoordinatorLayout,
     // which makes them appear correctly at the bottom.
@@ -122,6 +127,7 @@
     private @Px int mNavbarHeight;
     private int mOrientation;
     private boolean mIsInMultiWindowMode;
+    private int mHeight;
 
     private ImageView mSpinnerView;
     private LinearLayout mNavbar;
@@ -139,6 +145,10 @@
     // Y offset when a dragging gesture starts.
     private int mDraggingStartY;
 
+    // Method to invoke to animate the tab. Animates by altering top y position by default,
+    // but using height for the close animation.
+    private Consumer<Integer> mTabAnimator = this::updateWindowPos;
+
     /** A callback to be called once the Custom Tab has been resized. */
     interface OnResizedCallback {
         /** The Custom Tab has been resized. */
@@ -202,7 +212,8 @@
                         mSeenFirstMoveOrDown = true;
                         mVelocityTracker.clear();
                         onMoveStart();
-                        mOffsetY = mActivity.getWindow().getAttributes().y - y;
+                        mDraggingStartY = mActivity.getWindow().getAttributes().y;
+                        mOffsetY = mDraggingStartY - y;
                         mLastPosY = y;
                         mStopShowingSpinner = false;
                     } else {
@@ -298,6 +309,7 @@
     public PartialCustomTabHeightStrategy(Activity activity, @Px int initialHeight,
             Integer navigationBarColor, Integer navigationBarDividerColor,
             OnResizedCallback onResizedCallback, ActivityLifecycleDispatcher lifecycleDispatcher) {
+        mWindowAboveNavbar = ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled();
         mActivity = activity;
         mMaxHeight = getMaximumPossibleHeight();
         mInitialHeight = MathUtils.clamp(
@@ -333,10 +345,8 @@
         mSpinnerFadeoutAnimatorListener = new AnimatorListener() {
             @Override
             public void onAnimationStart(Animator animator) {}
-
             @Override
             public void onAnimationRepeat(Animator animator) {}
-
             @Override
             public void onAnimationEnd(Animator animator) {
                 mSpinner.stop();
@@ -395,9 +405,12 @@
 
         initializeHeight();
         updateShadowOffset();
-
-        setContentsHeight();
-        updateNavbarVisibility(true);
+        if (mWindowAboveNavbar) {
+            maybeInvokeResizeCallback();
+        } else {
+            setContentsHeight();
+            updateNavbarVisibility(true);
+        }
     }
 
     private int initialY() {
@@ -475,8 +488,8 @@
     // ValueAnimator.AnimatorUpdateListener implementation.
     @Override
     public void onAnimationUpdate(ValueAnimator valueAnimator) {
-        int ypos = (int) valueAnimator.getAnimatedValue();
-        updateWindowPos(ypos);
+        int value = (int) valueAnimator.getAnimatedValue();
+        mTabAnimator.accept(value);
     }
 
     private void roundCorners(
@@ -540,8 +553,9 @@
     }
 
     private void initializeHeight() {
-        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
-        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+        Window window = mActivity.getWindow();
+        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
         mNavbarHeight = getNavbarHeight();
         int maxExpandedY = getFullyExpandedYCoordinate();
@@ -551,32 +565,53 @@
             // Resizing by user dragging is not supported in landscape mode; no need to set
             // the status here.
             height = mDisplayHeight - maxExpandedY;
-            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            if (!mWindowAboveNavbar) {
+                mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            }
         } else {
             height = mInitialHeight;
             mStatus = HeightStatus.INITIAL_HEIGHT;
-            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            if (!mWindowAboveNavbar) {
+                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            }
         }
 
-        WindowManager.LayoutParams attributes = mActivity.getWindow().getAttributes();
-        // TODO(jinsukkim): Handle multi-window mode.
-        if (attributes.height == height) return;
+        WindowManager.LayoutParams attrs = window.getAttributes();
+        if (attrs.height == height) return;
 
-        // We do not resize Window but just translate its vertical offset, and resize Coordinator-
-        // LayoutForPointer instead. This helps us work around the round-corner bug in Android S.
-        // See b/223536648.
-        attributes.y = Math.max(maxExpandedY, mDisplayHeight - height);
-        mActivity.getWindow().setAttributes(attributes);
-
+        if (mWindowAboveNavbar) {
+            // To avoid the bottom navigation bar area flickering when starting dragging, position
+            // web contents area right above the navigation bar so the two won't overlap. The
+            // navigation area now just shows whatever is underneath: 1) loading view/web contents
+            // while dragging 2) host app's navigation bar when at rest.
+            positionAtHeight(height);
+            mHeight = attrs.height;
+        } else {
+            // We do not resize Window but just translate its vertical offset, and resize
+            // CoordinatorLayoutForPointer instead. This helps us work around the round-corner bug
+            // in Android S. See b/223536648.
+            attrs.y = Math.max(maxExpandedY, mDisplayHeight - height);
+            window.setAttributes(attrs);
+        }
         updateDragBarVisibility();
     }
 
+    private void positionAtHeight(int height) {
+        WindowManager.LayoutParams attrs = mActivity.getWindow().getAttributes();
+        attrs.height = height - mNavbarHeight;
+        attrs.y = mNavbarHeight;
+        attrs.gravity = Gravity.BOTTOM;
+        mActivity.getWindow().setAttributes(attrs);
+    }
+
     private void updateDragBarVisibility() {
         View dragBar = mActivity.findViewById(R.id.drag_bar);
         if (dragBar != null) dragBar.setVisibility(isFullHeight() ? View.GONE : View.VISIBLE);
     }
 
     private void updateShadowOffset() {
+        // TODO(jinsukkim): Remove the shadow when in full-height so there won't be a gap
+        //                  beneath the status bar.
         if (isFullHeight() || mDrawOutlineShadow) {
             mShadowOffset = 0;
         } else {
@@ -608,11 +643,13 @@
         // bar and (optionally) the 90%-height adjustment.
         int topY = getFullyExpandedYCoordinateWithAdjustment();
         y = MathUtils.clamp(y, topY, mMaxHeight);
-        WindowManager.LayoutParams attributes = mActivity.getWindow().getAttributes();
-        if (attributes.y == y) return;
+        Window window = mActivity.getWindow();
+        WindowManager.LayoutParams attrs = window.getAttributes();
+        if (attrs.y == y) return;
 
-        attributes.y = y;
-        mActivity.getWindow().setAttributes(attributes);
+        attrs.y = y;
+        window.setAttributes(attrs);
+
         if (mFinishRunnable != null) return;
 
         // Starting dragging from INITIAL_HEIGHT state, we can hide the spinner if the tab:
@@ -639,13 +676,30 @@
         }
     }
 
+    private void updateWindowHeight(int height) {
+        Window window = mActivity.getWindow();
+        WindowManager.LayoutParams attrs = window.getAttributes();
+        attrs.height = height;
+        window.setAttributes(attrs);
+    }
+
     private boolean isSpinnerVisible() {
         return mSpinnerView != null && mSpinnerView.getVisibility() == View.VISIBLE;
     }
 
     private void onMoveStart() {
-        mDraggingStartY = mActivity.getWindow().getAttributes().y;
-        updateNavbarVisibility(false);
+        if (mWindowAboveNavbar) {
+            Window window = mActivity.getWindow();
+            window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            WindowManager.LayoutParams attrs = window.getAttributes();
+            attrs.y = mMaxHeight - attrs.height - mNavbarHeight;
+            attrs.height = mMaxHeight;
+            attrs.gravity = Gravity.NO_GRAVITY;
+            window.setAttributes(attrs);
+            showNavbarButtons(false);
+        } else {
+            updateNavbarVisibility(false);
+        }
     }
 
     private void onMoveEnd() {
@@ -654,11 +708,19 @@
             return;
         }
         hideSpinnerView();
-        updateNavbarVisibility(true);
+        if (mWindowAboveNavbar) {
+            Window window = mActivity.getWindow();
+            window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+            positionAtHeight(mMaxHeight - window.getAttributes().y);
+            showNavbarButtons(true);
+            maybeInvokeResizeCallback();
+        } else {
+            updateNavbarVisibility(true);
+        }
     }
 
     private void hideSpinnerView() {
-        setContentsHeight();
+        if (!mWindowAboveNavbar) setContentsHeight();
 
         // TODO(crbug.com/1328555): Look into observing a view resize event to ensure the fade
         // animation can always cover the transition artifact.
@@ -681,7 +743,11 @@
 
             // Toolbar should not be hidden by spinner screen.
             ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(MATCH_PARENT, 0);
-            lp.setMargins(0, mToolbarView.getHeight() + getHandleHeight() + mShadowOffset, 0, 0);
+            int topMargin = mToolbarView.getHeight() + mShadowOffset;
+            // See the comment below for why we add handle height.
+            if (!mWindowAboveNavbar) topMargin += getHandleHeight();
+            lp.setMargins(0, topMargin, 0, 0);
+
             mSpinner = new CircularProgressDrawable(mActivity);
             mSpinner.setStyle(CircularProgressDrawable.LARGE);
             mSpinnerView.setImageDrawable(mSpinner);
@@ -691,9 +757,14 @@
             mSpinner.setColorSchemeColors(colorList);
             centerSpinnerVertically(lp);
         }
-
         // Spinner view is added to ContentFrameLayout to hide both WebContents and navigation bar.
-        if (mSpinnerView.getParent() == null) mContentFrame.addView(mSpinnerView);
+        // For window-above-navbar, it should be added to CoordinatorLayoutForPointer to obscure
+        // the flickering at the beginning of dragging action. Their top positions differ by
+        // |getHandleHeight()| which is a top margin of CoordinatorLayoutForPointer.
+        if (mSpinnerView.getParent() == null) {
+            ViewGroup parent = mWindowAboveNavbar ? mCoordinatorLayout : mContentFrame;
+            parent.addView(mSpinnerView);
+        }
         mSpinnerView.clearAnimation();
         mSpinnerView.setAlpha(0.f);
         mSpinnerView.setVisibility(View.VISIBLE);
@@ -808,6 +879,14 @@
         }
     }
 
+    private void maybeInvokeResizeCallback() {
+        WindowManager.LayoutParams attrs = mActivity.getWindow().getAttributes();
+        if (mHeight != attrs.height && attrs.height > 0) {
+            mOnResizedCallback.onResized(attrs.height);
+            mHeight = attrs.height;
+        }
+    }
+
     private void showNavbarButtons(boolean show) {
         View decorView = mActivity.getWindow().getDecorView();
         WindowInsetsControllerCompat controller =
@@ -901,23 +980,25 @@
         if (mFinishRunnable != null) return;
 
         mFinishRunnable = finishRunnable;
-
-        int start = mActivity.getWindow().getAttributes().y;
-        int end = mDisplayHeight - mNavbarHeight;
-
-        if (isFullHeight()) {
+        WindowManager.LayoutParams attrs = mActivity.getWindow().getAttributes();
+        if (attrs.gravity == Gravity.BOTTOM) {
+            mTabAnimator = this::updateWindowHeight;
+            mAnimator.setIntValues(attrs.height, 0);
+        } else {
+            mAnimator.setIntValues(attrs.y, mDisplayHeight - mNavbarHeight);
+        }
+        if (!mWindowAboveNavbar && isFullHeight()) {
             mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
         }
         mAnimator.setDuration(
                 mActivity.getResources().getInteger(android.R.integer.config_mediumAnimTime));
-        mAnimator.setIntValues(start, end);
         mAnimator.setInterpolator(new AccelerateInterpolator());
         mAnimator.start();
     }
 
     @Override
     public boolean canDrawOutsideScreen() {
-        return !isFullHeight();
+        return !mWindowAboveNavbar && !isFullHeight();
     }
 
     @VisibleForTesting
@@ -937,4 +1018,9 @@
     int getNavbarHeightForTesting() {
         return mNavbarHeight;
     }
+
+    @VisibleForTesting
+    void setWindowAboveNavbarForTesting(boolean windowAboveNavbar) {
+        mWindowAboveNavbar = windowAboveNavbar;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunSignInProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunSignInProcessor.java
index 46c55fc..7b8e0ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunSignInProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunSignInProcessor.java
@@ -11,6 +11,7 @@
 import androidx.annotation.NonNull;
 
 import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -68,7 +69,7 @@
 
         final String accountName = getFirstRunFlowSignInAccountName();
         if (TextUtils.isEmpty(accountName) && getFirstRunFlowSignInSetup()) {
-            assert SyncConsentFirstRunFragment.shouldEnableImmediately();
+            assert ChromeFeatureList.isEnabled(ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE);
             openAdvancedSyncSettings(activity);
             setFirstRunFlowSignInComplete(true);
             return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java
index 3eddafe..3d199df 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java
@@ -11,11 +11,15 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.FREMobileIdentityConsistencyFieldTrial;
+import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.services.SigninPreferencesManager;
 import org.chromium.chrome.browser.ui.signin.SyncConsentFragmentBase;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
+import org.chromium.components.signin.base.CoreAccountInfo;
+import org.chromium.components.signin.identitymanager.ConsentLevel;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 
 import java.util.List;
@@ -33,15 +37,6 @@
     // saved state bundle. See crbug.com/1225102
     public SyncConsentFirstRunFragment() {}
 
-    /**
-     * Returns true if sync will be enabled as soon as the user clicks the "Yes, I'm in" button,
-     * and false if this will be deferred until the main activity starts.
-     */
-    public static boolean shouldEnableImmediately() {
-        return ChromeFeatureList.isEnabled(ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE)
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS);
-    }
-
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
@@ -84,25 +79,52 @@
                     MobileFreProgress.SYNC_CONSENT_SETTINGS_LINK_CLICK);
         }
 
-        if (shouldEnableImmediately()) {
-            // Enable sync now. Leave the account pref empty in FirstRunSignInProcessor, so start()
-            // doesn't try to do it a second time. Only set the advanced setup pref later in
-            // closeAndMaybeOpenSyncSettings(), because settings shouldn't open if
-            // signinAndEnableSync() fails.
-            FirstRunSignInProcessor.setFirstRunFlowSignInAccountName(null);
-            signinAndEnableSync(accountName, settingsClicked, callback);
-        } else {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE)) {
             // Enabling sync is deferred to FirstRunSignInProcessor.start().
             FirstRunSignInProcessor.setFirstRunFlowSignInAccountName(accountName);
             FirstRunSignInProcessor.setFirstRunFlowSignInSetup(settingsClicked);
             getPageDelegate().advanceToNextPage();
             callback.run();
+            return;
         }
+
+        // Enable sync now. Leave the account pref empty in FirstRunSignInProcessor, so start()
+        // doesn't try to do it a second time. Only set the advanced setup pref later in
+        // closeAndMaybeOpenSyncSettings(), because settings shouldn't open if
+        // signinAndEnableSync() fails.
+        FirstRunSignInProcessor.setFirstRunFlowSignInAccountName(null);
+        if (!getPageDelegate().getProperties().getBoolean(IS_CHILD_ACCOUNT, false)) {
+            signinAndEnableSync(accountName, settingsClicked, callback);
+            return;
+        }
+
+        // Special case for child accounts. In rare cases, e.g. if Terms & Conditions is clicked,
+        // SigninChecker might have been triggered before the FRE ends and started sign-in (the
+        // ConsentLevel depends on AllowSyncOffForChildAccounts). In doubt, wait.
+        Profile profile = Profile.getLastUsedRegularProfile();
+        IdentityServicesProvider.get().getSigninManager(profile).runAfterOperationInProgress(() -> {
+            CoreAccountInfo syncingAccount = IdentityServicesProvider.get()
+                                                     .getIdentityManager(profile)
+                                                     .getPrimaryAccountInfo(ConsentLevel.SYNC);
+            if (syncingAccount == null) {
+                signinAndEnableSync(accountName, settingsClicked, callback);
+                return;
+            }
+
+            if (!accountName.equals(syncingAccount.getEmail())) {
+                throw new IllegalStateException(
+                        "Child accounts should only be allowed to sync with a single account");
+            }
+
+            // SigninChecker enabled sync already. Just open settings if needed.
+            closeAndMaybeOpenSyncSettings(settingsClicked);
+            callback.run();
+        });
     }
 
     @Override
     protected void closeAndMaybeOpenSyncSettings(boolean settingsClicked) {
-        assert shouldEnableImmediately();
+        assert ChromeFeatureList.isEnabled(ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE);
         // Now that signinAndEnableSync() succeeded, signal whether FirstRunSignInProcessor.start()
         // should open settings.
         FirstRunSignInProcessor.setFirstRunFlowSignInSetup(settingsClicked);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
index 3ea0817..f6d96f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
@@ -23,6 +23,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
 import org.chromium.chrome.browser.preferences.PrefChangeRegistrar.PrefObserver;
@@ -319,6 +320,8 @@
     // is set to allow the app to be drawn outside the screen. Returns {@code null} if not
     // necessary.
     private Rect getAppRectInWindow() {
+        if (ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled()) return null;
+
         View view = getView().getRootView().findViewById(R.id.coordinator);
         if (!view.isAttachedToWindow()) return null;
         WindowManager.LayoutParams attrs = ((Activity) getContext()).getWindow().getAttributes();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
index a27ef4f..8775720 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
@@ -260,15 +260,16 @@
                 Supplier<Profile> profileSupplier, Callback<Tab> printCallback,
                 @ShareOrigin int shareOrigin, long shareStartTime, boolean sharingHubEnabled) {
             Profile profile = profileSupplier.get();
-            // In some cases, ProfileSupplier.get() will return null or will not be initialized in
-            // Native. See https://crbug.com/1346710 and https://crbug.com/1353138 for context.
-            if (profile == null || !profile.isNativeInitialized()) {
+            // In some cases, ProfileSupplier.get() will return null. See https://crbug.com/1346710
+            // and https://crbug.com/1353138 for context.
+            if (profile == null) {
                 profile = Profile.getLastUsedRegularProfile();
             }
             if (chromeShareExtras.shareDirectly()) {
                 ShareHelper.shareWithLastUsedComponent(params);
             } else if (sharingHubEnabled && !chromeShareExtras.sharingTabGroup()
                     && profile != null) {
+                profile.ensureNativeInitialized();
                 // TODO(crbug.com/1085078): Sharing hub is suppressed for tab group sharing.
                 // Re-enable it when tab group sharing is supported by sharing hub.
                 RecordHistogram.recordEnumeratedHistogram(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java
index c7e036c..590c1c65 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java
@@ -87,8 +87,8 @@
     @VisibleForTesting
     public static final String TAB_SWITCHER_ON_RETURN_MS_PARAM = "tab_switcher_on_return_time_ms";
     public static final IntCachedFieldTrialParameter TAB_SWITCHER_ON_RETURN_MS =
-            new IntCachedFieldTrialParameter(
-                    ChromeFeatureList.TAB_SWITCHER_ON_RETURN, TAB_SWITCHER_ON_RETURN_MS_PARAM, -1);
+            new IntCachedFieldTrialParameter(ChromeFeatureList.TAB_SWITCHER_ON_RETURN,
+                    TAB_SWITCHER_ON_RETURN_MS_PARAM, 28800000); // 8 hours
 
     @VisibleForTesting
     static final String UMA_TIME_TO_GTS_FIRST_MEANINGFUL_PAINT =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 7d22e27..459a9fba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -1311,6 +1311,18 @@
     }
 
     /**
+     * Provides the height of the base app area on which bottom sheet client is drawn. This is
+     * not necessary for most embedders of BottomSheet, unless they have non-zero vertical Window
+     * offset that would push down a part of app area out of the screen. BottomSheet then uses
+     * this height to resize the sheet content so all of it is visible.
+     * @return Supplier of the height of the base app area. {@code null} if not necessary.
+     */
+    @Nullable
+    protected Supplier<Integer> getBaseHeightProvider() {
+        return null;
+    }
+
+    /**
      * Whether UI like popup can be drawn outside the screen. {@code false} by default.
      */
     protected boolean canDrawOutsideScreen() {
@@ -1401,8 +1413,7 @@
                         -> mScrimCoordinator,
                 sheetInitializedCallback, mActivity.getWindow(),
                 mWindowAndroid.getKeyboardDelegate(),
-                () -> mActivity.findViewById(R.id.sheet_container),
-                () -> mActivity.findViewById(R.id.coordinator).getHeight());
+                () -> mActivity.findViewById(R.id.sheet_container), getBaseHeightProvider());
         BottomSheetControllerFactory.setExceptionReporter(
                 (throwable)
                         -> ChromePureJavaExceptionReporter.reportJavaException(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 8b23dd9..f1a540b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -1792,7 +1792,19 @@
     @Test
     @SmallTest
     @Features.EnableFeatures({ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES})
-    public void testLaunchPartialCustomTabActivity() throws Exception {
+    @Features.DisableFeatures({ChromeFeatureList.CCT_RESIZABLE_WINDOW_ABOVE_NAVBAR})
+    public void testLaunchPartialCustomTabActivity_fixedWindow() throws Exception {
+        testLaunchPartialCustomTabActivity();
+    }
+
+    @Test
+    @SmallTest
+    @Features.EnableFeatures({ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES})
+    public void testLaunchPartialCustomTabActivity_dynamicWindow() throws Exception {
+        testLaunchPartialCustomTabActivity();
+    }
+
+    private void testLaunchPartialCustomTabActivity() throws Exception {
         Intent intent = createMinimalCustomTabIntent();
         CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
         CustomTabsConnection connection = CustomTabsConnection.getInstance();
@@ -1801,8 +1813,13 @@
         intent.putExtra(CustomTabIntentDataProvider.EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL, 50);
         mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
 
-        WindowManager.LayoutParams attributes = getActivity().getWindow().getAttributes();
-        assertNotEquals("The window should have non-zero y offset", 0, attributes.y);
+        if (ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled()) {
+            // A Normal CCT height is set to MATCH_PARENT while Partial CCT has non-zero value.
+            int fullHeight = ViewGroup.LayoutParams.MATCH_PARENT;
+            WindowManager.LayoutParams attrs = getActivity().getWindow().getAttributes();
+            assertNotEquals("The window should have non-full height", fullHeight, attrs.height);
+            return;
+        }
 
         // Verify the hierarchy of the enclosing layouts that PCCT relies on for its operation.
         CallbackHelper eventHelper = new CallbackHelper();
@@ -1814,6 +1831,8 @@
                         cvh.getParent() instanceof CoordinatorLayoutForPointer);
                 assertTrue("ContentFrameLayout should be the parent of CoodinatorLayoutForPointer",
                         cvh.getParent().getParent() instanceof ContentFrameLayout);
+                WindowManager.LayoutParams attrs = getActivity().getWindow().getAttributes();
+                assertNotEquals("The window should have non-zero y", 0, attrs.y);
                 eventHelper.notifyCalled();
             });
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
index 8467f4e..a3790ac 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
@@ -281,10 +281,8 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS,
-            ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
-    public void
-    acceptingSyncEndsFreAndEnablesSyncIfEnableSyncImmediatelyFeatureEnabled() {
+    @EnableFeatures({ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
+    public void acceptingSyncEndsFreAndEnablesSyncIfEnableSyncImmediatelyFeatureEnabled() {
         when(mExternalAuthUtilsMock.canUseGooglePlayServices(any())).thenReturn(true);
         mAccountManagerTestRule.addAccount(TEST_EMAIL);
         launchFirstRunActivityAndWaitForNativeInitialization();
@@ -317,10 +315,8 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS,
-            ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
-    public void
-    refusingSyncEndsFreAndDoesNotEnableSyncIfEnableSyncImmediatelyFeatureEnabled() {
+    @EnableFeatures({ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
+    public void refusingSyncEndsFreAndDoesNotEnableSyncIfEnableSyncImmediatelyFeatureEnabled() {
         mAccountManagerTestRule.addAccount(TEST_EMAIL);
         launchFirstRunActivityAndWaitForNativeInitialization();
         waitUntilCurrentPageIs(SigninFirstRunFragment.class);
@@ -353,8 +349,7 @@
 
     @Test
     @MediumTest
-    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS,
-            ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
+    @EnableFeatures({ChromeFeatureList.ENABLE_SYNC_IMMEDIATELY_IN_FRE})
     @DisabledTest(message = "https://crbug.com/1335094")
     public void
     clickingSettingsEndsFreAndStartsEnablingSyncIfEnableSyncImmediatelyFeatureEnabled() {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategyTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategyTest.java
index 126b81e..37f5a91 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategyTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategyTest.java
@@ -21,6 +21,7 @@
 
 import android.animation.Animator.AnimatorListener;
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Looper;
@@ -39,6 +40,7 @@
 import android.widget.LinearLayout;
 
 import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
+import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.After;
 import org.junit.Before;
@@ -48,13 +50,17 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.ParameterizedRobolectricTestRunner;
+import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
+import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.LooperMode.Mode;
 import org.robolectric.shadows.ShadowLog;
 
 import org.chromium.base.Callback;
-import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.customtabs.PartialCustomTabHeightStrategy.PartialCustomTabHandleStrategy;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -63,10 +69,12 @@
 import org.chromium.chrome.test.util.browser.Features;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 /** Tests for {@link PartialCustomTabHandleStrategy}. */
-@RunWith(BaseRobolectricTestRunner.class)
+@RunWith(ParameterizedRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 @Features.EnableFeatures({ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES,
         ChromeFeatureList.CCT_RESIZABLE_ALLOW_RESIZE_BY_USER_GESTURE})
@@ -81,6 +89,18 @@
 
     private static final int NAVBAR_HEIGHT = 160;
     private static final int MAX_INIT_POS = DEVICE_HEIGHT / 2;
+    private static final int INITIAL_HEIGHT = DEVICE_HEIGHT / 2 - NAVBAR_HEIGHT;
+    private static final int FULL_HEIGHT = DEVICE_HEIGHT - NAVBAR_HEIGHT;
+
+    @Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {{true}, {false}});
+    }
+
+    // Parameterize the internal implementation. 'Fixed window' type does not resize the Window
+    // but the WebContents, while 'Window above navbar' type dynamically resizes the Window.
+    @Parameter(0)
+    public boolean mWindowAboveNavbar;
 
     @Mock
     private Activity mActivity;
@@ -125,7 +145,10 @@
     private ViewGroup mCoordinatorLayout;
     @Mock
     private View mDragBar;
+    @Mock
+    private CommandLine mCommandLine;
 
+    private Context mContext;
     private List<WindowManager.LayoutParams> mAttributeResults;
     private DisplayMetrics mRealMetrics;
     private Callback<Integer> mBottomInsetCallback = inset -> {};
@@ -187,6 +210,9 @@
         })
                 .when(mDisplay)
                 .getRealMetrics(any(DisplayMetrics.class));
+        mContext = ApplicationProvider.getApplicationContext();
+        ContextUtils.initApplicationContextForTests(mContext);
+        CommandLine.setInstanceForTesting(mCommandLine);
     }
 
     @After
@@ -198,6 +224,7 @@
     private PartialCustomTabHeightStrategy createPcctAtHeight(int heightPx) {
         PartialCustomTabHeightStrategy pcct = new PartialCustomTabHeightStrategy(
                 mActivity, heightPx, null, null, mOnResizedCallback, mActivityLifecycleDispatcher);
+        pcct.setWindowAboveNavbarForTesting(mWindowAboveNavbar);
         pcct.setMockViewForTesting(
                 mNavbar, mSpinnerView, mSpinner, mToolbarView, mToolbarCoordinator);
         return pcct;
@@ -209,7 +236,7 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
     }
 
     @Test
@@ -218,7 +245,7 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(0, mAttributeResults.get(0).y);
+        assertTabIsFullHeight(mAttributeResults.get(0));
     }
 
     @Test
@@ -227,7 +254,7 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(0, mAttributeResults.get(0).y);
+        assertTabIsFullHeight(mAttributeResults.get(0));
     }
 
     @Test
@@ -264,9 +291,10 @@
      * Simulate dragging the tab and lifting the finger at the end.
      * @param handleStrategy {@link PartialCustomTabHandleStrategy} object.
      * @param ypos Series of y positions simulating the events.
-     * @return Y position of the tab after the dragging finishes.
+     * @return Window attributes after the dragging finishes.
      */
-    private int dragTab(PartialCustomTabHandleStrategy handleStrategy, int... ypos) {
+    private WindowManager.LayoutParams dragTab(
+            PartialCustomTabHandleStrategy handleStrategy, int... ypos) {
         int npos = ypos.length;
         assert npos >= 2;
         long timestamp = SystemClock.uptimeMillis();
@@ -281,7 +309,7 @@
 
         int length = mAttributeResults.size();
         assertTrue(length > 1);
-        return mAttributeResults.get(length - 1).y;
+        return mAttributeResults.get(length - 1);
     }
 
     private void assertMotionEventIgnored(PartialCustomTabHandleStrategy handleStrategy) {
@@ -289,6 +317,22 @@
                 event(SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1500)));
     }
 
+    private void assertTabIsAtInitialPos(WindowManager.LayoutParams attrs) {
+        if (mWindowAboveNavbar) {
+            assertEquals(INITIAL_HEIGHT, attrs.height);
+        } else {
+            assertEquals(MAX_INIT_POS, attrs.y);
+        }
+    }
+
+    private void assertTabIsFullHeight(WindowManager.LayoutParams attrs) {
+        if (mWindowAboveNavbar) {
+            assertEquals(FULL_HEIGHT, attrs.height);
+        } else {
+            assertEquals(0, attrs.y);
+        }
+    }
+
     private void disableSpinnerAnimation() {
         // Disable animation for the mock spinner view.
         doAnswer(invocation -> {
@@ -307,7 +351,7 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         // Pass null because we have a mock Activity and we don't depend on the GestureDetector
         // inside as we test MotionEvents directly.
@@ -315,13 +359,13 @@
                 strategy.new PartialCustomTabHandleStrategy(null);
 
         // Drag to the top.
-        assertEquals(0, dragTab(handleStrategy, 1500, 1000, 500));
+        assertTabIsFullHeight(dragTab(handleStrategy, 1500, 1000, 500));
 
         // Drag down a little -> slide back to the top.
-        assertEquals(0, dragTab(handleStrategy, 50, 100, 150));
+        assertTabIsFullHeight(dragTab(handleStrategy, 50, 100, 150));
 
         // Drag down enough -> slide to the initial position.
-        assertEquals(MAX_INIT_POS, dragTab(handleStrategy, 50, 650, 1300));
+        assertTabIsAtInitialPos(dragTab(handleStrategy, 50, 650, 1300));
     }
 
     @Test
@@ -330,16 +374,17 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         PartialCustomTabHandleStrategy handleStrategy =
                 strategy.new PartialCustomTabHandleStrategy(null);
 
         // Drag up slightly -> slide back to the initial height.
-        assertEquals(MAX_INIT_POS, dragTab(handleStrategy, 1500, 1450, 1400));
+        assertTabIsAtInitialPos(dragTab(handleStrategy, 1500, 1450, 1400));
 
         // Drag down slightly -> slide back to the initial height.
-        assertEquals(MAX_INIT_POS, dragTab(handleStrategy, 1500, 1550, 1600));
+        assertTabIsAtInitialPos(dragTab(handleStrategy, 1500, 1550, 1600));
     }
 
     @Test
@@ -350,13 +395,13 @@
         verify(mWindow).clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         PartialCustomTabHandleStrategy handleStrategy =
                 strategy.new PartialCustomTabHandleStrategy(null);
 
         // Shake the tab from the initial position slightly -> back to the initial height.
-        assertEquals(MAX_INIT_POS, dragTab(handleStrategy, 1500, 1450, 1600));
+        assertTabIsAtInitialPos(dragTab(handleStrategy, 1500, 1450, 1600));
     }
 
     @Test
@@ -395,6 +440,9 @@
 
     @Test
     public void rotateToLandescapeHideCustomNavbar() {
+        // Custom navigation bar is drawn only on 'Fixed Window'-type implementation.
+        if (mWindowAboveNavbar) return;
+
         PartialCustomTabHeightStrategy strategy = createPcctAtHeight(800);
 
         mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -447,14 +495,14 @@
         verifyWindowFlagsSet();
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         PartialCustomTabHandleStrategy handleStrategy =
                 strategy.new PartialCustomTabHandleStrategy(null);
         final boolean[] closed = {false};
         handleStrategy.setCloseClickHandler(() -> closed[0] = true);
 
-        dragTab(handleStrategy, MAX_INIT_POS, DEVICE_HEIGHT - 400);
+        dragTab(handleStrategy, INITIAL_HEIGHT, DEVICE_HEIGHT - 400);
         assertTrue("Close click handler should be called.", closed[0]);
     }
 
@@ -466,7 +514,7 @@
         verify(mWindow).clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         when(mSpinnerView.getVisibility()).thenReturn(View.GONE);
 
@@ -505,7 +553,7 @@
         verify(mWindow).clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         when(mSpinnerView.getVisibility()).thenReturn(View.GONE);
 
@@ -540,7 +588,7 @@
         verify(mWindow).clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
 
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         when(mSpinnerView.getVisibility()).thenReturn(View.GONE);
 
@@ -548,8 +596,8 @@
                 strategy.new PartialCustomTabHandleStrategy(null);
 
         long timestamp = SystemClock.uptimeMillis();
-        actionDown(handleStrategy, timestamp, 1500);
-        actionMove(handleStrategy, timestamp, 1450);
+        actionDown(handleStrategy, timestamp, INITIAL_HEIGHT - 100);
+        actionMove(handleStrategy, timestamp, INITIAL_HEIGHT - 150);
 
         // Verify the spinner is visible.
         verify(mSpinnerView).setVisibility(View.VISIBLE);
@@ -557,7 +605,7 @@
         clearInvocations(mSpinnerView);
 
         // Drag below the initial height.
-        actionMove(handleStrategy, timestamp, MAX_INIT_POS + 100);
+        actionMove(handleStrategy, timestamp, INITIAL_HEIGHT + 100);
 
         // Verify the spinner goes invisible.
         verify(mSpinnerView).setVisibility(View.GONE);
@@ -567,7 +615,7 @@
     public void expandToFullHeightOnShowingKeyboard() {
         PartialCustomTabHeightStrategy strategy = createPcctAtHeight(500);
         assertEquals(1, mAttributeResults.size());
-        assertEquals(MAX_INIT_POS, mAttributeResults.get(0).y);
+        assertTabIsAtInitialPos(mAttributeResults.get(0));
 
         strategy.onShowSoftInput();
         shadowOf(Looper.getMainLooper()).idle();
@@ -576,7 +624,7 @@
         assertTrue(length > 1);
 
         // Verify that the tab expands to full height.
-        assertEquals(0, mAttributeResults.get(length - 1).y);
+        assertTabIsFullHeight(mAttributeResults.get(length - 1));
     }
 
     private void verifyWindowFlagsSet() {
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 02b93e9dc..17cf8d0 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1557,15 +1557,6 @@
     {" - tabs don't shrink", kMinimumTabWidthSettingFull,
      std::size(kMinimumTabWidthSettingFull), nullptr}};
 
-const FeatureEntry::FeatureParam kAlsoShowMediaTabsinOpenTabsSection[] = {
-    {features::kTabSearchAlsoShowMediaTabsinOpenTabsSectionParameterName,
-     "true"}};
-
-const FeatureEntry::FeatureVariation kTabSearchMediaTabsVariations[] = {
-    {" - media tabs also shown in open tabs section",
-     kAlsoShowMediaTabsinOpenTabsSection,
-     std::size(kAlsoShowMediaTabsinOpenTabsSection), nullptr}};
-
 const FeatureEntry::FeatureParam kTabSearchSearchThresholdSmall[] = {
     {features::kTabSearchSearchThresholdName, "0.3"}};
 const FeatureEntry::FeatureParam kTabSearchSearchThresholdMedium[] = {
@@ -2063,54 +2054,28 @@
 };
 
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_SingleSurface[] = {
-    {"start_surface_variation", "single"},
+    {"open_ntp_instead_of_start", "false"},
+    {"show_last_active_tab_only", "false"},
     {"show_tabs_in_mru_order", "true"}};
 
-const FeatureEntry::FeatureParam kStartSurfaceAndroid_SingleSurface_V2[] = {
-    {"start_surface_variation", "single"},
-    {"show_last_active_tab_only", "true"},
-    {"open_ntp_instead_of_start", "true"}};
-
-const FeatureEntry::FeatureParam kStartSurfaceAndroid_SingleSurfaceSingleTab[] =
-    {{"start_surface_variation", "single"},
-     {"show_last_active_tab_only", "true"},
-     {"hide_switch_when_no_incognito_tabs", "true"}};
-
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_CandidateA[] = {
-    {"start_surface_variation", "single"},
-    {"show_last_active_tab_only", "true"},
-    {"hide_switch_when_no_incognito_tabs", "true"},
-    {"tab_count_button_on_start_surface", "true"}};
+    {"open_ntp_instead_of_start", "false"}};
 
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_CandidateA_SyncCheck[] = {
-    {"start_surface_variation", "single"},
-    {"show_last_active_tab_only", "true"},
-    {"hide_switch_when_no_incognito_tabs", "true"},
-    {"tab_count_button_on_start_surface", "true"},
+    {"open_ntp_instead_of_start", "false"},
     {"check_sync_before_show_start_at_startup", "true"}};
 
 const FeatureEntry::FeatureParam
     kStartSurfaceAndroid_CandidateA_SigninPromoTimeLimit[] = {
-        {"start_surface_variation", "single"},
-        {"show_last_active_tab_only", "true"},
-        {"hide_switch_when_no_incognito_tabs", "true"},
-        {"tab_count_button_on_start_surface", "true"},
+        {"open_ntp_instead_of_start", "false"},
         {"sign_in_promo_show_since_last_background_limit_ms", "30000"}};
 
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_CandidateB[] = {
-    {"start_surface_variation", "single"},
-    {"show_last_active_tab_only", "true"},
-    {"hide_switch_when_no_incognito_tabs", "true"},
-    {"tab_count_button_on_start_surface", "true"},
     {"open_ntp_instead_of_start", "true"}};
 
 const FeatureEntry::FeatureParam
     kStartSurfaceAndroid_CandidateB_AlwaysShowIncognito[] = {
-        {"start_surface_variation", "single"},
-        {"show_last_active_tab_only", "true"},
-        {"hide_switch_when_no_incognito_tabs", "false"},
-        {"tab_count_button_on_start_surface", "true"},
-        {"open_ntp_instead_of_start", "true"}};
+        {"hide_switch_when_no_incognito_tabs", "false"}};
 
 const FeatureEntry::FeatureVariation kStartSurfaceAndroidVariations[] = {
     {"Candidate A", kStartSurfaceAndroid_CandidateA,
@@ -2127,10 +2092,6 @@
      std::size(kStartSurfaceAndroid_CandidateB_AlwaysShowIncognito), nullptr},
     {"Single Surface", kStartSurfaceAndroid_SingleSurface,
      std::size(kStartSurfaceAndroid_SingleSurface), nullptr},
-    {"Single Surface V2", kStartSurfaceAndroid_SingleSurface_V2,
-     std::size(kStartSurfaceAndroid_SingleSurface_V2), nullptr},
-    {"Single Surface + Single Tab", kStartSurfaceAndroid_SingleSurfaceSingleTab,
-     std::size(kStartSurfaceAndroid_SingleSurfaceSingleTab), nullptr},
 };
 
 const FeatureEntry::FeatureParam kFeedPositionAndroid_push_down_feed_small[] = {
@@ -6205,6 +6166,10 @@
          chrome::android::kCCTResizableForThirdParties,
          kCCTResizableThirdPartiesDefaultPolicyVariations,
          "CCTResizableThirdPartiesDefaultPolicy")},
+    {"cct-resizable-window-above-navbar",
+     flag_descriptions::kCCTResizableWindowAboveNavbarName,
+     flag_descriptions::kCCTResizableWindowAboveNavbarDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kCCTResizableWindowAboveNavbar)},
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
@@ -8018,13 +7983,6 @@
                                     kDrawPredictedPointVariations,
                                     "DrawPredictedInkPoint")},
 
-    {flag_descriptions::kTabSearchMediaTabsId,
-     flag_descriptions::kTabSearchMediaTabsName,
-     flag_descriptions::kTabSearchMediaTabsDescription, kOsDesktop,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(features::kTabSearchMediaTabs,
-                                    kTabSearchMediaTabsVariations,
-                                    "TabSearchMediaTabs")},
-
 #if BUILDFLAG(IS_ANDROID)
     {"optimization-guide-push-notifications",
      flag_descriptions::kOptimizationGuidePushNotificationName,
diff --git a/chrome/browser/android/history_report/data_observer.cc b/chrome/browser/android/history_report/data_observer.cc
index 9da2cbc..c39e286 100644
--- a/chrome/browser/android/history_report/data_observer.cc
+++ b/chrome/browser/android/history_report/data_observer.cc
@@ -52,7 +52,8 @@
 
 void DataObserver::BookmarkNodeAdded(BookmarkModel* model,
                                      const BookmarkNode* parent,
-                                     size_t index) {
+                                     size_t index,
+                                     bool added_by_user) {
   delta_file_service_->PageAdded(parent->children()[index]->url());
   data_changed_callback_.Run();
 }
diff --git a/chrome/browser/android/history_report/data_observer.h b/chrome/browser/android/history_report/data_observer.h
index 1483daff..dce37315 100644
--- a/chrome/browser/android/history_report/data_observer.h
+++ b/chrome/browser/android/history_report/data_observer.h
@@ -54,7 +54,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.h b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
index 3ff776b..3aa0f14 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service.h
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
@@ -38,12 +38,9 @@
   void TryRestoreMostRecentlyUsedSession();
 
  private:
-  // Virtual for testing.
-  virtual const sync_sessions::SyncedSession*
-  GetMostRecentlyUsedRemoteSession();
+  const sync_sessions::SyncedSession* GetMostRecentlyUsedRemoteSession();
 
-  // Virtual for testing.
-  virtual const sync_sessions::SyncedSession* GetLocalSession();
+  const sync_sessions::SyncedSession* GetLocalSession();
 
   // Virtual for testing.
   virtual void RestoreForeignSessionWindows(
@@ -52,7 +49,8 @@
   // Virtual for testing.
   virtual void RestoreLocalSessionWindows();
 
-  sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate();
+  // Virtual for testing.
+  virtual sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate();
 
   Profile* const profile_;
 
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
index 64bc034..c2b388b 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
@@ -3,17 +3,22 @@
 // found in the LICENSE file.
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/ranges/algorithm.h"
 #include "base/time/time.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/sync_sessions/open_tabs_ui_delegate.h"
 #include "components/sync_sessions/synced_session.h"
 #include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 constexpr char local_session_name[] = "local_session";
-constexpr char remote_session_name[] = "remote_session";
+constexpr char remote_session_1_name[] = "remote_session_1";
+constexpr char remote_session_2_name[] = "remote_session_2";
+const base::Time most_recent_time = base::Time::FromDoubleT(15);
 const base::Time more_recent_time = base::Time::FromDoubleT(10);
-const base::Time less_recent_time = base::Time::FromDoubleT(5);
+const base::Time least_recent_time = base::Time::FromDoubleT(5);
 std::unique_ptr<sync_sessions::SyncedSession> CreateNewSession(
     const std::string& session_name,
     const base::Time& session_time) {
@@ -22,51 +27,107 @@
   session->modified_time = session_time;
   return session;
 }
+
+class MockOpenTabsUIDelegate : public sync_sessions::OpenTabsUIDelegate {
+ public:
+  MockOpenTabsUIDelegate() = default;
+
+  bool GetAllForeignSessions(
+      std::vector<const sync_sessions::SyncedSession*>* sessions) override {
+    *sessions = foreign_sessions_;
+    base::ranges::sort(*sessions, std::greater(),
+                       [](const sync_sessions::SyncedSession* session) {
+                         return session->modified_time;
+                       });
+
+    return !sessions->empty();
+  }
+
+  bool GetLocalSession(
+      const sync_sessions::SyncedSession** local_session) override {
+    *local_session = local_session_;
+    return *local_session != nullptr;
+  }
+
+  void SetForeignSessionsForTesting(
+      std::vector<const sync_sessions::SyncedSession*> foreign_sessions) {
+    foreign_sessions_ = foreign_sessions;
+  }
+
+  void SetLocalSessionForTesting(sync_sessions::SyncedSession* local_session) {
+    local_session_ = local_session;
+  }
+
+  MOCK_METHOD3(GetForeignTab,
+               bool(const std::string& tag,
+                    const SessionID tab_id,
+                    const sessions::SessionTab** tab));
+
+  MOCK_METHOD1(DeleteForeignSession, void(const std::string& tag));
+
+  MOCK_METHOD2(GetForeignSession,
+               bool(const std::string& tag,
+                    std::vector<const sessions::SessionWindow*>* windows));
+
+  MOCK_METHOD2(GetForeignSessionTabs,
+               bool(const std::string& tag,
+                    std::vector<const sessions::SessionTab*>* tabs));
+
+ private:
+  std::vector<const sync_sessions::SyncedSession*> foreign_sessions_;
+  sync_sessions::SyncedSession* local_session_ = nullptr;
+};
+
 }  // namespace
 
 namespace ash {
 class TestFloatingWorkSpaceService : public ash::FloatingWorkspaceService {
  public:
   explicit TestFloatingWorkSpaceService(TestingProfile* profile)
-      : ash::FloatingWorkspaceService(profile) {}
-  void RestoreLocalSessionWindows() override {
-    restored_session_ = GetLocalSession();
+      : ash::FloatingWorkspaceService(profile) {
+    mock_open_tabs_ = std::make_unique<MockOpenTabsUIDelegate>();
   }
+
+  void RestoreLocalSessionWindows() override {
+    mock_open_tabs_->GetLocalSession(&restored_session_);
+  }
+
   void RestoreForeignSessionWindows(
       const sync_sessions::SyncedSession* session) override {
     restored_session_ = session;
   }
-  const sync_sessions::SyncedSession* GetLocalSession() override {
-    return local_session_;
-  }
-  const sync_sessions::SyncedSession* GetMostRecentlyUsedRemoteSession()
-      override {
-    return most_recently_used_remote_session_;
-  }
+
   const sync_sessions::SyncedSession* GetRestoredSession() {
     return restored_session_;
   }
-  void SetLocalSessionForTesting(const sync_sessions::SyncedSession* session) {
-    local_session_ = session;
+
+  void SetLocalSessionForTesting(sync_sessions::SyncedSession* session) {
+    mock_open_tabs_->SetLocalSessionForTesting(session);
   }
-  void SetMostRecentlyUsedRemoteSession(
-      const sync_sessions::SyncedSession* session) {
-    most_recently_used_remote_session_ = session;
+
+  void SetForeignSessionForTesting(
+      std::vector<const sync_sessions::SyncedSession*> foreign_sessions) {
+    mock_open_tabs_->SetForeignSessionsForTesting(foreign_sessions);
   }
 
  private:
+  sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate() override {
+    return mock_open_tabs_.get();
+  }
   const sync_sessions::SyncedSession* restored_session_ = nullptr;
-  const sync_sessions::SyncedSession* local_session_ = nullptr;
-  const sync_sessions::SyncedSession* most_recently_used_remote_session_ =
-      nullptr;
+  std::unique_ptr<MockOpenTabsUIDelegate> mock_open_tabs_;
 };
 
 class FloatingWorkspaceServiceTest : public testing::Test {
  public:
   FloatingWorkspaceServiceTest() = default;
+
   ~FloatingWorkspaceServiceTest() override = default;
+
   TestingProfile* profile() const { return profile_.get(); }
+
   const base::TimeDelta GetMaxRestoreTime() { return max_restore_time_; }
+
   void SetUp() override {
     TestingProfile::Builder profile_builder;
     base::ScopedTempDir temp_dir;
@@ -85,15 +146,22 @@
 
 TEST_F(FloatingWorkspaceServiceTest, RestoreRemoteSession) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
-  // Remote session is more recent at the beginning.
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, less_recent_time);
-  std::unique_ptr<sync_sessions::SyncedSession> remote_session =
-      CreateNewSession(remote_session_name, more_recent_time);
+      CreateNewSession(local_session_name, more_recent_time);
+  std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
+  // This remote session has most recent timestamp and should be restored.
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      most_recent_remote_session =
+          CreateNewSession(remote_session_1_name, most_recent_time);
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      less_recent_remote_session =
+          CreateNewSession(remote_session_2_name, least_recent_time);
+  foreign_sessions.push_back(less_recent_remote_session.get());
+  foreign_sessions.push_back(most_recent_remote_session.get());
+
   test_floating_workspace_service.SetLocalSessionForTesting(
       local_session.get());
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(
-      remote_session.get());
+  test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin
@@ -102,24 +170,31 @@
       FROM_HERE, run_loop.QuitClosure(), GetMaxRestoreTime());
   run_loop.Run();
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
-  EXPECT_EQ(remote_session_name,
+  EXPECT_EQ(remote_session_1_name,
             test_floating_workspace_service.GetRestoredSession()->session_name);
 }
 
 TEST_F(FloatingWorkspaceServiceTest, RestoreLocalSession) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
-  // Local session is more recent.
+  // Local session has most recent timestamp and should be restored.
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, more_recent_time);
-  std::unique_ptr<sync_sessions::SyncedSession> remote_session =
-      CreateNewSession(remote_session_name, less_recent_time);
+      CreateNewSession(local_session_name, most_recent_time);
+  std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      most_recent_remote_session =
+          CreateNewSession(remote_session_1_name, more_recent_time);
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      less_recent_remote_session =
+          CreateNewSession(remote_session_2_name, least_recent_time);
+  foreign_sessions.push_back(less_recent_remote_session.get());
+  foreign_sessions.push_back(most_recent_remote_session.get());
+
   test_floating_workspace_service.SetLocalSessionForTesting(
       local_session.get());
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(
-      remote_session.get());
+  test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
-  // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin.
+  // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin
   base::RunLoop run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(), GetMaxRestoreTime());
@@ -131,16 +206,22 @@
 
 TEST_F(FloatingWorkspaceServiceTest, RestoreRemoteSessionAfterUpdated) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
-  // Local session is more recent at the beginning
-  // but updated remote session within 3 seconds is more recent.
+  // Local session has most recent timestamp and should be restored.
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, more_recent_time);
-  std::unique_ptr<sync_sessions::SyncedSession> remote_session =
-      CreateNewSession(remote_session_name, less_recent_time);
+      CreateNewSession(local_session_name, most_recent_time);
+  std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      most_recent_remote_session =
+          CreateNewSession(remote_session_1_name, more_recent_time);
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      less_recent_remote_session =
+          CreateNewSession(remote_session_2_name, least_recent_time);
+  foreign_sessions.push_back(less_recent_remote_session.get());
+  foreign_sessions.push_back(most_recent_remote_session.get());
+
   test_floating_workspace_service.SetLocalSessionForTesting(
       local_session.get());
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(
-      remote_session.get());
+  test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin
@@ -151,28 +232,34 @@
   first_run_loop.Run();
   // Remote session got updated during the 3 second delay of dispatching task
   // and updated remote session is most recent.
-  base::Time remote_session_updated_time = more_recent_time + base::Seconds(5);
-  remote_session =
-      CreateNewSession(remote_session_name, remote_session_updated_time);
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(
-      remote_session.get());
+  base::Time remote_session_updated_time = most_recent_time + base::Seconds(5);
+  std::vector<const sync_sessions::SyncedSession*> updated_foreign_sessions;
+  // Now previously less recent remote session becomes most recent
+  // and should be restored.
+  less_recent_remote_session->modified_time = remote_session_updated_time;
   base::RunLoop second_run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, second_run_loop.QuitClosure(),
       GetMaxRestoreTime() - first_run_loop_delay_time);
   second_run_loop.Run();
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
-  EXPECT_EQ(remote_session_name,
+  EXPECT_EQ(less_recent_remote_session->session_name,
             test_floating_workspace_service.GetRestoredSession()->session_name);
 }
 
 TEST_F(FloatingWorkspaceServiceTest, NoLocalSession) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
-  std::unique_ptr<sync_sessions::SyncedSession> remote_session =
-      CreateNewSession(remote_session_name, less_recent_time);
-  test_floating_workspace_service.SetLocalSessionForTesting(nullptr);
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(
-      remote_session.get());
+  std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      most_recent_remote_session =
+          CreateNewSession(remote_session_1_name, more_recent_time);
+  const std::unique_ptr<sync_sessions::SyncedSession>
+      less_recent_remote_session =
+          CreateNewSession(remote_session_2_name, least_recent_time);
+  foreign_sessions.push_back(less_recent_remote_session.get());
+  foreign_sessions.push_back(most_recent_remote_session.get());
+  test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
+
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin
@@ -181,16 +268,16 @@
       FROM_HERE, run_loop.QuitClosure(), GetMaxRestoreTime());
   run_loop.Run();
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
-  EXPECT_EQ(remote_session_name,
+  EXPECT_EQ(most_recent_remote_session->session_name,
             test_floating_workspace_service.GetRestoredSession()->session_name);
 }
+
 TEST_F(FloatingWorkspaceServiceTest, NoRemoteSession) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, less_recent_time);
+      CreateNewSession(local_session_name, least_recent_time);
   test_floating_workspace_service.SetLocalSessionForTesting(
       local_session.get());
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(nullptr);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin
@@ -205,8 +292,6 @@
 
 TEST_F(FloatingWorkspaceServiceTest, NoSession) {
   TestFloatingWorkSpaceService test_floating_workspace_service(profile());
-  test_floating_workspace_service.SetLocalSessionForTesting(nullptr);
-  test_floating_workspace_service.SetMostRecentlyUsedRemoteSession(nullptr);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for 3 seconds which is kMaxTimeAvailableForRestoreAfterLogin.
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.cc
new file mode 100644
index 0000000..8696b9a
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.cc
@@ -0,0 +1,47 @@
+// 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/policy/reporting/metrics_reporting/metric_browsertest_utils.h"
+
+#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
+#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+
+namespace ash::reporting {
+
+MetricBrowserTestBase::MetricBrowserTestBase() {
+  device_state_.set_skip_initial_policy_setup(true);
+}
+
+MetricBrowserTestBase::~MetricBrowserTestBase() = default;
+
+void MetricBrowserTestBase::OnCoreConnected(policy::CloudPolicyCore* core) {
+  run_loop_->Quit();
+  mock_task_runner_ =
+      std::make_unique<base::ScopedMockTimeMessageLoopTaskRunner>();
+}
+
+void MetricBrowserTestBase::OnRefreshSchedulerStarted(
+    policy::CloudPolicyCore* core) {}
+
+void MetricBrowserTestBase::OnCoreDisconnecting(policy::CloudPolicyCore* core) {
+}
+
+void MetricBrowserTestBase::SetUpDelayedInitialization() {
+  auto* browser_policy_manager = g_browser_process->platform_part()
+                                     ->browser_policy_connector_ash()
+                                     ->GetDeviceCloudPolicyManager();
+  run_loop_ = std::make_unique<base::RunLoop>();
+  browser_policy_manager->core()->AddObserver(this);
+  device_state_.RequestDevicePolicyUpdate();
+  run_loop_->Run();
+  mock_task_runner_->task_runner()->FastForwardBy(
+      ::reporting::metrics::kInitDelay);
+  browser_policy_manager->core()->RemoveObserver(this);
+  // Destroy the mock task runner. The test can create its own
+  // ScopedMockTimeMessageLoopTaskRunner if it requires further time control.
+  mock_task_runner_.reset();
+}
+
+}  // namespace ash::reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.h b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.h
new file mode 100644
index 0000000..5b53425
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.h
@@ -0,0 +1,55 @@
+// 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_POLICY_REPORTING_METRICS_REPORTING_METRIC_BROWSERTEST_UTILS_H_
+#define CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_BROWSERTEST_UTILS_H_
+
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
+#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "content/public/test/browser_test.h"
+
+namespace ash::reporting {
+
+// The base class of metric browser tests.
+
+// One main challenge of the metric browser tests is the delayed initialization
+// (which enqueues info metric) by |MetricReportingManager|: It must be called
+// after |MissiveClientTestObserver| has been set up (so setting delayed
+// initialization time to zero won't work) but before the rest of the test body.
+// Therefore, some mock time manipulation is required. Due to the complexity of
+// mocking time in browser tests, this class addresses the issue as follows:
+//
+// - In its constructor, initial policy setup is skipped. This prevents delayed
+// initialization task from being posted.
+// - A method |SetUpDelayedInitialization| is provided. This should be called
+// after |MissiveClientTestObserver| has been initialized and device settings
+// have been set.
+//
+// Check out network/network_info_sampler_browsertest.cc for an example.
+class MetricBrowserTestBase : public policy::DevicePolicyCrosBrowserTest,
+                              public policy::CloudPolicyCore::Observer {
+ protected:
+  MetricBrowserTestBase();
+  ~MetricBrowserTestBase() override;
+
+  // Called after the core is connected.
+  void OnCoreConnected(policy::CloudPolicyCore* core) override;
+  // Called after the refresh scheduler is started.
+  void OnRefreshSchedulerStarted(policy::CloudPolicyCore* core) override;
+  // Called before the core is disconnected.
+  void OnCoreDisconnecting(policy::CloudPolicyCore* core) override;
+
+  // Run |MetricReportingManager::DelayedInit| by advancing the mock clock.
+  void SetUpDelayedInitialization();
+
+ private:
+  std::unique_ptr<base::ScopedMockTimeMessageLoopTaskRunner> mock_task_runner_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+};
+}  // namespace ash::reporting
+
+#endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_BROWSERTEST_UTILS_H_
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_info_sampler_browsertest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_info_sampler_browsertest.cc
new file mode 100644
index 0000000..4749309
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_info_sampler_browsertest.cc
@@ -0,0 +1,72 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/components/settings/cros_settings_names.h"
+#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
+#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.h"
+#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
+#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
+#include "chromeos/dbus/missive/missive_client_test_observer.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "components/reporting/proto/synced/metric_data.pb.h"
+#include "components/reporting/proto/synced/record.pb.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "content/public/test/browser_test.h"
+
+namespace ash::reporting {
+
+namespace {
+
+using ::chromeos::MissiveClientTestObserver;
+using ::reporting::Destination;
+using ::reporting::MetricData;
+using ::reporting::Priority;
+using ::testing::Eq;
+using ::testing::NotNull;
+
+class NetworkInfoSamplerBrowserTest : public MetricBrowserTestBase {
+ protected:
+  NetworkInfoSamplerBrowserTest() = default;
+  ~NetworkInfoSamplerBrowserTest() override = default;
+  ScopedTestingCrosSettings scoped_testing_cros_settings_;
+};
+
+IN_PROC_BROWSER_TEST_F(NetworkInfoSamplerBrowserTest,
+                       ReportNetworkInfoDefaultDevices) {
+  // Default network devices
+  scoped_testing_cros_settings_.device_settings()->SetBoolean(
+      kReportDeviceNetworkInterfaces, true);
+  MissiveClientTestObserver observer(Destination::INFO_METRIC);
+  // Start initialization after the observer is initialized.
+  SetUpDelayedInitialization();
+
+  // Indicates at least one network interface is available.
+  bool has_network_interfaces = false;
+  do {
+    // At least one record, otherwise this line would time out when the loop is
+    // entered for the first time.
+    auto [priority, record] = observer.GetNextEnqueuedRecord();
+    EXPECT_THAT(priority, Eq(Priority::SLOW_BATCH));
+    EXPECT_THAT(record.destination(), Eq(Destination::INFO_METRIC));
+    ::reporting::MetricData record_data;
+    ASSERT_TRUE(record_data.ParseFromString(record.data()));
+    EXPECT_TRUE(record_data.has_timestamp_ms());
+    EXPECT_TRUE(record_data.has_info_data());
+    EXPECT_FALSE(record_data.has_telemetry_data());
+
+    const auto& info_data = record_data.info_data();
+    if (info_data.has_networks_info()) {
+      const auto& networks_info = info_data.networks_info();
+      has_network_interfaces |= (networks_info.network_interfaces_size() > 0);
+    }
+  } while (!has_network_interfaces);
+
+  ASSERT_TRUE(has_network_interfaces)
+      << "No network interface is in any records.";
+}
+
+}  // namespace
+
+}  // namespace ash::reporting
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc
index cc7cbad..4bc99d06 100644
--- a/chrome/browser/bookmarks/android/bookmark_bridge.cc
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc
@@ -1257,7 +1257,8 @@
 
 void BookmarkBridge::BookmarkNodeAdded(BookmarkModel* model,
                                        const BookmarkNode* parent,
-                                       size_t index) {
+                                       size_t index,
+                                       bool added_by_user) {
   if (!IsLoaded())
     return;
 
diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser/bookmarks/android/bookmark_bridge.h
index bdacd62..aea7945 100644
--- a/chrome/browser/bookmarks/android/bookmark_bridge.h
+++ b/chrome/browser/bookmarks/android/bookmark_bridge.h
@@ -342,7 +342,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc b/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
index d3f327c..2295f0e 100644
--- a/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
+++ b/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
@@ -233,7 +233,7 @@
   base::Value::List updated;
   updated.Append(CreateFolder("Container", CreateTestTree()));
 
-  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(), _, _)).Times(5);
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(), _, _, _)).Times(5);
   // The remaining nodes have been pushed to positions 1 and 2; they'll both be
   // removed when at position 1.
   const BookmarkNode* parent = managed_->managed_node();
@@ -291,10 +291,10 @@
 TEST_F(ManagedBookmarkServiceTest, RemoveAllDoesntRemoveManaged) {
   EXPECT_EQ(2u, managed_->managed_node()->children().size());
 
-  EXPECT_CALL(observer_,
-              BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 0));
-  EXPECT_CALL(observer_,
-              BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 1));
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(),
+                                           model_->bookmark_bar_node(), 0, _));
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(),
+                                           model_->bookmark_bar_node(), 1, _));
   model_->AddURL(model_->bookmark_bar_node(), 0, u"Test",
                  GURL("http://google.com/"));
   model_->AddFolder(model_->bookmark_bar_node(), 1, u"Test Folder");
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index cfa6a3f..bbaf393 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -78,7 +78,6 @@
 #include "chromeos/startup/browser_init_params.h"
 #include "components/account_manager_core/account.h"
 #include "components/account_manager_core/account_manager_util.h"
-#include "components/signin/public/identity_manager/identity_test_utils.h"
 #endif
 
 using content::BrowserThread;
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index fb7da1181..aa29c8a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -147,7 +147,6 @@
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
 #include "chrome/browser/profiles/profile_android.h"
 #include "components/cdm/browser/media_drm_storage_impl.h"  // nogncheck crbug.com/1125897
-#include "components/feed/buildflags.h"
 #include "components/feed/core/v2/public/feed_service.h"
 #include "components/feed/feed_feature_list.h"
 #include "components/installedapp/android/jni_headers/PackageHash_jni.h"
@@ -170,7 +169,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/net/system_proxy_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
 #include "chromeos/ash/components/dbus/attestation/attestation_client.h"
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index c2992590..0c2dfbca 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -251,16 +251,16 @@
 #if BUILDFLAG(IS_MAC)
 #include <Security/Security.h>
 
+#if defined(ARCH_CPU_X86_64)
+#include "base/mac/mac_util.h"
+#include "base/threading/platform_thread.h"
+#endif  // defined(ARCH_CPU_X86_64)
+
 #include "chrome/browser/app_controller_mac.h"
 #include "chrome/browser/mac/keystone_glue.h"
 #include "chrome/browser/ui/ui_features.h"
 #endif  // BUILDFLAG(IS_MAC)
 
-// TODO(port): several win-only methods have been pulled out of this, but
-// BrowserMain() as a whole needs to be broken apart so that it's usable by
-// other platforms. For now, it's just a stub. This is a serious work in
-// progress and should not be taken as an indication of a real refactoring.
-
 #if BUILDFLAG(IS_WIN)
 #include "base/trace_event/trace_event_etw_export_win.h"
 #include "base/win/win_util.h"
@@ -1048,6 +1048,20 @@
         // BUILDFLAG(IS_OPENBSD)
 
 #if BUILDFLAG(IS_MAC)
+#if defined(ARCH_CPU_X86_64)
+  // The use of Rosetta to run the x64 version of Chromium on Arm is neither
+  // tested nor maintained, and there are reports of it crashing in weird ways
+  // (e.g. https://crbug.com/1305353). Warn the user if this is the case, as
+  // it's almost certainly accidental on their part.
+  if (base::mac::GetCPUType() == base::mac::CPUType::kTranslatedIntel) {
+    LOG(ERROR) << "The use of Rosetta to run the x64 version of Chromium on "
+                  "Arm is neither tested nor maintained, and unexpected "
+                  "behavior will likely result. Please check that all tools "
+                  "that spawn Chromium are Arm-native.";
+    base::PlatformThread::Sleep(base::Seconds(3));
+  }
+#endif  // defined(ARCH_CPU_X86_64)
+
   // Get the Keychain API to register for distributed notifications on the main
   // thread, which has a proper CFRunloop, instead of later on the I/O thread,
   // which doesn't. This ensures those notifications will get delivered
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 0fbdf34..164c1191 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3034,6 +3034,19 @@
                                                           accessing_origin);
 }
 
+bool ChromeContentBrowserClient::IsPrivateAggregationAllowed(
+    content::BrowserContext* browser_context,
+    const url::Origin& top_frame_origin,
+    const url::Origin& reporting_origin) {
+  Profile* profile = Profile::FromBrowserContext(browser_context);
+  auto* privacy_sandbox_settings =
+      PrivacySandboxSettingsFactory::GetForProfile(profile);
+  DCHECK(privacy_sandbox_settings);
+
+  return privacy_sandbox_settings->IsPrivateAggregationAllowed(
+      top_frame_origin, reporting_origin);
+}
+
 #if BUILDFLAG(IS_CHROMEOS)
 void ChromeContentBrowserClient::OnTrustAnchorUsed(
     content::BrowserContext* browser_context) {
@@ -3463,7 +3476,8 @@
       frame_name, disposition, features, user_gesture, opener_suppressed);
   NavigateParams nav_params =
       blocked_params.CreateNavigateParams(opener->GetProcess(), web_contents);
-  return blocked_content::MaybeBlockPopup(
+  return !blocked_content::ConsiderForPopupBlocking(disposition) ||
+         blocked_content::MaybeBlockPopup(
              web_contents, &opener_top_level_frame_url,
              std::make_unique<ChromePopupNavigationDelegate>(
                  std::move(nav_params)),
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 268836f..edaf7a0 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -313,6 +313,10 @@
   bool IsSharedStorageAllowed(content::BrowserContext* browser_context,
                               const url::Origin& top_frame_origin,
                               const url::Origin& accessing_origin) override;
+  bool IsPrivateAggregationAllowed(
+      content::BrowserContext* browser_context,
+      const url::Origin& top_frame_origin,
+      const url::Origin& reporting_origin) override;
 #if BUILDFLAG(IS_CHROMEOS)
   void OnTrustAnchorUsed(content::BrowserContext* browser_context) override;
 #endif
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index c65af2f..f9d320b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -3560,7 +3560,6 @@
     "../ui/webui/settings/ash/search/search_handler_unittest.cc",
     "../ui/webui/settings/ash/search/search_tag_registry_unittest.cc",
     "../ui/webui/settings/chromeos/bluetooth_handler_unittest.cc",
-    "../ui/webui/settings/chromeos/change_picture_handler_unittest.cc",
     "../ui/webui/settings/chromeos/device_keyboard_handler_unittest.cc",
     "../ui/webui/settings/chromeos/device_name_handler_unittest.cc",
     "../ui/webui/settings/chromeos/device_storage_handler_unittest.cc",
diff --git a/chrome/browser/component_updater/recovery_improved_component_installer.cc b/chrome/browser/component_updater/recovery_improved_component_installer.cc
index 454d3e1c..963f7ce4 100644
--- a/chrome/browser/component_updater/recovery_improved_component_installer.cc
+++ b/chrome/browser/component_updater/recovery_improved_component_installer.cc
@@ -28,7 +28,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
 #include "chrome/browser/component_updater/component_updater_utils.h"
 #include "components/services/unzip/content/unzip_service.h"
 #include "components/update_client/patcher.h"
diff --git a/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc b/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
index 100a404..6d9386ce 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
@@ -8,7 +8,6 @@
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/content_settings/sound_content_setting_observer.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
diff --git a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
index c366c1b1..4b65863 100644
--- a/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
+++ b/chrome/browser/dev_ui/android/dev_ui_loader_throttle.cc
@@ -62,6 +62,7 @@
          host == chrome::kChromeUIUserActionsHost ||
          host == chrome::kChromeUIWebApksHost ||
          host == chrome::kChromeUIWebRtcLogsHost ||
+         host == content::kChromeUIPrivateAggregationInternalsHost ||
          host == content::kChromeUIAttributionInternalsHost ||
          host == content::kChromeUIBlobInternalsHost ||
          host == content::kChromeUIGpuHost ||
diff --git a/chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos_unittest.cc b/chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos_unittest.cc
index 389387f..1310450 100644
--- a/chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos_unittest.cc
+++ b/chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos_unittest.cc
@@ -11,7 +11,6 @@
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
-#include "chrome/browser/device_identity/chromeos/device_oauth2_token_store_chromeos.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
diff --git a/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc b/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc
index 33287dd0..1ef29a9 100644
--- a/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc
+++ b/chrome/browser/extensions/api/bookmarks/bookmarks_api.cc
@@ -284,7 +284,8 @@
 
 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
                                             const BookmarkNode* parent,
-                                            size_t index) {
+                                            size_t index,
+                                            bool added_by_user) {
   const BookmarkNode* node = parent->children()[index].get();
   BookmarkTreeNode tree_node =
       bookmark_api_helpers::GetBookmarkTreeNode(managed_, node, false, false);
diff --git a/chrome/browser/extensions/api/bookmarks/bookmarks_api.h b/chrome/browser/extensions/api/bookmarks/bookmarks_api.h
index 9912ca1..fddfba66 100644
--- a/chrome/browser/extensions/api/bookmarks/bookmarks_api.h
+++ b/chrome/browser/extensions/api/bookmarks/bookmarks_api.h
@@ -65,7 +65,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/extensions/api/commands/command_service.cc b/chrome/browser/extensions/api/commands/command_service.cc
index 0dd3a1d..a3065da 100644
--- a/chrome/browser/extensions/api/commands/command_service.cc
+++ b/chrome/browser/extensions/api/commands/command_service.cc
@@ -74,23 +74,20 @@
 
 // Merge |suggested_key_prefs| into the saved preferences for the extension. We
 // merge rather than overwrite to preserve existing was_assigned preferences.
-void MergeSuggestedKeyPrefs(
-    const std::string& extension_id,
-    ExtensionPrefs* extension_prefs,
-    std::unique_ptr<base::DictionaryValue> suggested_key_prefs) {
-  const base::DictionaryValue* current_prefs;
-  if (extension_prefs->ReadPrefAsDictionary(extension_id,
-                                            kCommands,
-                                            &current_prefs)) {
-    std::unique_ptr<base::DictionaryValue> new_prefs =
-        base::DictionaryValue::From(
-            base::Value::ToUniquePtrValue(current_prefs->Clone()));
-    new_prefs->MergeDictionary(suggested_key_prefs.get());
+void MergeSuggestedKeyPrefs(const std::string& extension_id,
+                            ExtensionPrefs* extension_prefs,
+                            base::Value::Dict suggested_key_prefs) {
+  const base::Value::Dict* current_prefs =
+      extension_prefs->ReadPrefAsDict(extension_id, kCommands);
+  if (current_prefs) {
+    base::Value::Dict new_prefs = current_prefs->Clone();
+    new_prefs.Merge(std::move(suggested_key_prefs));
     suggested_key_prefs = std::move(new_prefs);
   }
 
-  extension_prefs->UpdateExtensionPref(extension_id, kCommands,
-                                       std::move(suggested_key_prefs));
+  extension_prefs->UpdateExtensionPref(
+      extension_id, kCommands,
+      std::make_unique<base::Value>(std::move(suggested_key_prefs)));
 }
 
 }  // namespace
@@ -223,12 +220,10 @@
   bindings->SetKey(key, std::move(keybinding));
 
   // Set the was_assigned pref for the suggested key.
-  std::unique_ptr<base::DictionaryValue> command_keys(
-      new base::DictionaryValue);
-  command_keys->SetBoolKey(kSuggestedKeyWasAssigned, true);
-  std::unique_ptr<base::DictionaryValue> suggested_key_prefs(
-      new base::DictionaryValue);
-  suggested_key_prefs->Set(command_name, std::move(command_keys));
+  base::Value::Dict command_keys;
+  command_keys.Set(kSuggestedKeyWasAssigned, true);
+  base::Value::Dict suggested_key_prefs;
+  suggested_key_prefs.Set(command_name, base::Value(std::move(command_keys)));
   MergeSuggestedKeyPrefs(extension_id, ExtensionPrefs::Get(profile_),
                          std::move(suggested_key_prefs));
 
@@ -503,18 +498,16 @@
 
 void CommandService::UpdateExtensionSuggestedCommandPrefs(
     const Extension* extension) {
-  std::unique_ptr<base::DictionaryValue> suggested_key_prefs(
-      new base::DictionaryValue);
+  base::Value::Dict suggested_key_prefs;
 
   const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
   if (commands) {
     for (auto iter = commands->cbegin(); iter != commands->cend(); ++iter) {
       const Command command = iter->second;
-      std::unique_ptr<base::DictionaryValue> command_keys(
-          new base::DictionaryValue);
-      command_keys->SetStringKey(
-          kSuggestedKey, Command::AcceleratorToString(command.accelerator()));
-      suggested_key_prefs->Set(command.command_name(), std::move(command_keys));
+      base::Value::Dict command_keys;
+      command_keys.Set(kSuggestedKey,
+                       Command::AcceleratorToString(command.accelerator()));
+      suggested_key_prefs.Set(command.command_name(), std::move(command_keys));
     }
   }
 
@@ -525,25 +518,21 @@
   // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
   if (browser_action_command &&
       browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
-    std::unique_ptr<base::DictionaryValue> command_keys(
-        new base::DictionaryValue);
-    command_keys->SetStringKey(
-        kSuggestedKey,
-        Command::AcceleratorToString(browser_action_command->accelerator()));
-    suggested_key_prefs->Set(browser_action_command->command_name(),
-                             std::move(command_keys));
+    base::Value::Dict command_keys;
+    command_keys.Set(kSuggestedKey, Command::AcceleratorToString(
+                                        browser_action_command->accelerator()));
+    suggested_key_prefs.Set(browser_action_command->command_name(),
+                            std::move(command_keys));
   }
 
   const Command* page_action_command =
       CommandsInfo::GetPageActionCommand(extension);
   if (page_action_command) {
-    std::unique_ptr<base::DictionaryValue> command_keys(
-        new base::DictionaryValue);
-    command_keys->SetStringKey(
-        kSuggestedKey,
-        Command::AcceleratorToString(page_action_command->accelerator()));
-    suggested_key_prefs->Set(page_action_command->command_name(),
-                             std::move(command_keys));
+    base::Value::Dict command_keys;
+    command_keys.Set(kSuggestedKey, Command::AcceleratorToString(
+                                        page_action_command->accelerator()));
+    suggested_key_prefs.Set(page_action_command->command_name(),
+                            std::move(command_keys));
   }
 
   // Merge into current prefs, if present.
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
index d1b23b9d..4c549e6e 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
@@ -219,7 +219,8 @@
 void DeclarativeContentIsBookmarkedConditionTracker::BookmarkNodeAdded(
     bookmarks::BookmarkModel* model,
     const bookmarks::BookmarkNode* parent,
-    size_t index) {
+    size_t index,
+    bool added_by_user) {
   if (!extensive_bookmark_changes_in_progress_) {
     for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) {
       web_contents_tracker_pair.second->BookmarkAddedForUrl(
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
index 64f45305..d757f7b6 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
@@ -138,7 +138,8 @@
   void BookmarkModelChanged() override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 45c3d7b..9f6a5218 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -348,6 +348,8 @@
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[translate::prefs::kBlockedLanguages] =
       settings_api::PrefType::PREF_TYPE_LIST;
+  (*s_allowlist)[translate::prefs::kPrefNeverPromptSitesWithTime] =
+      settings_api::PrefType::PREF_TYPE_LIST;
   (*s_allowlist)[language::prefs::kSelectedLanguages] =
       settings_api::PrefType::PREF_TYPE_STRING;
   (*s_allowlist)[language::prefs::kForcedLanguages] =
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 361206df..a2beb699 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -333,8 +333,6 @@
 
 }  // namespace
 
-using ContextType = ExtensionBrowserTest::ContextType;
-
 class ExtensionWebRequestApiTest : public ExtensionApiTest {
  public:
   explicit ExtensionWebRequestApiTest(
@@ -401,6 +399,29 @@
   std::unique_ptr<NavigateTabMessageHandler> navigationHandler_;
 };
 
+using ContextType = ExtensionBrowserTest::ContextType;
+
+class ExtensionWebRequestApiTestWithContextType
+    : public ExtensionWebRequestApiTest,
+      public testing::WithParamInterface<ContextType> {
+ public:
+  ExtensionWebRequestApiTestWithContextType()
+      : ExtensionWebRequestApiTest(GetParam()) {}
+  ExtensionWebRequestApiTestWithContextType(
+      const ExtensionWebRequestApiTestWithContextType&) = delete;
+  ExtensionWebRequestApiTestWithContextType& operator=(
+      const ExtensionWebRequestApiTestWithContextType&) = delete;
+  ~ExtensionWebRequestApiTestWithContextType() override = default;
+};
+
+INSTANTIATE_TEST_SUITE_P(PersistentBackground,
+                         ExtensionWebRequestApiTestWithContextType,
+                         ::testing::Values(ContextType::kPersistentBackground));
+
+INSTANTIATE_TEST_SUITE_P(ServiceWorker,
+                         ExtensionWebRequestApiTestWithContextType,
+                         ::testing::Values(ContextType::kServiceWorker));
+
 class DevToolsFrontendInWebRequestApiTest : public ExtensionApiTest {
  public:
   void SetUpOnMainThread() override {
@@ -895,7 +916,7 @@
 }
 
 // Flaky on all platforms: https://crbug.com/1003661
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        DISABLED_WebRequestExtraHeaders_Auth) {
   CancelLoginDialog login_dialog_helper;
 
@@ -904,24 +925,26 @@
       << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestChangeCSPHeaders) {
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
+                       WebRequestChangeCSPHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("webrequest/test_change_csp_headers"))
       << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        WebRequestCORSWithExtraHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("webrequest/test_cors")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestRedirects) {
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
+                       WebRequestRedirects) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("webrequest/test_redirects")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        WebRequestRedirectsWithExtraHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("webrequest/test_redirects",
@@ -930,7 +953,7 @@
 }
 
 // Tests that redirects from secure to insecure don't send the referrer header.
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        WebRequestRedirectsToInsecure) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   GURL insecure_destination =
@@ -953,6 +976,9 @@
       << message_;
 }
 
+// TODO(crbug.com/1093066): The JS file for this test uses XmlHttpRequest,
+// which needs to be migrated to use fetch() to be compatible with
+// service workers.
 IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
                        WebRequestSubresourceRedirects) {
   ASSERT_TRUE(StartEmbeddedTestServer());
@@ -960,6 +986,9 @@
       << message_;
 }
 
+// TODO(crbug.com/1093066): The JS file for this test uses XmlHttpRequest,
+// which needs to be migrated to use fetch() to be compatible with
+// service workers.
 IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
                        WebRequestSubresourceRedirectsWithExtraHeaders) {
   ASSERT_TRUE(StartEmbeddedTestServer());
@@ -968,7 +997,8 @@
       << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebRequestNewTab) {
+IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
+                       WebRequestNewTab) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   // Wait for the extension to set itself up and return control to us.
   ASSERT_TRUE(RunExtensionTest("webrequest/test_new_tab")) << message_;
@@ -1094,27 +1124,6 @@
   EXPECT_EQ(exptected_content_incognito_window, body);
 }
 
-class ExtensionWebRequestApiTestWithContextType
-    : public ExtensionWebRequestApiTest,
-      public testing::WithParamInterface<ContextType> {
- public:
-  ExtensionWebRequestApiTestWithContextType()
-      : ExtensionWebRequestApiTest(GetParam()) {}
-  ExtensionWebRequestApiTestWithContextType(
-      const ExtensionWebRequestApiTestWithContextType&) = delete;
-  ExtensionWebRequestApiTestWithContextType& operator=(
-      const ExtensionWebRequestApiTestWithContextType&) = delete;
-  ~ExtensionWebRequestApiTestWithContextType() override = default;
-};
-
-INSTANTIATE_TEST_SUITE_P(PersistentBackground,
-                         ExtensionWebRequestApiTestWithContextType,
-                         ::testing::Values(ContextType::kPersistentBackground));
-
-INSTANTIATE_TEST_SUITE_P(ServiceWorker,
-                         ExtensionWebRequestApiTestWithContextType,
-                         ::testing::Values(ContextType::kServiceWorker));
-
 IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        WebRequestDeclarativePermissionSpanning1) {
   // Test spanning with incognito permission.
@@ -5200,8 +5209,6 @@
       << message_;
 }
 
-using ContextType = ExtensionBrowserTest::ContextType;
-
 class WebRequestApiTestWithContextType
     : public ExtensionWebRequestApiTest,
       public testing::WithParamInterface<ContextType> {
diff --git a/chrome/browser/feature_engagement/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java b/chrome/browser/feature_engagement/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java
index 16e2902..8c37be5 100644
--- a/chrome/browser/feature_engagement/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java
+++ b/chrome/browser/feature_engagement/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java
@@ -32,11 +32,7 @@
         if (profile == null) {
             throw new IllegalArgumentException("Profile is required for retrieving tracker.");
         }
-        if (!profile.isNativeInitialized()) {
-            // Temporary to debug https://crbug.com/1346710.
-            throw new IllegalArgumentException("Profile must have a valid native pointer.");
-        }
-
+        profile.ensureNativeInitialized();
         return TrackerFactoryJni.get().getTrackerForProfile(profile);
     }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4e38600..975ded25 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -854,6 +854,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "cct-resizable-window-above-navbar",
+    "owners": ["jinsukkim", "twellington"],
+    "expiry_milestone": 110
+  },
+  {
     "name": "cellular-bypass-esim-installation-connectivity-check",
     "owners": [ "azeemarshad", "khorimoto", "jiajunz", "cros-connectivity@google.com" ],
     // This flag does not expire because it allows some test cases that host a local SM-DP+ server
@@ -2439,6 +2444,21 @@
     "expiry_milestone": 107
   },
   {
+    "name": "enable-lens-in-home-screen-widget",
+    "owners": [ "schechter", "hujasonx" ],
+    "expiry_milestone": 120
+  },
+  {
+    "name": "enable-lens-in-keyboard",
+    "owners": [ "schechter", "hujasonx" ],
+    "expiry_milestone": 120
+  },
+  {
+    "name": "enable-lens-in-ntp",
+    "owners": [ "schechter", "hujasonx" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "enable-lens-standalone",
     "owners": [ "stanfield@google.com", "benwgold@google.com", "juanmojica@google.com" ],
     "expiry_milestone": 107
@@ -6148,11 +6168,6 @@
     "expiry_milestone": 110
   },
   {
-    "name": "tab-search-media-tabs",
-    "owners": ["elainechien", "romanarora"],
-    "expiry_milestone": 110
-  },
-  {
     "name": "tangible-sync",
     "owners": [ "aliceywang", "chrome-signin-team" ],
     "expiry_milestone":120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f59187b0..e4c3c7e3 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1611,11 +1611,6 @@
     "When enabled, the audio indicators in the tab strip double as tab audio "
     "mute controls.";
 
-const char kTabSearchMediaTabsId[] = "tab-search-media-tabs";
-const char kTabSearchMediaTabsName[] = "Tab Search Media Tabs";
-const char kTabSearchMediaTabsDescription[] =
-    "Enable indicators on media tabs in Tab Search.";
-
 const char kTabSwitcherOnReturnName[] = "Tab switcher on return";
 const char kTabSwitcherOnReturnDescription[] =
     "Enable tab switcher on return after specified time has elapsed";
@@ -3330,6 +3325,12 @@
     "Bottom sheet Custom Tabs (third party)";
 const char kCCTResizableForThirdPartiesDescription[] =
     "Enable bottom sheet Custom Tabs for third party apps.";
+const char kCCTResizableWindowAboveNavbarName[] =
+    "Bottom sheet Custom Tabs placed above the NavBar";
+const char kCCTResizableWindowAboveNavbarDescription[] =
+    "Avoid various UI glitches/misbehavior by always keeping bottom sheet "
+    "custom tab above the navigation bar. The navigation bar color is not "
+    "customizable - it is up to the host app to ensure the bar looks okay.";
 
 const char kCCTRealTimeEngagementSignalsName[] =
     "Enable CCT real-time engagement signals.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index fdad6f1..2130dc7 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -894,10 +894,6 @@
 extern const char kTabAudioMutingName[];
 extern const char kTabAudioMutingDescription[];
 
-extern const char kTabSearchMediaTabsId[];
-extern const char kTabSearchMediaTabsName[];
-extern const char kTabSearchMediaTabsDescription[];
-
 extern const char kTabSwitcherOnReturnName[];
 extern const char kTabSwitcherOnReturnDescription[];
 
@@ -1875,6 +1871,8 @@
 extern const char kCCTResizableForFirstPartiesDescription[];
 extern const char kCCTResizableForThirdPartiesName[];
 extern const char kCCTResizableForThirdPartiesDescription[];
+extern const char kCCTResizableWindowAboveNavbarName[];
+extern const char kCCTResizableWindowAboveNavbarDescription[];
 
 extern const char kCCTRealTimeEngagementSignalsName[];
 extern const char kCCTRealTimeEngagementSignalsDescription[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d105345..cf4f1e8 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -201,6 +201,7 @@
     &kCCTResizableAllowResizeByUserGesture,
     &kCCTResizableForFirstParties,
     &kCCTResizableForThirdParties,
+    &kCCTResizableWindowAboveNavbar,
     &kCCTRetainingState,
     &kCCTResourcePrefetch,
     &kCCTToolbarCustomizations,
@@ -563,6 +564,9 @@
 const base::Feature kCCTResizableForThirdParties{
     "CCTResizableForThirdParties", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kCCTResizableWindowAboveNavbar{
+    "CCTResizableWindowAboveNavbar", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kCCTResourcePrefetch{"CCTResourcePrefetch",
                                          base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index b0e7936..2b360ec 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -52,6 +52,7 @@
 extern const base::Feature kCCTResizableAllowResizeByUserGesture;
 extern const base::Feature kCCTResizableForFirstParties;
 extern const base::Feature kCCTResizableForThirdParties;
+extern const base::Feature kCCTResizableWindowAboveNavbar;
 extern const base::Feature kCCTResourcePrefetch;
 extern const base::Feature kCCTRetainingState;
 extern const base::Feature kCCTToolbarCustomizations;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index b700fc8..a6f50ef1 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -61,6 +61,7 @@
                     .put(ChromeFeatureList.COMMERCE_COUPONS, false)
                     .put(ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES, false)
                     .put(ChromeFeatureList.CCT_TOOLBAR_CUSTOMIZATIONS, true)
+                    .put(ChromeFeatureList.CCT_RESIZABLE_WINDOW_ABOVE_NAVBAR, true)
                     .put(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS, false)
                     .put(ChromeFeatureList.COMMAND_LINE_ON_NON_ROOTED, false)
                     .put(ChromeFeatureList.CONDITIONAL_TAB_STRIP_ANDROID, false)
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 34fb846..ddf10c0 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
@@ -264,6 +264,7 @@
             "CCTResizableAllowResizeByUserGesture";
     public static final String CCT_RESIZABLE_FOR_FIRST_PARTIES = "CCTResizableForFirstParties";
     public static final String CCT_RESIZABLE_FOR_THIRD_PARTIES = "CCTResizableForThirdParties";
+    public static final String CCT_RESIZABLE_WINDOW_ABOVE_NAVBAR = "CCTResizableWindowAboveNavbar";
     public static final String CCT_RESOURCE_PREFETCH = "CCTResourcePrefetch";
     public static final String CCT_RETAINING_STATE = "CCTRetainingState";
     public static final String CCT_REPORT_PARALLEL_REQUEST_STATUS =
@@ -630,6 +631,8 @@
             new CachedFlag(CCT_RESIZABLE_FOR_FIRST_PARTIES, true);
     public static final CachedFlag sCctResizableForThirdParties =
             new CachedFlag(CCT_RESIZABLE_FOR_THIRD_PARTIES, false);
+    public static final CachedFlag sCctResizableWindowAboveNavbar =
+            new CachedFlag(CCT_RESIZABLE_WINDOW_ABOVE_NAVBAR, true);
     public static final CachedFlag sCctToolbarCustomizations =
             new CachedFlag(CCT_TOOLBAR_CUSTOMIZATIONS, true);
     public static final CachedFlag sCloseTabSuggestions =
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index eec2cb4..df63803 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -313,9 +313,23 @@
     base::Erase(media_types, DesktopMediaList::Type::kWindow);
   }
 
-  auto includable_web_contents_filter =
+  auto includable_web_contents_filter = base::BindRepeating(
+      [](DesktopMediaList::WebContentsFilter capture_policy_filter,
+         bool exclude_self_browser_surface,
+         base::WeakPtr<content::WebContents> capturing_web_contents,
+         content::WebContents* captured_web_contents) {
+        if (!capturing_web_contents)
+          return false;
+        if (!capture_policy_filter.Run(captured_web_contents))
+          return false;
+        if (!exclude_self_browser_surface)
+          return true;
+        return capturing_web_contents.get() != captured_web_contents;
+      },
       capture_policy::GetIncludableWebContentsFilter(request_origin,
-                                                     capture_level);
+                                                     capture_level),
+      pending_request.request.exclude_self_browser_surface,
+      web_contents->GetWeakPtr());
 
   auto source_lists = picker_factory_->CreateMediaList(
       media_types, web_contents, includable_web_contents_filter);
diff --git a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
index e7fa044..242894a 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -90,6 +90,13 @@
     return request;
   }
 
+  content::MediaStreamRequest MakeExcludeSelfBrowserSurfaceRequest(
+      bool exclude_self_browser_surface) {
+    content::MediaStreamRequest request = MakeRequest(/*request_audio=*/false);
+    request.exclude_self_browser_surface = exclude_self_browser_surface;
+    return request;
+  }
+
   content::MediaResponseCallback MakeCallback(
       base::RunLoop* wait_loop,
       blink::mojom::MediaStreamRequestResult* request_result,
@@ -172,6 +179,10 @@
     access_handler_->WebContentsDestroyed(web_contents());
   }
 
+  bool IsWebContentsExcluded() const {
+    return picker_factory_->IsWebContentsExcluded();
+  }
+
   DesktopMediaPicker::Params GetParams() {
     return picker_factory_->picker()->GetParams();
   }
@@ -741,3 +752,35 @@
   EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, results[2]);
   access_handler_.reset();
 }
+
+class DisplayMediaAccessHandlerTestWithSelfBrowserSurface
+    : public DisplayMediaAccessHandlerTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  DisplayMediaAccessHandlerTestWithSelfBrowserSurface()
+      : exclude_self_browser_surface_(GetParam()) {}
+
+  ~DisplayMediaAccessHandlerTestWithSelfBrowserSurface() override = default;
+
+ protected:
+  const bool exclude_self_browser_surface_;
+};
+
+INSTANTIATE_TEST_SUITE_P(_,
+                         DisplayMediaAccessHandlerTestWithSelfBrowserSurface,
+                         ::testing::Bool());
+
+TEST_P(DisplayMediaAccessHandlerTestWithSelfBrowserSurface,
+       CheckIsWebContentsExcluded) {
+  SetTestFlags({{MakePickerTestFlags(/*request_audio=*/false)}});
+  blink::mojom::MediaStreamRequestResult result;
+  blink::mojom::StreamDevices devices;
+  base::RunLoop wait_loop;
+
+  HandleRequest(
+      MakeExcludeSelfBrowserSurfaceRequest(exclude_self_browser_surface_),
+      &wait_loop, &result, devices);
+  wait_loop.Run();
+  EXPECT_EQ(exclude_self_browser_surface_, IsWebContentsExcluded());
+  access_handler_.reset();
+}
diff --git a/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc b/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
index fc12d70..c69b34f 100644
--- a/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
+++ b/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.cc
@@ -105,6 +105,7 @@
     content::WebContents* web_contents,
     DesktopMediaList::WebContentsFilter includable_web_contents_filter) {
   EXPECT_LE(current_test_, tests_count_);
+  is_web_contents_excluded_ = !includable_web_contents_filter.Run(web_contents);
   std::vector<std::unique_ptr<DesktopMediaList>> media_lists;
   for (auto source_type : types)
     media_lists.emplace_back(new FakeDesktopMediaList(source_type));
diff --git a/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h b/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
index 3c1af8a..eeb0c2a 100644
--- a/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
+++ b/chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h
@@ -46,6 +46,7 @@
   //  |test_flags| are expected to outlive the factory.
   void SetTestFlags(TestFlags* test_flags, int tests_count);
   FakeDesktopMediaPicker* picker() const { return picker_; }
+  bool IsWebContentsExcluded() const { return is_web_contents_excluded_; }
   // DesktopMediaPickerFactory implementation
   std::unique_ptr<DesktopMediaPicker> CreatePicker() override;
   std::vector<std::unique_ptr<DesktopMediaList>> CreateMediaList(
@@ -59,6 +60,7 @@
   raw_ptr<TestFlags> test_flags_;
   int tests_count_;
   int current_test_;
+  bool is_web_contents_excluded_ = false;
 };
 
 class FakeDesktopMediaPicker : public DesktopMediaPicker {
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index 9cebac001..f129b50 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -51,6 +51,7 @@
 static const char kSameOriginRenamedTitle[] = "Renamed Same Origin Tab";
 // TODO(https://crbug.com/1215089): Enable on Lacros.
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
+static const char kMainHtmlTitle[] = "WebRTC Automated Test";
 // The captured tab is identified by its title.
 static const char kCapturedTabTitle[] = "totally-unique-captured-page-title";
 static const char kCapturedPageMain[] = "/webrtc/captured_page_main.html";
@@ -464,7 +465,7 @@
     command_line->AppendSwitch(
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
-        switches::kAutoSelectTabCaptureSourceByTitle, "WebRTC Automated Test");
+        switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle);
   }
 
   bool PreferCurrentTab() const override {
@@ -1040,6 +1041,53 @@
 
 #endif
 
+// TODO(https://crbug.com/1215089): Enable this test suite on Lacros.
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
+class GetDisplayMediaSelfBrowserSurfaceBrowserTest
+    : public WebRtcTestBase,
+      public testing::WithParamInterface<bool> {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    WebRtcTestBase::SetUpInProcessBrowserTestFixture();
+
+    DetectErrorsInJavaScript();
+
+    base::FilePath test_dir;
+    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(
+        switches::kEnableExperimentalWebPlatformFeatures);
+    command_line->AppendSwitchASCII(
+        switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle);
+  }
+
+  bool IsSelfBrowserSurfaceInclude() { return GetParam(); }
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         GetDisplayMediaSelfBrowserSurfaceBrowserTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(GetDisplayMediaSelfBrowserSurfaceBrowserTest,
+                       SelfBrowserSurfaceChangesCapturedTab) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
+  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);
+
+  const std::string constraints =
+      base::StringPrintf("{video: true, selfBrowserSurface: \"%s\"}",
+                         IsSelfBrowserSurfaceInclude() ? "include" : "exclude");
+  RunGetDisplayMedia(capturing_tab, constraints, /*is_fake_ui=*/false,
+                     /*expect_success=*/true, /*is_tab_capture=*/true);
+
+  EXPECT_EQ(IsSelfBrowserSurfaceInclude(), capturing_tab->IsBeingCaptured());
+  EXPECT_EQ(!IsSelfBrowserSurfaceInclude(), other_tab->IsBeingCaptured());
+}
+
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_CHROMEOS_ASH)
 
 class WebRtcScreenCaptureSelectAllScreensTest
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_features.cc b/chrome/browser/nearby_sharing/common/nearby_share_features.cc
index b7e35cc..763093d 100644
--- a/chrome/browser/nearby_sharing/common/nearby_share_features.cc
+++ b/chrome/browser/nearby_sharing/common/nearby_share_features.cc
@@ -32,10 +32,6 @@
 const base::Feature kNearbySharingOnePageOnboarding{
     "NearbySharingOnePageOnboarding", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables receiving WiFi networks using Nearby Share.
-const base::Feature kNearbySharingReceiveWifiCredentials{
-    "NearbySharingReceiveWifiCredentials", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables auto-accept functionality when sharing between a user's own devices.
 const base::Feature kNearbySharingSelfShareAutoAccept{
     "NearbySharingSelfShareAutoAccept", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_features.h b/chrome/browser/nearby_sharing/common/nearby_share_features.h
index f622c24..37818b5 100644
--- a/chrome/browser/nearby_sharing/common/nearby_share_features.h
+++ b/chrome/browser/nearby_sharing/common/nearby_share_features.h
@@ -14,7 +14,6 @@
 extern const base::Feature kNearbySharingChildAccounts;
 extern const base::Feature kNearbySharingDeviceContacts;
 extern const base::Feature kNearbySharingOnePageOnboarding;
-extern const base::Feature kNearbySharingReceiveWifiCredentials;
 extern const base::Feature kNearbySharingSelfShareAutoAccept;
 extern const base::Feature kNearbySharingSelfShareUI;
 extern const base::Feature kNearbySharingVisibilityReminder;
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.cc b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
index 30d3427..feeaeb0c 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
@@ -222,9 +222,7 @@
   size_t attachment_count = share_target.file_attachments.size() +
                             share_target.text_attachments.size();
 
-  if (!share_target.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!share_target.wifi_credentials_attachments.empty()) {
     std::u16string network_name =
         base::UTF8ToUTF16(share_target.wifi_credentials_attachments[0].ssid());
     return base::ReplaceStringPlaceholders(
@@ -238,9 +236,7 @@
 }
 
 std::u16string GetProgressNotificationTitle(const ShareTarget& share_target) {
-  if (!share_target.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!share_target.wifi_credentials_attachments.empty()) {
     return FormatNotificationTitle(
         share_target,
         IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE_WIFI_CREDENTIALS,
@@ -256,9 +252,7 @@
 }
 
 std::u16string GetSuccessNotificationTitle(const ShareTarget& share_target) {
-  if (!share_target.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!share_target.wifi_credentials_attachments.empty()) {
     return FormatNotificationTitle(
         share_target,
         IDS_NEARBY_NOTIFICATION_RECEIVE_SUCCESS_TITLE_WIFI_CREDENTIALS,
@@ -273,9 +267,7 @@
 }
 
 std::u16string GetFailureNotificationTitle(const ShareTarget& share_target) {
-  if (!share_target.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!share_target.wifi_credentials_attachments.empty()) {
     return FormatNotificationTitle(
         share_target,
         IDS_NEARBY_NOTIFICATION_RECEIVE_FAILURE_TITLE_WIFI_CREDENTIALS,
@@ -313,9 +305,7 @@
   size_t attachment_count = share_target.file_attachments.size() +
                             share_target.text_attachments.size();
   std::u16string message;
-  if (!share_target.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!share_target.wifi_credentials_attachments.empty()) {
     message = base::ReplaceStringPlaceholders(
         l10n_util::GetStringUTF16(
             IDS_NEARBY_NOTIFICATION_CONNECTION_REQUEST_MESSAGE_WIFI_CREDENTIALS),
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
index ae7dfee..2824253 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager_unittest.cc
@@ -152,24 +152,16 @@
 class NearbyNotificationManagerTestBase : public testing::Test {
  public:
   explicit NearbyNotificationManagerTestBase(
-      std::tuple<bool, bool, bool> feature_list) {
+      std::tuple<bool, bool> feature_list) {
     std::vector<base::Feature> enabled_features;
     std::vector<base::Feature> disabled_features;
     is_self_share_enabled_ = std::get<0>(feature_list);
-    is_receive_wifi_credentials_enabled_ = std::get<1>(feature_list);
-    is_self_share_auto_accept_enabled_ = std::get<2>(feature_list);
+    is_self_share_auto_accept_enabled_ = std::get<1>(feature_list);
     if (is_self_share_enabled_) {
       enabled_features.push_back(features::kNearbySharingSelfShareUI);
     } else {
       disabled_features.push_back(features::kNearbySharingSelfShareUI);
     }
-    if (is_receive_wifi_credentials_enabled_) {
-      enabled_features.push_back(
-          features::kNearbySharingReceiveWifiCredentials);
-    } else {
-      disabled_features.push_back(
-          features::kNearbySharingReceiveWifiCredentials);
-    }
     if (is_self_share_auto_accept_enabled_) {
       enabled_features.push_back(features::kNearbySharingSelfShareAutoAccept);
     } else {
@@ -271,7 +263,7 @@
           CreateFileAttachment(FileAttachment::Type::kVideo));
     }
 
-    if (wifi_credentials_attachments && is_receive_wifi_credentials_enabled_) {
+    if (wifi_credentials_attachments) {
       share_target.wifi_credentials_attachments.push_back(
           CreateWifiCredentialsAttachment(
               WifiCredentialsAttachment::SecurityType::kWpaPsk));
@@ -294,7 +286,6 @@
   data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
   MockSettingsOpener* settings_opener_;
   bool is_self_share_enabled_ = false;
-  bool is_receive_wifi_credentials_enabled_ = false;
   bool is_self_share_auto_accept_enabled_ = false;
 };
 
@@ -302,7 +293,7 @@
 // Visibility Reminder enabled and disabled.
 class NearbyNotificationManagerTest
     : public NearbyNotificationManagerTestBase,
-      public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
   NearbyNotificationManagerTest()
       : NearbyNotificationManagerTestBase(/*feature_list=*/GetParam()) {}
@@ -407,10 +398,9 @@
 };
 
 // Boolean parameter is |is_incoming| and the tuple parameter is a feature list
-// containing |is_self_share_enabled|, |is_receive_wifi_credentials_enabled|,
-// and |is_self_share_auto_accept_enabled|.
-using AttachmentsTestParam = std::
-    tuple<AttachmentsTestParamInternal, bool, std::tuple<bool, bool, bool>>;
+// containing |is_self_share_enabled| and |is_self_share_auto_accept_enabled|.
+using AttachmentsTestParam =
+    std::tuple<AttachmentsTestParamInternal, bool, std::tuple<bool, bool>>;
 
 class NearbyNotificationManagerAttachmentsTest
     : public NearbyNotificationManagerTestBase,
@@ -422,10 +412,8 @@
 };
 
 // Boolean parameter is |with_token| and the tuple parameter is featuree list
-// contains |is_self_share_enabled|, |is_receive_wifi_credentials_enabled|,
-// and |is_self_share_auto_accept_enabled|.
-using ConnectionRequestTestParam =
-    std::tuple<bool, std::tuple<bool, bool, bool>>;
+// contains |is_self_share_enabled| and |is_self_share_auto_accept_enabled|.
+using ConnectionRequestTestParam = std::tuple<bool, std::tuple<bool, bool>>;
 
 class NearbyNotificationManagerConnectionRequestTest
     : public NearbyNotificationManagerTestBase,
@@ -442,9 +430,7 @@
     const std::string& device_name,
     const std::string& network_name,
     bool use_capitalized_resource) {
-  if (!param.wifi_credentials_attachments.empty() &&
-      base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
+  if (!param.wifi_credentials_attachments.empty()) {
     return base::ReplaceStringPlaceholders(
         l10n_util::GetStringUTF16(resource_id),
         {base::ASCIIToUTF16(network_name), base::ASCIIToUTF16(device_name)},
@@ -595,7 +581,7 @@
   for (FileAttachment::Type type : param.file_attachments)
     share_target.file_attachments.push_back(CreateFileAttachment(type));
 
-  if (is_incoming && is_receive_wifi_credentials_enabled_) {
+  if (is_incoming) {
     for (WifiCredentialsAttachment::SecurityType securityType :
          param.wifi_credentials_attachments) {
       share_target.wifi_credentials_attachments.push_back(
@@ -607,8 +593,7 @@
   manager()->ShowProgress(share_target, transfer_metadata);
 
   std::u16string expected;
-  if (!param.wifi_credentials_attachments.empty() &&
-      is_receive_wifi_credentials_enabled_ && is_incoming) {
+  if (!param.wifi_credentials_attachments.empty() && is_incoming) {
     expected = FormatNotificationTitle(
         IDS_NEARBY_NOTIFICATION_RECEIVE_PROGRESS_TITLE_WIFI_CREDENTIALS, param,
         device_name, share_target.wifi_credentials_attachments[0].ssid(),
@@ -648,7 +633,7 @@
   for (FileAttachment::Type type : param.file_attachments)
     share_target.file_attachments.push_back(CreateFileAttachment(type));
 
-  if (is_incoming && is_receive_wifi_credentials_enabled_) {
+  if (is_incoming) {
     for (WifiCredentialsAttachment::SecurityType securityType :
          param.wifi_credentials_attachments) {
       share_target.wifi_credentials_attachments.push_back(
@@ -659,8 +644,7 @@
   manager()->ShowSuccess(share_target);
 
   std::u16string expected;
-  if (!param.wifi_credentials_attachments.empty() &&
-      is_receive_wifi_credentials_enabled_ && is_incoming) {
+  if (!param.wifi_credentials_attachments.empty() && is_incoming) {
     expected = FormatNotificationTitle(
         IDS_NEARBY_NOTIFICATION_RECEIVE_SUCCESS_TITLE_WIFI_CREDENTIALS, param,
         device_name, share_target.wifi_credentials_attachments[0].ssid(),
@@ -700,7 +684,7 @@
   for (FileAttachment::Type type : param.file_attachments)
     share_target.file_attachments.push_back(CreateFileAttachment(type));
 
-  if (is_incoming && is_receive_wifi_credentials_enabled_) {
+  if (is_incoming) {
     for (WifiCredentialsAttachment::SecurityType securityType :
          param.wifi_credentials_attachments) {
       share_target.wifi_credentials_attachments.push_back(
@@ -732,8 +716,7 @@
     }
 
     std::u16string expected_title;
-    if (!param.wifi_credentials_attachments.empty() &&
-        is_receive_wifi_credentials_enabled_ && is_incoming) {
+    if (!param.wifi_credentials_attachments.empty() && is_incoming) {
       expected_title = FormatNotificationTitle(
           IDS_NEARBY_NOTIFICATION_RECEIVE_FAILURE_TITLE_WIFI_CREDENTIALS, param,
           device_name, share_target.wifi_credentials_attachments[0].ssid(),
@@ -767,10 +750,9 @@
 INSTANTIATE_TEST_SUITE_P(
     NearbyNotificationManagerAttachmentsTest,
     NearbyNotificationManagerAttachmentsTest,
-    testing::Combine(
-        testing::ValuesIn(kAttachmentsTestParams),
-        testing::Bool(),
-        testing::Combine(testing::Bool(), testing::Bool(), testing::Bool())));
+    testing::Combine(testing::ValuesIn(kAttachmentsTestParams),
+                     testing::Bool(),
+                     testing::Combine(testing::Bool(), testing::Bool())));
 
 TEST_P(NearbyNotificationManagerConnectionRequestTest,
        ShowConnectionRequest_ShowsNotification) {
@@ -847,7 +829,6 @@
                          NearbyNotificationManagerConnectionRequestTest,
                          testing::Combine(testing::Bool(),
                                           testing::Combine(testing::Bool(),
-                                                           testing::Bool(),
                                                            testing::Bool())));
 
 TEST_P(NearbyNotificationManagerTest,
@@ -1636,9 +1617,6 @@
 
 TEST_P(NearbyNotificationManagerTest,
        SuccessNotificationClicked_WifiCredentialsReceived) {
-  if (!is_receive_wifi_credentials_enabled_)
-    return;
-
   base::RunLoop run_loop;
   manager()->SetOnSuccessClickedForTesting(base::BindLambdaForTesting(
       [&](NearbyNotificationManager::SuccessNotificationAction action) {
@@ -1952,6 +1930,4 @@
 
 INSTANTIATE_TEST_SUITE_P(NearbyNotificationManagerTest,
                          NearbyNotificationManagerTest,
-                         testing::Combine(testing::Bool(),
-                                          testing::Bool(),
-                                          testing::Bool()));
+                         testing::Combine(testing::Bool(), testing::Bool()));
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index 5d67521..308554c 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -3416,30 +3416,25 @@
     share_target.text_attachments.push_back(std::move(attachment));
   }
 
-  if (base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
-    for (const auto& wifi_credentials :
-         introduction_frame->wifi_credentials_metadata) {
-      if (wifi_credentials->ssid.empty()) {
-        Fail(share_target,
-             TransferMetadata::Status::kUnsupportedAttachmentType);
-        NS_LOG(WARNING) << __func__
-                        << ": Ignore introduction, due to invalid Wi-Fi SSID";
-        return;
-      }
-
-      NS_LOG(VERBOSE) << __func__ << ": Found Wi-Fi Credentials: id="
-                      << wifi_credentials->id
-                      << ", payload_id=" << wifi_credentials->payload_id
-                      << ", security_type=" << wifi_credentials->security_type;
-
-      WifiCredentialsAttachment attachment(wifi_credentials->id,
-                                           wifi_credentials->security_type,
-                                           wifi_credentials->ssid);
-      SetAttachmentPayloadId(attachment, wifi_credentials->payload_id);
-      share_target.wifi_credentials_attachments.push_back(
-          std::move(attachment));
+  for (const auto& wifi_credentials :
+       introduction_frame->wifi_credentials_metadata) {
+    if (wifi_credentials->ssid.empty()) {
+      Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType);
+      NS_LOG(WARNING) << __func__
+                      << ": Ignore introduction, due to invalid Wi-Fi SSID";
+      return;
     }
+
+    NS_LOG(VERBOSE) << __func__
+                    << ": Found Wi-Fi Credentials: id=" << wifi_credentials->id
+                    << ", payload_id=" << wifi_credentials->payload_id
+                    << ", security_type=" << wifi_credentials->security_type;
+
+    WifiCredentialsAttachment attachment(wifi_credentials->id,
+                                         wifi_credentials->security_type,
+                                         wifi_credentials->ssid);
+    SetAttachmentPayloadId(attachment, wifi_credentials->payload_id);
+    share_target.wifi_credentials_attachments.push_back(std::move(attachment));
   }
 
   if (!share_target.has_attachments()) {
@@ -3955,64 +3950,61 @@
     attachment_info.text_body = std::move(text_body);
   }
 
-  if (base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
-    for (auto& wifi_credentials : share_target.wifi_credentials_attachments) {
-      AttachmentInfo& attachment_info =
-          attachment_info_map_[wifi_credentials.id()];
-      absl::optional<int64_t> payload_id = attachment_info.payload_id;
-      if (!payload_id) {
-        NS_LOG(WARNING) << __func__
-                        << ": No payload id found for wifi credentials - "
-                        << wifi_credentials.id();
-        return false;
-      }
-
-      location::nearby::connections::mojom::Payload* incoming_payload =
-          nearby_connections_manager_->GetIncomingPayload(*payload_id);
-      if (!incoming_payload || !incoming_payload->content ||
-          !incoming_payload->content->is_bytes()) {
-        NS_LOG(WARNING) << __func__
-                        << ": No payload found for Wi-Fi credentials - "
-                        << wifi_credentials.id();
-        return false;
-      }
-
-      const std::vector<uint8_t>& bytes =
-          incoming_payload->content->get_bytes()->bytes;
-      if (bytes.empty()) {
-        NS_LOG(WARNING)
-            << __func__
-            << ": Incoming bytes is empty for Wi-Fi password with payload_id - "
-            << *payload_id;
-        return false;
-      }
-
-      sharing::nearby::WifiCredentials credentials_proto;
-      if (!credentials_proto.ParseFromArray(bytes.data(), bytes.size())) {
-        NS_LOG(WARNING) << __func__
-                        << ": Failed to parse Wi-Fi credentials proto";
-        return false;
-      }
-
-      if (credentials_proto.password().empty()) {
-        NS_LOG(WARNING) << __func__ << ": No Wi-Fi password found";
-        return false;
-      }
-
-      if (credentials_proto.has_hidden_ssid() &&
-          credentials_proto.hidden_ssid()) {
-        NS_LOG(WARNING) << __func__ << ": Network is hidden";
-        return false;
-      }
-
-      std::string wifi_password(credentials_proto.password());
-      wifi_credentials.set_wifi_password(wifi_password);
-
-      // Automatically set up the Wi-Fi network for the user.
-      wifi_network_handler_->ConfigureWifiNetwork(wifi_credentials,
-                                                  base::DoNothing());
+  for (auto& wifi_credentials : share_target.wifi_credentials_attachments) {
+    AttachmentInfo& attachment_info =
+        attachment_info_map_[wifi_credentials.id()];
+    absl::optional<int64_t> payload_id = attachment_info.payload_id;
+    if (!payload_id) {
+      NS_LOG(WARNING) << __func__
+                      << ": No payload id found for wifi credentials - "
+                      << wifi_credentials.id();
+      return false;
     }
+
+    location::nearby::connections::mojom::Payload* incoming_payload =
+        nearby_connections_manager_->GetIncomingPayload(*payload_id);
+    if (!incoming_payload || !incoming_payload->content ||
+        !incoming_payload->content->is_bytes()) {
+      NS_LOG(WARNING) << __func__
+                      << ": No payload found for Wi-Fi credentials - "
+                      << wifi_credentials.id();
+      return false;
+    }
+
+    const std::vector<uint8_t>& bytes =
+        incoming_payload->content->get_bytes()->bytes;
+    if (bytes.empty()) {
+      NS_LOG(WARNING)
+          << __func__
+          << ": Incoming bytes is empty for Wi-Fi password with payload_id - "
+          << *payload_id;
+      return false;
+    }
+
+    sharing::nearby::WifiCredentials credentials_proto;
+    if (!credentials_proto.ParseFromArray(bytes.data(), bytes.size())) {
+      NS_LOG(WARNING) << __func__
+                      << ": Failed to parse Wi-Fi credentials proto";
+      return false;
+    }
+
+    if (credentials_proto.password().empty()) {
+      NS_LOG(WARNING) << __func__ << ": No Wi-Fi password found";
+      return false;
+    }
+
+    if (credentials_proto.has_hidden_ssid() &&
+        credentials_proto.hidden_ssid()) {
+      NS_LOG(WARNING) << __func__ << ": Network is hidden";
+      return false;
+    }
+
+    std::string wifi_password(credentials_proto.password());
+    wifi_credentials.set_wifi_password(wifi_password);
+
+    // Automatically set up the Wi-Fi network for the user.
+    wifi_network_handler_->ConfigureWifiNetwork(wifi_credentials,
+                                                base::DoNothing());
   }
   return true;
 }
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
index ca890e5..6e7c19b 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -349,8 +349,7 @@
 const std::vector<base::Feature> kTestFeatures = {
     features::kNearbySharingBackgroundScanning,
     features::kNearbySharingSelfShareAutoAccept,
-    features::kNearbySharingSelfShareUI,
-    features::kNearbySharingReceiveWifiCredentials};
+    features::kNearbySharingSelfShareUI};
 
 bool FileExists(const base::FilePath& file_path) {
   base::ScopedAllowBlockingForTesting allow_blocking;
@@ -413,13 +412,10 @@
 
   std::vector<sharing::mojom::WifiCredentialsMetadataPtr>
       mojo_wifi_credentials_metadatas;
-  if (base::FeatureList::IsEnabled(
-          features::kNearbySharingReceiveWifiCredentials)) {
-    mojo_wifi_credentials_metadatas.push_back(
-        sharing::mojom::WifiCredentialsMetadata::New(kSsid, kWifiSecurityType,
-                                                     kWifiCredentialsPayloadId,
-                                                     kWifiCredentialsId));
-  }
+  mojo_wifi_credentials_metadatas.push_back(
+      sharing::mojom::WifiCredentialsMetadata::New(kSsid, kWifiSecurityType,
+                                                   kWifiCredentialsPayloadId,
+                                                   kWifiCredentialsId));
 
   sharing::mojom::V1FramePtr mojo_v1frame =
       sharing::mojom::V1Frame::NewIntroduction(
@@ -1220,13 +1216,8 @@
       }
 
       if (id == kWifiCredentialsPayloadId) {
-        if (base::FeatureList::IsEnabled(
-                features::kNearbySharingReceiveWifiCredentials)) {
-          fake_nearby_connections_manager_->SetIncomingPayload(
-              id, GetWifiPayloadPtr(id, kWifiPassword));
-        } else {
-          continue;
-        }
+        fake_nearby_connections_manager_->SetIncomingPayload(
+            id, GetWifiPayloadPtr(id, kWifiPassword));
       }
 
       base::WeakPtr<NearbyConnectionsManager::PayloadStatusListener> listener =
@@ -1275,25 +1266,21 @@
             EXPECT_EQ(kTextPayload, text.text_body());
           }
 
-          if (base::FeatureList::IsEnabled(
-                  features::kNearbySharingReceiveWifiCredentials)) {
-            EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
-            for (const WifiCredentialsAttachment& wifi_credentials :
-                 share_target.wifi_credentials_attachments) {
-              EXPECT_EQ(kSsid, wifi_credentials.ssid());
-              EXPECT_EQ(kWifiPassword, wifi_credentials.wifi_password());
-              EXPECT_EQ(kWifiSecurityType, wifi_credentials.security_type());
-            }
-            EXPECT_EQ(1u, wifi_network_handler_->num_configure_network_calls());
-            EXPECT_EQ(kSsid, wifi_network_handler_->last_attachment().ssid());
-            EXPECT_EQ(kWifiPassword,
-                      wifi_network_handler_->last_attachment().wifi_password());
-            EXPECT_EQ(kWifiSecurityType,
-                      wifi_network_handler_->last_attachment().security_type());
-          } else {
-            EXPECT_EQ(0u, share_target.wifi_credentials_attachments.size());
-            EXPECT_EQ(0u, wifi_network_handler_->num_configure_network_calls());
+          EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
+          for (const WifiCredentialsAttachment& wifi_credentials :
+               share_target.wifi_credentials_attachments) {
+            EXPECT_EQ(kSsid, wifi_credentials.ssid());
+            EXPECT_EQ(kWifiPassword, wifi_credentials.wifi_password());
+            EXPECT_EQ(kWifiSecurityType, wifi_credentials.security_type());
           }
+
+          EXPECT_EQ(1u, wifi_network_handler_->num_configure_network_calls());
+          EXPECT_EQ(kSsid, wifi_network_handler_->last_attachment().ssid());
+          EXPECT_EQ(kWifiPassword,
+                    wifi_network_handler_->last_attachment().wifi_password());
+          EXPECT_EQ(kWifiSecurityType,
+                    wifi_network_handler_->last_attachment().security_type());
+
           run_loop_success.Quit();
         }));
 
@@ -1331,116 +1318,111 @@
 
   void ReceiveBadWifiPayload(
       location::nearby::connections::mojom::PayloadPtr wifi_payload) {
-    if (base::FeatureList::IsEnabled(
-            features::kNearbySharingReceiveWifiCredentials)) {
-      for (int64_t payload_id : kValidIntroductionFramePayloadIds) {
-        fake_nearby_connections_manager_->SetPayloadPathStatus(
-            payload_id, location::nearby::connections::mojom::Status::kSuccess);
-      }
+    for (int64_t payload_id : kValidIntroductionFramePayloadIds) {
+      fake_nearby_connections_manager_->SetPayloadPathStatus(
+          payload_id, location::nearby::connections::mojom::Status::kSuccess);
+    }
 
-      NiceMock<MockTransferUpdateCallback> callback;
-      ShareTarget share_target = SetUpIncomingConnection(callback);
+    NiceMock<MockTransferUpdateCallback> callback;
+    ShareTarget share_target = SetUpIncomingConnection(callback);
 
-      base::RunLoop run_loop_accept;
-      EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
-          .WillOnce(testing::Invoke(
-              [](const ShareTarget& share_target, TransferMetadata metadata) {
-                EXPECT_FALSE(metadata.is_final_status());
-                EXPECT_EQ(TransferMetadata::Status::kAwaitingRemoteAcceptance,
-                          metadata.status());
-              }));
-
-      service_->Accept(
-          share_target,
-          base::BindLambdaForTesting(
-              [&](NearbySharingServiceImpl::StatusCodes status_code) {
-                EXPECT_EQ(NearbySharingServiceImpl::StatusCodes::kOk,
-                          status_code);
-                run_loop_accept.Quit();
-              }));
-
-      run_loop_accept.Run();
-
-      fake_nearby_connections_manager_->SetIncomingPayload(
-          kFilePayloadId, GetFilePayloadPtr(kFilePayloadId));
-
-      for (int64_t id : kValidIntroductionFramePayloadIds) {
-        if (id == kFilePayloadId)
-          continue;
-
-        if (id != kWifiCredentialsPayloadId) {
-          fake_nearby_connections_manager_->SetIncomingPayload(
-              id, GetTextPayloadPtr(id, kTextPayload));
-        } else {
-          fake_nearby_connections_manager_->SetIncomingPayload(
-              id, std::move(wifi_payload));
-        }
-
-        base::WeakPtr<NearbyConnectionsManager::PayloadStatusListener>
-            listener = fake_nearby_connections_manager_
-                           ->GetRegisteredPayloadStatusListener(id);
-        ASSERT_TRUE(listener);
-
-        base::RunLoop run_loop_progress;
-        EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
-            .WillOnce(testing::Invoke([&](const ShareTarget& share_target,
-                                          TransferMetadata metadata) {
+    base::RunLoop run_loop_accept;
+    EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
+        .WillOnce(testing::Invoke(
+            [](const ShareTarget& share_target, TransferMetadata metadata) {
               EXPECT_FALSE(metadata.is_final_status());
-              EXPECT_EQ(TransferMetadata::Status::kInProgress,
+              EXPECT_EQ(TransferMetadata::Status::kAwaitingRemoteAcceptance,
                         metadata.status());
-              run_loop_progress.Quit();
             }));
 
-        location::nearby::connections::mojom::PayloadTransferUpdatePtr payload =
-            location::nearby::connections::mojom::PayloadTransferUpdate::New(
-                id,
-                location::nearby::connections::mojom::PayloadStatus::kSuccess,
-                /*total_bytes=*/kPayloadSize,
-                /*bytes_transferred=*/kPayloadSize);
-        listener->OnStatusUpdate(std::move(payload),
-                                 /*upgraded_medium=*/absl::nullopt);
-        run_loop_progress.Run();
+    service_->Accept(
+        share_target,
+        base::BindLambdaForTesting(
+            [&](NearbySharingServiceImpl::StatusCodes status_code) {
+              EXPECT_EQ(NearbySharingServiceImpl::StatusCodes::kOk,
+                        status_code);
+              run_loop_accept.Quit();
+            }));
 
-        task_environment_.FastForwardBy(kMinProgressUpdateFrequency);
+    run_loop_accept.Run();
+
+    fake_nearby_connections_manager_->SetIncomingPayload(
+        kFilePayloadId, GetFilePayloadPtr(kFilePayloadId));
+
+    for (int64_t id : kValidIntroductionFramePayloadIds) {
+      if (id == kFilePayloadId)
+        continue;
+
+      if (id != kWifiCredentialsPayloadId) {
+        fake_nearby_connections_manager_->SetIncomingPayload(
+            id, GetTextPayloadPtr(id, kTextPayload));
+      } else {
+        fake_nearby_connections_manager_->SetIncomingPayload(
+            id, std::move(wifi_payload));
       }
 
-      base::RunLoop run_loop_success;
-      EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
-          .WillOnce(testing::Invoke([&](const ShareTarget& share_target,
-                                        TransferMetadata metadata) {
-            EXPECT_TRUE(metadata.is_final_status());
-            EXPECT_EQ(TransferMetadata::Status::kIncompletePayloads,
-                      metadata.status());
-
-            ASSERT_TRUE(share_target.has_attachments());
-            EXPECT_EQ(0u, wifi_network_handler_->num_configure_network_calls());
-
-            run_loop_success.Quit();
-          }));
-
       base::WeakPtr<NearbyConnectionsManager::PayloadStatusListener> listener =
           fake_nearby_connections_manager_->GetRegisteredPayloadStatusListener(
-              kFilePayloadId);
+              id);
       ASSERT_TRUE(listener);
 
+      base::RunLoop run_loop_progress;
+      EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
+          .WillOnce(testing::Invoke([&](const ShareTarget& share_target,
+                                        TransferMetadata metadata) {
+            EXPECT_FALSE(metadata.is_final_status());
+            EXPECT_EQ(TransferMetadata::Status::kInProgress, metadata.status());
+            run_loop_progress.Quit();
+          }));
+
       location::nearby::connections::mojom::PayloadTransferUpdatePtr payload =
           location::nearby::connections::mojom::PayloadTransferUpdate::New(
-              kFilePayloadId,
-              location::nearby::connections::mojom::PayloadStatus::kSuccess,
+              id, location::nearby::connections::mojom::PayloadStatus::kSuccess,
               /*total_bytes=*/kPayloadSize,
               /*bytes_transferred=*/kPayloadSize);
       listener->OnStatusUpdate(std::move(payload),
                                /*upgraded_medium=*/absl::nullopt);
+      run_loop_progress.Run();
 
-      run_loop_success.Run();
-
-      EXPECT_FALSE(fake_nearby_connections_manager_->connection_endpoint_info(
-          kEndpointId));
-      EXPECT_FALSE(fake_nearby_connections_manager_->has_incoming_payloads());
-
-      // To avoid UAF in OnIncomingTransferUpdate().
-      service_->UnregisterReceiveSurface(&callback);
+      task_environment_.FastForwardBy(kMinProgressUpdateFrequency);
     }
+
+    base::RunLoop run_loop_success;
+    EXPECT_CALL(callback, OnTransferUpdate(testing::_, testing::_))
+        .WillOnce(testing::Invoke([&](const ShareTarget& share_target,
+                                      TransferMetadata metadata) {
+          EXPECT_TRUE(metadata.is_final_status());
+          EXPECT_EQ(TransferMetadata::Status::kIncompletePayloads,
+                    metadata.status());
+
+          ASSERT_TRUE(share_target.has_attachments());
+          EXPECT_EQ(0u, wifi_network_handler_->num_configure_network_calls());
+
+          run_loop_success.Quit();
+        }));
+
+    base::WeakPtr<NearbyConnectionsManager::PayloadStatusListener> listener =
+        fake_nearby_connections_manager_->GetRegisteredPayloadStatusListener(
+            kFilePayloadId);
+    ASSERT_TRUE(listener);
+
+    location::nearby::connections::mojom::PayloadTransferUpdatePtr payload =
+        location::nearby::connections::mojom::PayloadTransferUpdate::New(
+            kFilePayloadId,
+            location::nearby::connections::mojom::PayloadStatus::kSuccess,
+            /*total_bytes=*/kPayloadSize,
+            /*bytes_transferred=*/kPayloadSize);
+    listener->OnStatusUpdate(std::move(payload),
+                             /*upgraded_medium=*/absl::nullopt);
+
+    run_loop_success.Run();
+
+    EXPECT_FALSE(fake_nearby_connections_manager_->connection_endpoint_info(
+        kEndpointId));
+    EXPECT_FALSE(fake_nearby_connections_manager_->has_incoming_payloads());
+
+    // To avoid UAF in OnIncomingTransferUpdate().
+    service_->UnregisterReceiveSurface(&callback);
   }
 
  protected:
@@ -3116,12 +3098,7 @@
         EXPECT_TRUE(share_target.has_attachments());
         EXPECT_EQ(3u, share_target.text_attachments.size());
         EXPECT_EQ(1u, share_target.file_attachments.size());
-        if (base::FeatureList::IsEnabled(
-                features::kNearbySharingReceiveWifiCredentials)) {
-          EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
-        } else {
-          EXPECT_EQ(0u, share_target.wifi_credentials_attachments.size());
-        }
+        EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
         EXPECT_EQ(kDeviceName, share_target.device_name);
         EXPECT_EQ(GURL(kTestMetadataIconUrl), share_target.image_url);
         EXPECT_EQ(kDeviceType, share_target.type);
@@ -3436,13 +3413,8 @@
     // for failure condition.
 
     if (id == kWifiCredentialsPayloadId) {
-      if (base::FeatureList::IsEnabled(
-              features::kNearbySharingReceiveWifiCredentials)) {
-        fake_nearby_connections_manager_->SetIncomingPayload(
-            id, GetWifiPayloadPtr(kWifiCredentialsPayloadId, kWifiPassword));
-      } else {
-        continue;
-      }
+      fake_nearby_connections_manager_->SetIncomingPayload(
+          id, GetWifiPayloadPtr(kWifiCredentialsPayloadId, kWifiPassword));
     }
 
     base::WeakPtr<NearbyConnectionsManager::PayloadStatusListener> listener =
@@ -3741,12 +3713,7 @@
         EXPECT_TRUE(share_target.has_attachments());
         EXPECT_EQ(3u, share_target.text_attachments.size());
         EXPECT_EQ(1u, share_target.file_attachments.size());
-        if (base::FeatureList::IsEnabled(
-                features::kNearbySharingReceiveWifiCredentials)) {
-          EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
-        } else {
-          EXPECT_EQ(0u, share_target.wifi_credentials_attachments.size());
-        }
+        EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
         EXPECT_EQ(kDeviceName, share_target.device_name);
         EXPECT_EQ(GURL(kTestMetadataIconUrl), share_target.image_url);
         EXPECT_EQ(kDeviceType, share_target.type);
@@ -3802,12 +3769,7 @@
         EXPECT_TRUE(share_target.has_attachments());
         EXPECT_EQ(3u, share_target.text_attachments.size());
         EXPECT_EQ(1u, share_target.file_attachments.size());
-        if (base::FeatureList::IsEnabled(
-                features::kNearbySharingReceiveWifiCredentials)) {
-          EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
-        } else {
-          EXPECT_EQ(0u, share_target.wifi_credentials_attachments.size());
-        }
+        EXPECT_EQ(1u, share_target.wifi_credentials_attachments.size());
         EXPECT_EQ(kDeviceName, share_target.device_name);
         EXPECT_EQ(GURL(kTestMetadataIconUrl), share_target.image_url);
         EXPECT_EQ(kDeviceType, share_target.type);
diff --git a/chrome/browser/nearby_sharing/payload_tracker.cc b/chrome/browser/nearby_sharing/payload_tracker.cc
index 8d754d1..6672c38d 100644
--- a/chrome/browser/nearby_sharing/payload_tracker.cc
+++ b/chrome/browser/nearby_sharing/payload_tracker.cc
@@ -53,8 +53,6 @@
 
   for (const auto& wifi_credentials :
        share_target.wifi_credentials_attachments) {
-    DCHECK(base::FeatureList::IsEnabled(
-        features::kNearbySharingReceiveWifiCredentials));
     auto it = attachment_info_map.find(wifi_credentials.id());
     if (it == attachment_info_map.end() || !it->second.payload_id) {
       NS_LOG(WARNING) << __func__
diff --git a/chrome/browser/nearby_sharing/payload_tracker_unittest.cc b/chrome/browser/nearby_sharing/payload_tracker_unittest.cc
index 6b79c20..9d91049 100644
--- a/chrome/browser/nearby_sharing/payload_tracker_unittest.cc
+++ b/chrome/browser/nearby_sharing/payload_tracker_unittest.cc
@@ -8,8 +8,6 @@
 
 #include "base/files/file_path.h"
 #include "base/test/mock_callback.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/nearby_sharing/constants.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata_builder.h"
 #include "content/public/test/browser_task_environment.h"
@@ -68,10 +66,6 @@
       share_target_.text_attachments.push_back(std::move(text));
     }
 
-    base::test::ScopedFeatureList scoped_feature_list;
-    scoped_feature_list.InitAndEnableFeature(
-        features::kNearbySharingReceiveWifiCredentials);
-
     WifiCredentialsAttachment wifi_credentials_ok(
         kWifiCredentialsIdOk, kWifiSecurityType, kWifiSsidOk);
 
diff --git a/chrome/browser/net/storage_test_utils.cc b/chrome/browser/net/storage_test_utils.cc
index a9b53553..30cf0be 100644
--- a/chrome/browser/net/storage_test_utils.cc
+++ b/chrome/browser/net/storage_test_utils.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/net/storage_test_utils.h"
 
+#include "base/strings/stringprintf.h"
 #include "content/public/test/browser_test_utils.h"
 
 namespace storage::test {
@@ -30,6 +31,12 @@
     "  () => { window.domAutomationController.send(false); },"
     ");";
 
+constexpr char kRequestStorageAccessForSite[] =
+    "document.requestStorageAccessForSite('%s').then("
+    "  () => { window.domAutomationController.send(true); },"
+    "  () => { window.domAutomationController.send(false); },"
+    ");";
+
 constexpr char kHasStorageAccess[] =
     "document.hasStorageAccess().then("
     "  (result) => { window.domAutomationController.send(result); },"
@@ -150,6 +157,15 @@
       .ExtractBool();
 }
 
+bool RequestStorageAccessForSite(content::RenderFrameHost* frame,
+                                 const std::string& site) {
+  return content::EvalJs(
+             frame,
+             base::StringPrintf(kRequestStorageAccessForSite, site.c_str()),
+             content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
+      .ExtractBool();
+}
+
 bool HasStorageAccessForFrame(content::RenderFrameHost* frame) {
   return content::EvalJs(frame, kHasStorageAccess,
                          content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
diff --git a/chrome/browser/net/storage_test_utils.h b/chrome/browser/net/storage_test_utils.h
index bfabd381..5c8db81 100644
--- a/chrome/browser/net/storage_test_utils.h
+++ b/chrome/browser/net/storage_test_utils.h
@@ -35,6 +35,11 @@
 // document.requestStorageAccess(). Returns true if the promise resolves; false
 // if it rejects.
 bool RequestStorageAccessForFrame(content::RenderFrameHost* frame);
+// Helper to request storage access with a site override for a frame using
+// document.requestStorageAccessForSite(site). Returns true if the promise
+// resolves; false if it rejects.
+bool RequestStorageAccessForSite(content::RenderFrameHost* frame,
+                                 const std::string& site);
 // Helper to see if a frame currently has storage access using
 // document.hasStorageAccess(). Returns true if the promise resolves with a
 // value of true; false otherwise.
diff --git a/chrome/browser/notifications/notification_interactive_uitest_mac.mm b/chrome/browser/notifications/notification_interactive_uitest_mac.mm
index 4131d89..9b0aa058 100644
--- a/chrome/browser/notifications/notification_interactive_uitest_mac.mm
+++ b/chrome/browser/notifications/notification_interactive_uitest_mac.mm
@@ -27,12 +27,17 @@
   {
     base::scoped_nsobject<WindowedNSNotificationObserver> observer(
         [[WindowedNSNotificationObserver alloc]
-            initForNotification:NSApplicationDidResignActiveNotification
+            initForNotification:NSApplicationDidHideNotification
                          object:NSApp]);
     [NSApp hide:nil];
     [observer wait];
   }
-  EXPECT_FALSE([NSApp isActive]);
+  EXPECT_TRUE([NSApp isHidden]);
+
+  base::scoped_nsobject<WindowedNSNotificationObserver> observer(
+      [[WindowedNSNotificationObserver alloc]
+          initForNotification:NSApplicationDidUnhideNotification
+                       object:NSApp]);
 
   std::string result = CreateNotification(
       browser(), true, "", "", "", "",
@@ -43,13 +48,8 @@
   message_center::Notification* notification =
       *message_center->GetVisibleNotifications().begin();
 
-  {
-    base::scoped_nsobject<WindowedNSNotificationObserver> observer(
-        [[WindowedNSNotificationObserver alloc]
-            initForNotification:NSApplicationDidBecomeActiveNotification
-                         object:NSApp]);
-    message_center->ClickOnNotification(notification->id());
-    [observer wait];
-  }
-  EXPECT_TRUE([NSApp isActive]);
+  message_center->ClickOnNotification(notification->id());
+  [observer wait];
+
+  EXPECT_FALSE([NSApp isHidden]);
 }
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index bfff557..817210a 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -461,7 +461,9 @@
               TouchToFillWebAuthnCredential::DisplayName(
                   suggestion.main_text.value),
               TouchToFillWebAuthnCredential::BackendId(
-                  suggestion.template GetPayload<std::string>()));
+                  (suggestion
+                       .template GetPayload<autofill::Suggestion::BackendId>())
+                      .value()));
         });
   }
 
diff --git a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
index 6d4b81e..c792857e 100644
--- a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
+++ b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
@@ -125,7 +125,8 @@
         password_manager::GetPlatformAuthenticatorLabel());
     suggestion.icon = "globeIcon";
     suggestion.frontend_id = autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL;
-    suggestion.payload = base::Base64Encode(credential.cred_id);
+    suggestion.payload =
+        autofill::Suggestion::BackendId(base::Base64Encode(credential.cred_id));
     suggestions.push_back(std::move(suggestion));
   }
   suggestions_ = std::move(suggestions);
diff --git a/chrome/browser/pdf/pdf_find_request_manager_browsertest.cc b/chrome/browser/pdf/pdf_find_request_manager_browsertest.cc
index e1edc7a..44706f5 100644
--- a/chrome/browser/pdf/pdf_find_request_manager_browsertest.cc
+++ b/chrome/browser/pdf/pdf_find_request_manager_browsertest.cc
@@ -350,4 +350,26 @@
   EXPECT_EQ(1, results.number_of_matches);
 }
 
+// Regression test for crbug.com/1352097.
+IN_PROC_BROWSER_TEST_F(PdfFindRequestManagerTest, SingleResultFindNext) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  LoadAndWait("/find_in_pdf_page.pdf");
+  ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(contents()));
+
+  auto options = blink::mojom::FindOptions::New();
+  Find("pdf", options.Clone());
+  delegate()->MarkNextReply();
+  delegate()->WaitForNextReply();
+
+  options->new_session = false;
+  Find("pdf", options.Clone());
+  delegate()->MarkNextReply();
+  delegate()->WaitForNextReply();
+
+  FindResults results = delegate()->GetFindResults();
+  EXPECT_EQ(last_request_id(), results.request_id);
+  EXPECT_EQ(1, results.number_of_matches);
+  EXPECT_EQ(1, results.active_match_ordinal);
+}
+
 }  // namespace content
diff --git a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
index a8a5b1b7..0ed3fdf 100644
--- a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
+++ b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
@@ -184,11 +184,21 @@
     /**
      * @return Whether or not the native side profile exists.
      */
-    // @VisibleForTesting (Temporarily allow calling this to debug https://crbug.com/1346710).
+    @VisibleForTesting
     public boolean isNativeInitialized() {
         return mNativeProfileAndroid != 0;
     }
 
+    /**
+     * When called, raises an exception if the native pointer is not initialized. This is useful to
+     * get a more debuggable stacktrace than failing on native-side when dereferencing.
+     */
+    public void ensureNativeInitialized() {
+        if (mNativeProfileAndroid == 0) {
+            throw new RuntimeException("Native profile pointer not initialized.");
+        }
+    }
+
     @Override
     public long getNativeBrowserContextPointer() {
         return ProfileJni.get().getBrowserContextPointer(mNativeProfileAndroid);
diff --git a/chrome/browser/renderer_preferences_util.cc b/chrome/browser/renderer_preferences_util.cc
index cc8ea097..ed6ffbb 100644
--- a/chrome/browser/renderer_preferences_util.cc
+++ b/chrome/browser/renderer_preferences_util.cc
@@ -83,13 +83,10 @@
 
 // Extracts the string representation of URLs allowed for local IP exposure.
 std::vector<std::string> GetLocalIpsAllowedUrls(
-    const base::Value* allowed_urls) {
+    const base::Value::List& allowed_urls) {
   std::vector<std::string> ret;
-  if (allowed_urls) {
-    const auto& urls = allowed_urls->GetListDeprecated();
-    for (const auto& url : urls)
-      ret.push_back(url.GetString());
-  }
+  for (const auto& url : allowed_urls)
+    ret.push_back(url.GetString());
   return ret;
 }
 
@@ -139,8 +136,8 @@
   ParsePortRange(webrtc_udp_port_range, &prefs->webrtc_udp_min_port,
                  &prefs->webrtc_udp_max_port);
 
-  const base::Value* allowed_urls =
-      pref_service->GetList(prefs::kWebRtcLocalIpsAllowedUrls);
+  const base::Value::List& allowed_urls =
+      pref_service->GetValueList(prefs::kWebRtcLocalIpsAllowedUrls);
   prefs->webrtc_local_ips_allowed_urls = GetLocalIpsAllowedUrls(allowed_urls);
   prefs->webrtc_allow_legacy_tls_protocols =
       pref_service->GetBoolean(prefs::kWebRTCAllowLegacyTLSProtocols);
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
index a8828ab5..4b99f5f 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
@@ -124,6 +124,10 @@
   created() {
     this.networkConfig_ =
         MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
+    window.CrPolicyStrings = {
+      controlledSettingPolicy:
+          loadTimeData.getString('controlledSettingPolicy'),
+    };
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_import_dialog.ts b/chrome/browser/resources/settings/autofill_page/passwords_import_dialog.ts
index c6110437..bb34b32c 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_import_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/passwords_import_dialog.ts
@@ -166,6 +166,7 @@
         this.handleSuccess_();
         break;
       case chrome.passwordsPrivate.ImportResultsStatus.IO_ERROR:
+      case chrome.passwordsPrivate.ImportResultsStatus.UNKNOWN_ERROR:
         this.descriptionText_ = this.i18n('importPasswordsUnknownError');
         this.dialogState = ImportDialogState.ERROR;
         break;
@@ -230,12 +231,16 @@
 
   private getFailedEntryError_(
       status: chrome.passwordsPrivate.ImportEntryStatus): string {
+    // TODO(crbug/1325290): return appropriate strings for LONG_URL,
+    // NON_ASCII_URL, UNKNOWN_ERROR.
     switch (status) {
       case chrome.passwordsPrivate.ImportEntryStatus.MISSING_PASSWORD:
         return this.i18n('importPasswordsMissingPassword');
       case chrome.passwordsPrivate.ImportEntryStatus.MISSING_URL:
         return this.i18n('importPasswordsMissingURL');
       case chrome.passwordsPrivate.ImportEntryStatus.INVALID_URL:
+      case chrome.passwordsPrivate.ImportEntryStatus.LONG_URL:
+      case chrome.passwordsPrivate.ImportEntryStatus.NON_ASCII_URL:
         return this.i18n('importPasswordsInvalidURL');
       case chrome.passwordsPrivate.ImportEntryStatus.LONG_PASSWORD:
         return this.i18n('importPasswordsLongPassword');
@@ -248,8 +253,11 @@
         return this.i18n('importPasswordsConflictDevice');
       case chrome.passwordsPrivate.ImportEntryStatus.CONFLICT_ACCOUNT:
         return this.i18n('importPasswordsConflictAccount', this.accountEmail);
+      case chrome.passwordsPrivate.ImportEntryStatus.UNKNOWN_ERROR:
+        return '';
+      default:
+        assertNotReached();
     }
-    assertNotReached();
   }
 
   private onCancelClick_() {
diff --git a/chrome/browser/resources/settings/chromeos/os_route.js b/chrome/browser/resources/settings/chromeos/os_route.js
index 4d6e1d83..fac2101 100644
--- a/chrome/browser/resources/settings/chromeos/os_route.js
+++ b/chrome/browser/resources/settings/chromeos/os_route.js
@@ -158,14 +158,6 @@
     r.PERSONALIZATION = createSection(
         r.BASIC, routesMojomWebui.PERSONALIZATION_SECTION_PATH,
         Section.kPersonalization);
-    // Top level PERSONALIZATION section only contains a link to personalization
-    // hub if hub is enabled. The subpages should only be accessible if hub is
-    // off.
-    if (!loadTimeData.getBoolean('isPersonalizationHubEnabled')) {
-      r.CHANGE_PICTURE = createSubpage(
-          r.PERSONALIZATION, routesMojomWebui.CHANGE_PICTURE_SUBPAGE_PATH,
-          Subpage.kChangePicture);
-    }
   }
 
   // Search and Assistant section.
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index b054eee..5b43c8b 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -117,7 +117,6 @@
   "chromeos/os_settings_page/main_page_behavior.js",
   "chromeos/os_settings_routes.js",
   "chromeos/parental_controls_page/parental_controls_browser_proxy.js",
-  "chromeos/personalization_page/change_picture_browser_proxy.js",
   "chromeos/personalization_page/personalization_hub_browser_proxy.js",
   "chromeos/personalization_search_handler.js",
   "chromeos/pref_to_setting_metric_converter.js",
@@ -327,7 +326,6 @@
   "chromeos/os_settings_search_box/os_search_result_row.js",
   "chromeos/os_settings_search_box/os_settings_search_box.js",
   "chromeos/parental_controls_page/parental_controls_page.js",
-  "chromeos/personalization_page/change_picture.js",
   "chromeos/personalization_page/personalization_page.js",
   "chromeos/settings_scheduler_slider/settings_scheduler_slider.js",
 ]
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index a97f69e..e00d775 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -39,7 +39,6 @@
 import './multidevice_page/multidevice_page.js';
 import './nearby_share_page/nearby_share_receive_dialog.js';
 import './nearby_share_page/nearby_share_subpage.js';
-import './personalization_page/change_picture.js';
 import './personalization_page/personalization_page.js';
 import './os_a11y_page/change_dictation_locale_dialog.js';
 import './os_about_page/channel_switcher_dialog.js';
@@ -170,7 +169,6 @@
 export {routes} from './os_route.js';
 export {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from './os_search_page/search_engines_browser_proxy.js';
 export {ParentalControlsBrowserProxy, ParentalControlsBrowserProxyImpl} from './parental_controls_page/parental_controls_browser_proxy.js';
-export {ChangePictureBrowserProxy, ChangePictureBrowserProxyImpl} from './personalization_page/change_picture_browser_proxy.js';
 export {PersonalizationHubBrowserProxy, PersonalizationHubBrowserProxyImpl} from './personalization_page/personalization_hub_browser_proxy.js';
 export {getPersonalizationSearchHandler, setPersonalizationSearchHandlerForTesting} from './personalization_search_handler.js';
 export {getSettingsSearchHandler, setSettingsSearchHandlerForTesting} from './settings_search_handler.js';
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
index 80ecbe5..9d8ab26 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
@@ -10,40 +10,11 @@
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":change_picture",
-    ":change_picture_browser_proxy",
     ":personalization_hub_browser_proxy",
     ":personalization_page",
   ]
 }
 
-js_library("change_picture") {
-  deps = [
-    ":change_picture_browser_proxy",
-    "..:deep_linking_behavior",
-    "..:metrics_recorder",
-    "..:os_route",
-    "..:route_observer_behavior",
-    "../..:router",
-    "//ash/webui/common/resources/cr_picture:cr_picture_list",
-    "//ash/webui/common/resources/cr_picture:cr_picture_pane",
-    "//ash/webui/common/resources/cr_picture:cr_picture_types",
-    "//ash/webui/common/resources/cr_picture:png",
-    "//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js:util.m",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-  externs_list = [ "//ui/webui/resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer_externs.js" ]
-}
-
-js_library("change_picture_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
 js_library("personalization_page") {
   deps = [
     ":personalization_hub_browser_proxy",
@@ -63,8 +34,5 @@
 }
 
 html_to_js("web_components") {
-  js_files = [
-    "change_picture.js",
-    "personalization_page.js",
-  ]
+  js_files = [ "personalization_page.js" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html
deleted file mode 100644
index 3c2d96b..0000000
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<style include="settings-shared">
-  :host {
-    /* #headerLine height + padding */
-    --cr-settings-header-height: calc(62px + 1.34em);
-    --title-height: 2em;
-    --title-padding: 16px;
-    display: block;
-    min-height: 328px;
-  }
-
-  #title {
-    height: var(--title-height);
-    margin-inline-start: 20px;
-    padding-top: var(--title-padding);
-  }
-
-  #container {
-    align-items: flex-start;
-    display: flex;
-    margin-inline-start: 20px;
-    position: absolute;
-    top: calc(var(--cr-settings-header-height) +
-              var(--title-padding) +
-              var(--title-height));
-    user-select: none;
-  }
-
-  #picturePane {
-    --cr-picture-image-size: 192px;
-    flex-shrink: 0;
-    height: 288px;
-    margin-inline-end: 24px;
-    margin-top: 6px;
-    position: relative;
-    width: 288px;
-  }
-
-  #sourceInfo {
-    color: var(--cros-text-color-disabled);
-    display: flex;
-    flex-direction: column;
-    margin-top: 20px;
-  }
-
-  #pictureList {
-    /* TODO(reveman): Find a way to have height align to viewport
-        without using fixed position. */
-    height: calc(100vh -
-        var(--cr-toolbar-height) -
-        var(--cr-toolbar-padding-top) -
-        var(--cr-settings-header-height) -
-        var(--title-padding) -
-        var(--title-height));
-    margin-inline-end: 16px;
-    margin-top: 0;
-    min-height: 332px;
-    overflow-x: hidden;
-    overflow-y: auto;
-    position: relative;
-  }
-
-</style>
-<div id="title">$i18n{changePicturePageDescription}</div>
-<div id="container">
-  <div>
-    <cr-picture-pane id="picturePane"
-        camera-present="[[cameraPresent_]]",
-        image-src="[[getImageSrc_(selectedItem_)]]"
-        image-type="[[getImageType_(selectedItem_)]]"
-        discard-image-label="$i18n{discardPhoto}"
-        preview-alt-text="$i18n{previewAltText}"
-        take-photo-label="$i18n{takePhoto}"
-        capture-video-label="$i18n{captureVideo}"
-        switch-mode-to-camera-label="$i18n{switchModeToCamera}"
-        switch-mode-to-video-label="$i18n{switchModeToVideo}"
-        camera-video-mode-enabled="[[cameraVideoModeEnabled_]]"
-        on-keys-pressed="onCameraPaneKeysPressed_">
-    </cr-picture-pane>
-    <div id="sourceInfo"
-        hidden="[[!shouldShowSourceInfo_(selectedItem_, authorInfo_, websiteInfo_)]]">
-      [[authorInfo_]]
-      <a href="[[websiteInfo_]]" target="_blank">
-        [[websiteInfo_]]
-      </a>
-    </div>
-  </div>
-  <cr-picture-list id="pictureList"
-      hidden="[[!currentDefaultImages_]]"
-      camera-present="[[cameraPresent_]]"
-      default-images="[[currentDefaultImages_]]"
-      selected-item="{{selectedItem_}}"
-      choose-file-label="$i18n{chooseFile}"
-      old-image-label="[[oldImageLabel_]]"
-      profile-image-label="$i18n{profilePhoto}"
-      take-photo-label="$i18n{takePhoto}"
-      capture-video-label="$i18n{captureVideo}">
-  </cr-picture-list>
-</div>
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
deleted file mode 100644
index 24c6463..0000000
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture.js
+++ /dev/null
@@ -1,405 +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.
-
-/**
- * @fileoverview
- * 'settings-change-picture' is the settings subpage containing controls to
- * edit a ChromeOS user's picture.
- */
-import 'chrome://resources/ash/common/cr_picture/cr_picture_list.js';
-import 'chrome://resources/ash/common/cr_picture/cr_picture_pane.js';
-import '../../settings_shared.css.js';
-
-import {CrPicture} from 'chrome://resources/ash/common/cr_picture/cr_picture_types.js';
-import {isEncodedPngDataUrlAnimated} from 'chrome://resources/ash/common/cr_picture/png.js';
-import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
-import {assert, assertNotReached} from 'chrome://resources/js/assert.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 {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {loadTimeData} from '../../i18n_setup.js';
-import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
-import {recordSettingChange} from '../metrics_recorder.js';
-import {routes} from '../os_route.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
-
-import {ChangePictureBrowserProxy, ChangePictureBrowserProxyImpl, DefaultImage} from './change_picture_browser_proxy.js';
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {DeepLinkingBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- * @implements {I18nBehaviorInterface}
- * @implements {WebUIListenerBehaviorInterface}
- */
-const SettingsChangePictureElementBase = mixinBehaviors(
-    [
-      DeepLinkingBehavior,
-      RouteObserverBehavior,
-      I18nBehavior,
-      WebUIListenerBehavior,
-    ],
-    PolymerElement);
-
-/** @polymer */
-class SettingsChangePictureElement extends SettingsChangePictureElementBase {
-  static get is() {
-    return 'settings-change-picture';
-  }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
-  static get properties() {
-    return {
-      /**
-       * True if the user has a plugged-in webcam.
-       * @private {boolean}
-       */
-      cameraPresent_: {
-        type: Boolean,
-        value: false,
-      },
-
-      /**
-       * The currently selected item. This property is bound to the
-       * iron-selector and never directly assigned. This may be undefined
-       * momentarily as the selection changes due to iron-selector
-       * implementation details.
-       * @private {?CrPicture.ImageElement}
-       */
-      selectedItem_: {
-        type: Object,
-        value: null,
-      },
-
-      /**
-       * The current set of the default user images.
-       * @private {?Array<!DefaultImage>}
-       */
-      currentDefaultImages_: {
-        type: Object,
-        value: null,
-      },
-
-      /**
-       * True when camera video mode is enabled.
-       * @private {boolean}
-       */
-      cameraVideoModeEnabled_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('changePictureVideoModeEnabled');
-        },
-        readOnly: true,
-      },
-
-      /**
-       * Author info of the default image.
-       * @private {string}
-       */
-      authorInfo_: String,
-
-      /**
-       * Website info of the default image.
-       * @private {string}
-       */
-      websiteInfo_: String,
-
-      /** @private */
-      oldImageLabel_: String,
-
-      /**
-       * Used by DeepLinkingBehavior to focus this page's deep links.
-       * @type {!Set<!Setting>}
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set([Setting.kChangeDeviceAccountImage]),
-      },
-    };
-  }
-
-  constructor() {
-    super();
-
-    /** @private {!ChangePictureBrowserProxy} */
-    this.browserProxy_ = ChangePictureBrowserProxyImpl.getInstance();
-
-    /** @private {?CrPictureListElement} */
-    this.pictureList_ = null;
-
-    /** @private {boolean} */
-    this.oldImagePending_ = false;
-  }
-
-  /** @override */
-  ready() {
-    super.ready();
-
-    this.pictureList_ =
-        /** @type {CrPictureListElement} */ (this.$.pictureList);
-
-    this.addEventListener('discard-image', this.onDiscardImage_);
-    this.addEventListener('image-activate', (e) => {
-      this.onImageActivate_(
-          /** @type {!CustomEvent<!CrPicture.ImageElement>} */ (e));
-    });
-    this.addEventListener('focus-action', this.onFocusAction_);
-    this.addEventListener('photo-taken', (e) => {
-      this.onPhotoTaken_(
-          /** @type {!CustomEvent<{photoDataUrl: string}>} */ (e));
-    });
-    this.addEventListener('switch-mode', (e) => {
-      this.onSwitchMode_(/** @type {!CustomEvent<boolean>} */ (e));
-    });
-  }
-
-  /** @override */
-  connectedCallback() {
-    super.connectedCallback();
-
-    this.addWebUIListener(
-        'default-images-changed', this.receiveDefaultImages_.bind(this));
-    this.addWebUIListener(
-        'selected-image-changed', this.receiveSelectedImage_.bind(this));
-    this.addWebUIListener(
-        'old-image-changed', this.receiveOldImage_.bind(this));
-    this.addWebUIListener(
-        'preview-deprecated-image',
-        this.receivePreviewDeprecatedImage_.bind(this));
-    this.addWebUIListener(
-        'profile-image-changed', this.receiveProfileImage_.bind(this));
-    this.addWebUIListener(
-        'camera-presence-changed', this.receiveCameraPresence_.bind(this));
-  }
-
-  /**
-   * Overridden from DeepLinkingBehavior.
-   * @param {!Setting} settingId
-   * @return {boolean}
-   */
-  beforeDeepLinkAttempt(settingId) {
-    assert(settingId === Setting.kChangeDeviceAccountImage);
-
-    this.pictureList_.setFocus();
-    return false;
-  }
-
-
-  /** @protected */
-  currentRouteChanged(newRoute) {
-    if (newRoute === routes.CHANGE_PICTURE) {
-      this.browserProxy_.initialize();
-      this.browserProxy_.requestSelectedImage();
-      this.pictureList_.setFocus();
-      this.attemptDeepLink();
-    } else {
-      // Ensure we deactivate the camera when we navigate away.
-      this.selectedItem_ = null;
-    }
-  }
-
-  /**
-   * Handler for the 'default-images-changed' event.
-   * @param {{current_default_images: !Array<!DefaultImage>}} info
-   * @private
-   */
-  receiveDefaultImages_(info) {
-    this.currentDefaultImages_ = info.current_default_images;
-  }
-
-  /**
-   * Handler for the 'selected-image-changed' event. Is only called with
-   * default images.
-   * @param {string} imageUrl
-   * @private
-   */
-  receiveSelectedImage_(imageUrl) {
-    this.pictureList_.setSelectedImageUrl(imageUrl);
-  }
-
-  /**
-   * Handler for the 'old-image-changed' event. The Old image is any selected
-   * non-profile and non-default image. It can be from the camera or a file.
-   * When this method is called, the Old image becomes the selected image.
-   * @param {string} imageUrl
-   * @private
-   */
-  receiveOldImage_(imageUrl) {
-    this.oldImageLabel_ = this.i18n(
-        isEncodedPngDataUrlAnimated(imageUrl) ? 'oldVideo' : 'oldPhoto');
-    this.oldImagePending_ = false;
-    this.pictureList_.setOldImageUrl(imageUrl);
-  }
-
-  /**
-   * Handler for the 'preview-deprecated-image' event.
-   * When this method is called, preview the deprecated default image in
-   * picturePane while do not show in the pictureList.
-   * Also set the source info for the deprecated image.
-   * @param {!{url: string, author: string, website: string}} imageInfo
-   * @private
-   */
-  receivePreviewDeprecatedImage_(imageInfo) {
-    this.$.picturePane.previewDeprecatedImage(imageInfo.url);
-    this.authorInfo_ =
-        imageInfo.author ? this.i18n('authorCreditText', imageInfo.author) : '';
-    this.websiteInfo_ = imageInfo.website;
-    this.selectedItem_ = null;
-  }
-
-  /**
-   * Whether the source info should be shown.
-   * @param {CrPicture.ImageElement} selectedItem
-   * @param {string} authorInfo
-   * @param {string} websiteInfo
-   * @private
-   */
-  shouldShowSourceInfo_(selectedItem, authorInfo, websiteInfo) {
-    return !selectedItem && (authorInfo || websiteInfo);
-  }
-
-  /**
-   * Handler for the 'profile-image-changed' event.
-   * @param {string} imageUrl
-   * @param {boolean} selected
-   * @private
-   */
-  receiveProfileImage_(imageUrl, selected) {
-    this.pictureList_.setProfileImageUrl(imageUrl, selected);
-  }
-
-  /**
-   * Handler for the 'camera-presence-changed' event.
-   * @param {boolean} cameraPresent
-   * @private
-   */
-  receiveCameraPresence_(cameraPresent) {
-    this.cameraPresent_ = cameraPresent;
-  }
-
-  /**
-   * Selects an image element.
-   * @param {!CrPicture.ImageElement} image
-   * @private
-   */
-  selectImage_(image) {
-    switch (image.dataset.type) {
-      case CrPicture.SelectionTypes.CAMERA:
-        /** CrPicturePaneElement */ (this.$.picturePane).takePhoto();
-        break;
-      case CrPicture.SelectionTypes.FILE:
-        this.browserProxy_.chooseFile();
-        recordSettingChange();
-        break;
-      case CrPicture.SelectionTypes.PROFILE:
-        this.browserProxy_.selectProfileImage();
-        recordSettingChange();
-        break;
-      case CrPicture.SelectionTypes.OLD:
-        this.browserProxy_.selectOldImage();
-        recordSettingChange();
-        break;
-      case CrPicture.SelectionTypes.DEFAULT:
-        this.browserProxy_.selectDefaultImage(image.dataset.url);
-        recordSettingChange();
-        break;
-      default:
-        assertNotReached('Selected unknown image type');
-    }
-  }
-
-  /**
-   * Handler for when an image is activated.
-   * @param {!CustomEvent<!CrPicture.ImageElement>} event
-   * @private
-   */
-  onImageActivate_(event) {
-    this.selectImage_(event.detail);
-  }
-
-  /** Focus the action button in the picture pane. */
-  onFocusAction_() {
-    /** CrPicturePaneElement */ (this.$.picturePane).focusActionButton();
-  }
-
-  /**
-   * @param {!CustomEvent<{photoDataUrl: string}>} event
-   * @private
-   */
-  onPhotoTaken_(event) {
-    this.oldImagePending_ = true;
-    this.browserProxy_.photoTaken(event.detail.photoDataUrl);
-    this.pictureList_.setOldImageUrl(event.detail.photoDataUrl);
-    this.pictureList_.setFocus();
-    getAnnouncerInstance().announce(
-        loadTimeData.getString('photoCaptureAccessibleText'));
-  }
-
-  /**
-   * @param {!CustomEvent<boolean>} event
-   * @private
-   */
-  onSwitchMode_(event) {
-    const videomode = event.detail;
-    getAnnouncerInstance().announce(this.i18n(
-        videomode ? 'videoModeAccessibleText' : 'photoModeAccessibleText'));
-  }
-
-  /**
-   * Callback the iron-a11y-keys "keys-pressed" event bubbles up from the
-   * cr-camera-pane.
-   * @param {!CustomEvent<!{key: string, keyboardEvent: Object}>} event
-   * @private
-   */
-  onCameraPaneKeysPressed_(event) {
-    this.$.pictureList.focus();
-    this.$.pictureList.onKeysPressed(event);
-  }
-
-  /** @private */
-  onDiscardImage_() {
-    // Prevent image from being discarded if old image is pending.
-    if (this.oldImagePending_) {
-      return;
-    }
-    this.pictureList_.setOldImageUrl(CrPicture.kDefaultImageUrl);
-    // Revert to profile image as we don't know what last used default image is.
-    this.browserProxy_.selectProfileImage();
-
-    const event = new CustomEvent('iron-announce', {
-      bubbles: true,
-      composed: true,
-      detail: {text: this.i18n('photoDiscardAccessibleText')},
-    });
-    this.dispatchEvent(event);
-  }
-
-  /**
-   * @param {CrPicture.ImageElement} selectedItem
-   * @return {string}
-   * @private
-   */
-  getImageSrc_(selectedItem) {
-    return (selectedItem && selectedItem.dataset.url) || '';
-  }
-
-  /**
-   * @param {CrPicture.ImageElement} selectedItem
-   * @return {string}
-   * @private
-   */
-  getImageType_(selectedItem) {
-    return (selectedItem && selectedItem.dataset.type) ||
-        CrPicture.SelectionTypes.NONE;
-  }
-}
-
-customElements.define(
-    SettingsChangePictureElement.is, SettingsChangePictureElement);
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js b/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js
deleted file mode 100644
index c6cff981..0000000
--- a/chrome/browser/resources/settings/chromeos/personalization_page/change_picture_browser_proxy.js
+++ /dev/null
@@ -1,115 +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.
-
-/**
- * An object describing a default image.
- * @typedef {{
- *   author: (string|undefined),
- *   index: number,
- *   title: (string|undefined),
- *   url: string,
- *   website: (string|undefined)
- * }}
- */
-export let DefaultImage;
-
-/** @interface */
-export class ChangePictureBrowserProxy {
-  /**
-   * Retrieves the initial set of default images, profile image, etc. As a
-   * response, the C++ sends these WebUIListener events:
-   * 'default-images-changed', 'profile-image-changed', 'old-image-changed',
-   * and 'selected-image-changed'
-   */
-  initialize() {}
-
-  /**
-   * Sets the user image to one of the default images. As a response, the C++
-   * sends the 'default-images-changed' WebUIListener event.
-   * @param {string} imageUrl
-   */
-  selectDefaultImage(imageUrl) {}
-
-  /**
-   * Sets the user image to the 'old' image. As a response, the C++ sends the
-   * 'old-image-changed' WebUIListener event.
-   */
-  selectOldImage() {}
-
-  /**
-   * Sets the user image to the profile image. As a response, the C++ sends
-   * the 'profile-image-changed' WebUIListener event.
-   */
-  selectProfileImage() {}
-
-  /**
-   * Provides the taken photo as a data URL to the C++ and sets the user
-   * image to the 'old' image. As a response, the C++ sends the
-   * 'old-image-changed' WebUIListener event.
-   * @param {string} photoDataUrl
-   */
-  photoTaken(photoDataUrl) {}
-
-  /**
-   * Requests a file chooser to select a new user image. No response is
-   * expected.
-   */
-  chooseFile() {}
-
-  /** Requests the currently selected image. */
-  requestSelectedImage() {}
-}
-
-/** @type {?ChangePictureBrowserProxy} */
-let instance = null;
-
-/**
- * @implements {ChangePictureBrowserProxy}
- */
-export class ChangePictureBrowserProxyImpl {
-  /** @return {!ChangePictureBrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new ChangePictureBrowserProxyImpl());
-  }
-
-  /** @param {!ChangePictureBrowserProxy} obj */
-  static setInstanceForTesting(obj) {
-    instance = obj;
-  }
-
-  /** @override */
-  initialize() {
-    chrome.send('onChangePicturePageInitialized');
-  }
-
-  /** @override */
-  selectDefaultImage(imageUrl) {
-    chrome.send('selectImage', [imageUrl, 'default']);
-  }
-
-  /** @override */
-  selectOldImage() {
-    chrome.send('selectImage', ['', 'old']);
-  }
-
-  /** @override */
-  selectProfileImage() {
-    chrome.send('selectImage', ['', 'profile']);
-  }
-
-  /** @override */
-  photoTaken(photoDataUrl) {
-    chrome.send('photoTaken', [photoDataUrl]);
-  }
-
-  /** @override */
-  chooseFile() {
-    chrome.send('chooseFile');
-  }
-
-  /** @override */
-  requestSelectedImage() {
-    chrome.send('requestSelectedImage');
-  }
-}
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
index 23731f2..ffb20436 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.html
@@ -9,19 +9,6 @@
           on-click="openPersonalizationHub_">
       </cr-link-row>
     </template>
-
-    <template is="dom-if" if="[[!isPersonalizationHubEnabled_]]">
-      <cr-link-row id="changePictureRow"
-          label="$i18n{changePictureTitle}"
-          on-click="navigateToChangePicture_"
-          role-description="$i18n{subpageArrowRoleDescription}">
-      </cr-link-row>
-    </template>
   </div>
 
-  <template is="dom-if" route-path="/changePicture">
-    <settings-subpage page-title="$i18n{changePictureTitle}">
-      <settings-change-picture></settings-change-picture>
-    </settings-subpage>
-  </template>
 </settings-animated-pages>
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
index 06f039f6..c515f74e 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/personalization_page.js
@@ -7,12 +7,10 @@
  * personalization settings.
  */
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
-import './change_picture.js';
 import '../../settings_page/settings_animated_pages.js';
 import '../../settings_page/settings_subpage.js';
 import '../../settings_shared.css.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 {loadTimeData} from '../../i18n_setup.js';
@@ -28,11 +26,10 @@
  * @constructor
  * @extends {PolymerElement}
  * @implements {DeepLinkingBehaviorInterface}
- * @implements {I18nBehaviorInterface}
  * @implements {RouteObserverBehaviorInterface}
  */
 const SettingsPersonalizationPageElementBase = mixinBehaviors(
-    [DeepLinkingBehavior, I18nBehavior, RouteObserverBehavior], PolymerElement);
+    [DeepLinkingBehavior, RouteObserverBehavior], PolymerElement);
 
 /** @polymer */
 class SettingsPersonalizationPageElement extends
@@ -66,10 +63,6 @@
         type: Object,
         value() {
           const map = new Map();
-          if (routes.CHANGE_PICTURE) {
-            map.set(routes.CHANGE_PICTURE.path, '#changePictureRow');
-          }
-
           return map;
         },
       },
@@ -111,11 +104,6 @@
   openPersonalizationHub_() {
     this.personalizationHubBrowserProxy_.openPersonalizationHub();
   }
-
-  /** @private */
-  navigateToChangePicture_() {
-    Router.getInstance().navigateTo(routes.CHANGE_PICTURE);
-  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/settings/languages_page/languages.ts b/chrome/browser/resources/settings/languages_page/languages.ts
index cf03ac7..022ac91 100644
--- a/chrome/browser/resources/settings/languages_page/languages.ts
+++ b/chrome/browser/resources/settings/languages_page/languages.ts
@@ -56,6 +56,7 @@
   translateTarget: string;
   alwaysTranslateCodes: string[];
   neverTranslateCodes: string[];
+  neverTranslateSites: string[];
   startingUILanguage: string;
   supportedInputMethods?: chrome.languageSettingsPrivate.InputMethod[];
   currentInputMethodId?: string;
@@ -132,6 +133,8 @@
           'prefs.translate_allowlists.value.*, languages)',
       'neverTranslateLanguagesPrefChanged_(' +
           'prefs.translate_blocked_languages.value.*, languages)',
+      'neverTranslateSitesPrefChanged_(' +
+          'prefs.translate_site_blocklist_with_time.value.*, languages)',
       // <if expr="is_win">
       'prospectiveUILanguageChanged_(prefs.intl.app_locale.value, languages)',
       // </if>
@@ -196,6 +199,7 @@
       translateTarget: '',
       alwaysTranslateCodes: [],
       neverTranslateCodes: [],
+      neverTranslateSites: [],
       startingUILanguage: '',
 
       // Only used by ChromeOS
@@ -469,6 +473,18 @@
     this.set('languages.neverTranslate', neverTranslateLanguages);
   }
 
+  /**
+   * Updates the list of never translate sites from translate prefs.
+   */
+  private neverTranslateSitesPrefChanged_() {
+    if (this.prefs === undefined || this.languages === undefined) {
+      return;
+    }
+    const neverTranslateSites =
+        Object.keys(this.getPref('translate_site_blocklist_with_time').value);
+    this.set('languages.neverTranslateSites', neverTranslateSites);
+  }
+
   private translateLanguagesPrefChanged_() {
     if (this.prefs === undefined || this.languages === undefined) {
       return;
@@ -543,6 +559,7 @@
       translateTarget: args.translateTarget,
       alwaysTranslate: alwaysTranslateLanguages,
       neverTranslate: neverTranslateLangauges,
+      neverTranslateSites: args.neverTranslateSites,
       spellCheckOnLanguages,
       spellCheckOffLanguages,
       // <if expr="is_win">
diff --git a/chrome/browser/resources/settings/languages_page/languages_types.ts b/chrome/browser/resources/settings/languages_page/languages_types.ts
index 76dee02..0f4777a 100644
--- a/chrome/browser/resources/settings/languages_page/languages_types.ts
+++ b/chrome/browser/resources/settings/languages_page/languages_types.ts
@@ -56,6 +56,7 @@
   translateTarget: string;
   alwaysTranslate: chrome.languageSettingsPrivate.Language[];
   neverTranslate: chrome.languageSettingsPrivate.Language[];
+  neverTranslateSites: string[];
   spellCheckOnLanguages: SpellCheckLanguageState[];
   spellCheckOffLanguages: SpellCheckLanguageState[];
   // TODO(dpapad): Wrap prospectiveUILanguage with if expr "is_win" block.
diff --git a/chrome/browser/resources/tab_search/app.ts b/chrome/browser/resources/tab_search/app.ts
index 5964c7c..6295dd8 100644
--- a/chrome/browser/resources/tab_search/app.ts
+++ b/chrome/browser/resources/tab_search/app.ts
@@ -401,10 +401,7 @@
     this.tabItemAction_(tabItem, e.model.index);
   }
 
-  private recordMetricsForAction(
-      action: string, isMediaTab: boolean, tabIndex: number,
-      indexRelativeToSection: number,
-      distanceFromInitiallySelectedIndex: number) {
+  private recordMetricsForAction(action: string, tabIndex: number) {
     const withSearch = !!this.searchText_;
     if (action === 'SwitchTab') {
       chrome.metricsPrivate.recordEnumerationValue(
@@ -417,21 +414,6 @@
         withSearch ? `Tabs.TabSearch.WebUI.IndexOf${action}InFilteredList` :
                      `Tabs.TabSearch.WebUI.IndexOf${action}InUnfilteredList`,
         tabIndex);
-    chrome.metricsPrivate.recordSmallCount(
-        withSearch ? `Tabs.TabSearch.DistanceOf${
-                         action}FromInitiallySelectedTabInFilteredList` :
-                     `Tabs.TabSearch.DistanceOf${
-                         action}FromInitiallySelectedTabInUnfilteredList`,
-        distanceFromInitiallySelectedIndex);
-    if (isMediaTab) {
-      chrome.metricsPrivate.recordBoolean(
-          `Tabs.TabSearch.WebUI.MediaTab${action}Action`, withSearch);
-    } else if (!withSearch) {
-      chrome.metricsPrivate.recordSmallCount(
-          `Tabs.TabSearch.WebUI.IndexRelativeToOpenTabsSectionOf${
-              action}InUnfilteredList`,
-          indexRelativeToSection);
-    }
   }
 
   /**
@@ -452,12 +434,7 @@
           }
         }
 
-        const isMediaTab = tabHasMediaAlerts((itemData as TabData).tab as Tab);
-        const tabIndexRelativeToSection =
-            isMediaTab ? tabIndex : tabIndex - this.filteredMediaTabsCount_;
-        this.recordMetricsForAction(
-            'SwitchTab', isMediaTab, tabIndex, tabIndexRelativeToSection,
-            Math.abs(this.initiallySelectedTabIndex_ - tabIndex));
+        this.recordMetricsForAction('SwitchTab', tabIndex);
         this.apiProxy_.switchToTab({tabId: (itemData as TabData).tab.tabId});
         action = 'SwitchTab';
         break;
@@ -486,12 +463,7 @@
     performance.mark('tab_search:close_tab:metric_begin');
     const tabId = e.model.item.tab.tabId;
     const tabIndex = e.model.index;
-    const isMediaTab = tabHasMediaAlerts(e.model.item.tab as Tab);
-    const tabIndexRelativeToSection =
-        isMediaTab ? tabIndex : tabIndex - this.filteredMediaTabsCount_;
-    this.recordMetricsForAction(
-        'CloseTab', isMediaTab, tabIndex, tabIndexRelativeToSection,
-        Math.abs(this.initiallySelectedTabIndex_ - tabIndex));
+    this.recordMetricsForAction('CloseTab', tabIndex);
     this.apiProxy_.closeTab(tabId);
     this.announceA11y_(loadTimeData.getString('a11yTabClosed'));
     listenOnce(this.$.tabsList, 'iron-items-changed', () => {
@@ -697,8 +669,7 @@
           filteredMediaTabs.length;
     }
 
-    if (!loadTimeData.getBoolean('alsoShowMediaTabsinOpenTabsSection') &&
-        this.searchText_.length === 0) {
+    if (this.searchText_.length === 0) {
       filteredOpenTabs = filteredOpenTabs.filter(
           tabData => !tabHasMediaAlerts(tabData.tab as Tab));
     }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
index b85e041..a488832 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
@@ -59,9 +59,9 @@
                 if (!LinkToTextHelper.hasTextFragment(url)) return;
 
                 Profile profile = profileSupplier.get();
-                // In some cases, ProfileSupplier.get() will return null or will not be initialized
-                // in Native. See https://crbug.com/1346710 and https://crbug.com/1353138.
-                if (profile == null || !profile.isNativeInitialized()) {
+                // In some cases, ProfileSupplier.get() will return null. See
+                // https://crbug.com/1346710 and https://crbug.com/1353138.
+                if (profile == null) {
                     profile = Profile.getLastUsedRegularProfile();
                 }
                 if (profile == null) {
diff --git a/chrome/browser/speech/tts_crosapi_util.cc b/chrome/browser/speech/tts_crosapi_util.cc
index 415565d4..b54ee9d2 100644
--- a/chrome/browser/speech/tts_crosapi_util.cc
+++ b/chrome/browser/speech/tts_crosapi_util.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/speech/tts_crosapi_util.h"
 
 #include "base/feature_list.h"
+#include "base/values.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/profiles/profile_manager.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_features.h"
@@ -103,6 +105,71 @@
   return mojo_voice;
 }
 
+crosapi::mojom::TtsUtterancePtr ToMojo(content::TtsUtterance* utterance) {
+  auto mojo_utterance = crosapi::mojom::TtsUtterance::New();
+  mojo_utterance->utterance_id = utterance->GetId();
+  mojo_utterance->text = utterance->GetText();
+  mojo_utterance->lang = utterance->GetLang();
+  mojo_utterance->voice_name = utterance->GetVoiceName();
+  mojo_utterance->volume = utterance->GetContinuousParameters().volume;
+  mojo_utterance->rate = utterance->GetContinuousParameters().rate;
+  mojo_utterance->pitch = utterance->GetContinuousParameters().pitch;
+  mojo_utterance->engine_id = utterance->GetEngineId();
+  mojo_utterance->should_clear_queue = utterance->GetShouldClearQueue();
+  mojo_utterance->src_id = utterance->GetSrcId();
+  mojo_utterance->src_url = utterance->GetSrcUrl();
+
+  for (const auto& event : utterance->GetDesiredEventTypes())
+    mojo_utterance->desired_event_types.push_back(
+        tts_crosapi_util::ToMojo(event));
+
+  for (const auto& event : utterance->GetRequiredEventTypes())
+    mojo_utterance->required_event_types.push_back(
+        tts_crosapi_util::ToMojo(event));
+
+  content::WebContents* web_contents = utterance->GetWebContents();
+  mojo_utterance->was_created_with_web_contents = web_contents != nullptr;
+
+  base::Value::Dict options = utterance->GetOptions()->Clone();
+  mojo_utterance->options = std::move(options);
+
+  return mojo_utterance;
+}
+
+std::unique_ptr<content::TtsUtterance> FromMojo(
+    crosapi::mojom::TtsUtterancePtr& mojo_utterance) {
+  // Construct TtsUtterance object.
+  content::BrowserContext* browser_context =
+      ProfileManager::GetPrimaryUserProfile();
+  std::unique_ptr<content::TtsUtterance> utterance =
+      content::TtsUtterance::Create(browser_context,
+                                    /*should_always_be_spoken=*/true);
+
+  utterance->SetText(mojo_utterance->text);
+  utterance->SetLang(mojo_utterance->lang);
+  utterance->SetVoiceName(mojo_utterance->voice_name);
+  utterance->SetContinuousParameters(
+      mojo_utterance->rate, mojo_utterance->pitch, mojo_utterance->volume);
+  utterance->SetEngineId(mojo_utterance->engine_id);
+  utterance->SetShouldClearQueue(mojo_utterance->should_clear_queue);
+  utterance->SetSrcUrl(mojo_utterance->src_url);
+  utterance->SetSrcId(mojo_utterance->src_id);
+
+  std::set<content::TtsEventType> desired_events;
+  for (const auto& mojo_event : mojo_utterance->desired_event_types)
+    desired_events.insert(tts_crosapi_util::FromMojo(mojo_event));
+  utterance->SetDesiredEventTypes(std::move(desired_events));
+
+  std::set<content::TtsEventType> required_events;
+  for (const auto& mojo_event : mojo_utterance->required_event_types)
+    required_events.insert(tts_crosapi_util::FromMojo(mojo_event));
+  utterance->SetRequiredEventTypes(std::move(required_events));
+
+  base::Value::Dict options = mojo_utterance->options.Clone();
+  utterance->SetOptions(std::move(options));
+  return utterance;
+}
+
 bool ShouldEnableLacrosTtsSupport() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   bool lacros_tts_support_enabled =
diff --git a/chrome/browser/speech/tts_crosapi_util.h b/chrome/browser/speech/tts_crosapi_util.h
index b33cef0..832c739c 100644
--- a/chrome/browser/speech/tts_crosapi_util.h
+++ b/chrome/browser/speech/tts_crosapi_util.h
@@ -15,6 +15,9 @@
 crosapi::mojom::TtsEventType ToMojo(content::TtsEventType event_type);
 content::VoiceData FromMojo(const crosapi::mojom::TtsVoicePtr& mojo_voice);
 crosapi::mojom::TtsVoicePtr ToMojo(const content::VoiceData& voice);
+crosapi::mojom::TtsUtterancePtr ToMojo(content::TtsUtterance* utterance);
+std::unique_ptr<content::TtsUtterance> FromMojo(
+    crosapi::mojom::TtsUtterancePtr& mojo_utterance);
 
 bool ShouldEnableLacrosTtsSupport();
 
diff --git a/chrome/browser/speech/tts_crosapi_util_unittest.cc b/chrome/browser/speech/tts_crosapi_util_unittest.cc
new file mode 100644
index 0000000..f930301a
--- /dev/null
+++ b/chrome/browser/speech/tts_crosapi_util_unittest.cc
@@ -0,0 +1,189 @@
+// 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 <set>
+
+#include "base/unguessable_token.h"
+#include "base/values.h"
+#include "chrome/browser/speech/extension_api/tts_extension_api_constants.h"
+#include "chrome/browser/speech/tts_crosapi_util.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/crosapi/mojom/tts.mojom.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/tts_controller.h"
+#include "content/public/browser/tts_utterance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kText[] = "hello, world";
+const char kVoiceName[] = "Alice";
+const char kLang[] = "en-GB";
+const int kSrcId = 12345;
+const char kSrcUrl[] = "http://test.com";
+const char kEngineId[] = "test_engine_id";
+const double kRate = 1.0f;
+const double kPitch = 0.5f;
+const double kVolume = 0.9f;
+const base::UnguessableToken kBrowerContextId =
+    base::UnguessableToken::Create();
+
+bool EventTypesMatches(
+    const std::set<content::TtsEventType>& tts_event_types_in,
+    const std::set<content::TtsEventType>& tts_event_types_out) {
+  if (tts_event_types_in.size() != tts_event_types_out.size())
+    return false;
+
+  for (const auto& event_type : tts_event_types_in) {
+    if (tts_event_types_out.find(event_type) == tts_event_types_out.end())
+      return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+class TtsUtteranceMojomTest : public testing::Test {
+ public:
+  TtsUtteranceMojomTest()
+      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
+        rvh_test_enabler_(new content::RenderViewHostTestEnabler()) {}
+
+  TtsUtteranceMojomTest(const TtsUtteranceMojomTest&) = delete;
+  TtsUtteranceMojomTest& operator=(const TtsUtteranceMojomTest&) = delete;
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    testing_profile_ = TestingProfile::Builder().Build();
+    web_contents_ = CreateTestWebContents();
+  }
+
+ protected:
+  content::BrowserContext* browser_context() { return testing_profile_.get(); }
+  content::WebContents* GetWebContents() { return web_contents_.get(); }
+
+ private:
+  std::unique_ptr<content::WebContents> CreateTestWebContents() {
+    auto site_instance = content::SiteInstance::Create(browser_context());
+    return content::WebContentsTester::CreateTestWebContents(
+        browser_context(), std::move(site_instance));
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
+  std::unique_ptr<TestingProfile> testing_profile_;
+  std::unique_ptr<content::WebContents> web_contents_;
+};
+
+// Test that every field in content::TtsUtterance in correctly converted
+// with a round trip conversion to and from crosapi::mojom::TtsUtterance.
+TEST_F(TtsUtteranceMojomTest, RoundTripWithoutOptions) {
+  std::unique_ptr<content::TtsUtterance> in_utterance =
+      content::TtsUtterance::Create();
+  in_utterance->SetText(kText);
+  in_utterance->SetVoiceName(kVoiceName);
+  in_utterance->SetLang(kLang);
+  in_utterance->SetSrcId(kSrcId);
+  in_utterance->SetSrcUrl(GURL(kSrcUrl));
+  in_utterance->SetEngineId(kEngineId);
+  in_utterance->SetContinuousParameters(kRate, kPitch, kVolume);
+  in_utterance->SetShouldClearQueue(true);
+
+  std::set<content::TtsEventType> required_event_types;
+  required_event_types.insert(content::TTS_EVENT_START);
+  required_event_types.insert(content::TTS_EVENT_WORD);
+  required_event_types.insert(content::TTS_EVENT_END);
+  in_utterance->SetRequiredEventTypes(required_event_types);
+
+  std::set<content::TtsEventType> desired_event_types;
+  desired_event_types.insert(content::TTS_EVENT_CANCELLED);
+  desired_event_types.insert(content::TTS_EVENT_RESUME);
+  desired_event_types.insert(content::TTS_EVENT_PAUSE);
+  in_utterance->SetDesiredEventTypes(desired_event_types);
+
+  // Round trip conversion to and from mojom utterance.
+  auto mojo_utterance = tts_crosapi_util::ToMojo(in_utterance.get());
+  mojo_utterance->browser_context_id = kBrowerContextId;
+  std::unique_ptr<content::TtsUtterance> out_utterance =
+      tts_crosapi_util::FromMojo(mojo_utterance);
+
+  ASSERT_EQ(out_utterance->GetText(), kText);
+  ASSERT_EQ(out_utterance->GetVoiceName(), kVoiceName);
+  ASSERT_EQ(out_utterance->GetLang(), kLang);
+  ASSERT_EQ(out_utterance->GetSrcId(), kSrcId);
+  ASSERT_EQ(out_utterance->GetSrcUrl(), GURL(kSrcUrl));
+  ASSERT_EQ(out_utterance->GetEngineId(), kEngineId);
+  auto continuouse_params = out_utterance->GetContinuousParameters();
+  ASSERT_EQ(continuouse_params.rate, kRate);
+  ASSERT_EQ(continuouse_params.pitch, kPitch);
+  ASSERT_EQ(continuouse_params.volume, kVolume);
+  ASSERT_TRUE(out_utterance->GetShouldClearQueue());
+
+  ASSERT_TRUE(EventTypesMatches(in_utterance->GetRequiredEventTypes(),
+                                out_utterance->GetRequiredEventTypes()));
+  ASSERT_TRUE(EventTypesMatches(in_utterance->GetDesiredEventTypes(),
+                                out_utterance->GetDesiredEventTypes()));
+
+  ASSERT_TRUE(out_utterance->ShouldAlwaysBeSpoken());
+}
+
+TEST_F(TtsUtteranceMojomTest, RoundTripWithOptions) {
+  std::unique_ptr<content::TtsUtterance> in_utterance =
+      content::TtsUtterance::Create();
+  base::Value::Dict in_options;
+  in_options.Set(tts_extension_api_constants::kVoiceNameKey, kVoiceName);
+  in_options.Set(tts_extension_api_constants::kRateKey, kRate);
+  in_options.Set(tts_extension_api_constants::kEnqueueKey, true);
+  in_utterance->SetOptions(std::move(in_options));
+
+  auto mojo_utterance = tts_crosapi_util::ToMojo(in_utterance.get());
+  std::unique_ptr<content::TtsUtterance> out_utterance =
+      tts_crosapi_util::FromMojo(mojo_utterance);
+
+  auto* out_options = out_utterance->GetOptions();
+
+  const base::Value* voice_value =
+      out_options->Find(tts_extension_api_constants::kVoiceNameKey);
+  ASSERT_TRUE(voice_value);
+  ASSERT_TRUE(voice_value->is_string());
+  ASSERT_EQ(voice_value->GetString(), kVoiceName);
+
+  const base::Value* rate_value =
+      out_options->Find(tts_extension_api_constants::kRateKey);
+  ASSERT_TRUE(rate_value);
+  ASSERT_TRUE(rate_value->is_double());
+  ASSERT_EQ(rate_value->GetDouble(), kRate);
+
+  const base::Value* enqueue_value =
+      out_options->Find(tts_extension_api_constants::kEnqueueKey);
+  ASSERT_TRUE(enqueue_value);
+  ASSERT_TRUE(enqueue_value->is_bool());
+  ASSERT_TRUE(enqueue_value->GetBool());
+}
+
+TEST_F(TtsUtteranceMojomTest, WasCreatedWithNoWebContents) {
+  std::unique_ptr<content::TtsUtterance> in_utterance =
+      content::TtsUtterance::Create();
+  ASSERT_FALSE(in_utterance->GetWebContents());
+  auto mojo_utterance = tts_crosapi_util::ToMojo(in_utterance.get());
+  ASSERT_FALSE(mojo_utterance->was_created_with_web_contents);
+}
+
+TEST_F(TtsUtteranceMojomTest, WasCreatedWithWebContents) {
+  content::WebContents* web_contents = GetWebContents();
+  std::unique_ptr<content::TtsUtterance> in_utterance =
+      content::TtsUtterance::Create(web_contents);
+  auto mojo_utterance = tts_crosapi_util::ToMojo(in_utterance.get());
+  ASSERT_TRUE(mojo_utterance->was_created_with_web_contents);
+
+  // Finish |in_utterance| so that it can be destructed without DCHECK
+  // failure.
+  in_utterance->Finish();
+}
diff --git a/chrome/browser/ssl/ssl_config_service_manager.cc b/chrome/browser/ssl/ssl_config_service_manager.cc
index 8845f3c..f921565 100644
--- a/chrome/browser/ssl/ssl_config_service_manager.cc
+++ b/chrome/browser/ssl/ssl_config_service_manager.cc
@@ -49,10 +49,11 @@
 
 // Converts a ListValue of StringValues into a vector of strings. Any Values
 // which cannot be converted will be skipped.
-std::vector<std::string> ListValueToStringVector(const base::ListValue* value) {
+std::vector<std::string> ValueListToStringVector(
+    const base::Value::List& list) {
   std::vector<std::string> results;
-  results.reserve(value->GetListDeprecated().size());
-  for (const auto& entry : value->GetListDeprecated()) {
+  results.reserve(list.size());
+  for (const auto& entry : list) {
     const std::string* s = entry.GetIfString();
     if (s)
       results.push_back(*s);
@@ -246,9 +247,9 @@
 
 void SSLConfigServiceManager::OnDisabledCipherSuitesChange(
     PrefService* local_state) {
-  const base::ListValue* value = &base::Value::AsListValue(
-      *local_state->GetList(prefs::kCipherSuiteBlacklist));
-  disabled_cipher_suites_ = ParseCipherSuites(ListValueToStringVector(value));
+  const base::Value::List& list =
+      local_state->GetValueList(prefs::kCipherSuiteBlacklist);
+  disabled_cipher_suites_ = ParseCipherSuites(ValueListToStringVector(list));
 }
 
 void SSLConfigServiceManager::CacheVariationsPolicy(PrefService* local_state) {
diff --git a/chrome/browser/storage_access_api/api_browsertest.cc b/chrome/browser/storage_access_api/api_browsertest.cc
index 1a0e540..2ae771b 100644
--- a/chrome/browser/storage_access_api/api_browsertest.cc
+++ b/chrome/browser/storage_access_api/api_browsertest.cc
@@ -60,21 +60,33 @@
       : https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
         permission_grants_unpartitioned_storage_(
             permission_grants_unpartitioned_storage),
-        is_storage_partitioned_(is_storage_partitioned) {
+        is_storage_partitioned_(is_storage_partitioned) {}
+
+  void SetUp() override {
+    features_.InitWithFeaturesAndParameters(GetEnabledFeatures(),
+                                            GetDisabledFeatures());
+    InProcessBrowserTest::SetUp();
+  }
+
+  virtual std::vector<base::test::ScopedFeatureList::FeatureAndParams>
+  GetEnabledFeatures() {
     std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled({
         {net::features::kStorageAccessAPI,
          {{"storage-access-api-grants-unpartitioned-storage",
-           BoolToString(permission_grants_unpartitioned_storage)}}},
+           BoolToString(permission_grants_unpartitioned_storage_)}}},
     });
-    std::vector<base::Feature> disabled;
-
-    if (is_storage_partitioned) {
+    if (is_storage_partitioned_) {
       enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}});
-    } else {
+    }
+    return enabled;
+  }
+
+  virtual std::vector<base::Feature> GetDisabledFeatures() {
+    std::vector<base::Feature> disabled;
+    if (!is_storage_partitioned_) {
       disabled.push_back(net::features::kThirdPartyStoragePartitioning);
     }
-
-    features_.InitWithFeaturesAndParameters(enabled, disabled);
+    return disabled;
   }
 
   void SetUpOnMainThread() override {
@@ -505,6 +517,16 @@
   EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "thirdparty=c");
 }
 
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
+                       RsaForSiteDisabledByDefault) {
+  NavigateToPageWithFrame("a.com");
+  // Ensure that the proposed extension is not available unless explicitly
+  // enabled.
+  EXPECT_TRUE(EvalJs(GetPrimaryMainFrame(),
+                     "\"requestStorageAccessForSite\" in document === false")
+                  .ExtractBool());
+}
+
 INSTANTIATE_TEST_CASE_P(/* no prefix */,
                         StorageAccessAPIBrowserTest,
                         testing::Combine(testing::Bool(), testing::Bool()));
@@ -649,4 +671,96 @@
                                           testing::Bool(),
                                           testing::Bool()));
 
+class StorageAccessAPIForSiteExtensionBrowserTest
+    : public StorageAccessAPIBaseBrowserTest,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  StorageAccessAPIForSiteExtensionBrowserTest()
+      : StorageAccessAPIBaseBrowserTest(std::get<0>(GetParam()),
+                                        std::get<1>(GetParam())) {}
+
+ protected:
+  std::vector<base::test::ScopedFeatureList::FeatureAndParams>
+  GetEnabledFeatures() override {
+    std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled =
+        StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
+    enabled.push_back({blink::features::kStorageAccessAPIForSiteExtension, {}});
+    return enabled;
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIForSiteExtensionBrowserTest,
+                       OnlySameOriginGrantedByDefault) {
+  SetBlockThirdPartyCookies(true);
+  base::HistogramTester histogram_tester;
+
+  NavigateToPageWithFrame("a.com");
+
+  // Asserting very basic behavior while the extension is being implemented.
+  EXPECT_FALSE(storage::test::RequestStorageAccessForSite(
+      GetFrame(), "https://asdf.example"));
+  EXPECT_FALSE(
+      storage::test::RequestStorageAccessForSite(GetFrame(), "mattwashere"));
+  EXPECT_TRUE(storage::test::RequestStorageAccessForSite(GetPrimaryMainFrame(),
+                                                         "https://a.com"));
+  EXPECT_FALSE(
+      storage::test::RequestStorageAccessForSite(GetFrame(), "https://a.com"));
+}
+
+INSTANTIATE_TEST_CASE_P(/* no prefix */,
+                        StorageAccessAPIForSiteExtensionBrowserTest,
+                        testing::Combine(testing::Bool(), testing::Bool()));
+
+// Tests to validate that, when the rsaForSite extension is explicitly disabled,
+// or if the larger Storage Access API is disabled, it does not leak onto the
+// document object.
+class StorageAccessAPIForSiteExtensionExplicitlyDisabledBrowserTest
+    : public StorageAccessAPIBaseBrowserTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  StorageAccessAPIForSiteExtensionExplicitlyDisabledBrowserTest()
+      : StorageAccessAPIBaseBrowserTest(true, true),
+        enable_standard_storage_access_api_(GetParam()) {}
+
+ protected:
+  std::vector<base::Feature> GetDisabledFeatures() override {
+    // The test should validate that either flag alone disables the API.
+    // Note that enabling the extension and not the standard API means both are
+    // disabled.
+    if (enable_standard_storage_access_api_) {
+      return {blink::features::kStorageAccessAPIForSiteExtension};
+    }
+    return {net::features::kStorageAccessAPI};
+  }
+  std::vector<base::test::ScopedFeatureList::FeatureAndParams>
+  GetEnabledFeatures() override {
+    // When the standard API is enabled, return the parent class's enabled
+    // feature list. Otherwise, enable only the extension; this should not take
+    // effect.
+    if (enable_standard_storage_access_api_) {
+      return StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
+    }
+    return {{blink::features::kStorageAccessAPIForSiteExtension, {}}};
+  }
+
+ private:
+  bool enable_standard_storage_access_api_;
+};
+
+IN_PROC_BROWSER_TEST_P(
+    StorageAccessAPIForSiteExtensionExplicitlyDisabledBrowserTest,
+    RsaForSiteNotPresentOnDocumentWhenExplicitlyDisabled) {
+  NavigateToPageWithFrame("a.com");
+  // Ensure that the proposed extension is not available unless explicitly
+  // enabled.
+  EXPECT_TRUE(EvalJs(GetPrimaryMainFrame(),
+                     "\"requestStorageAccessForSite\" in document === false")
+                  .ExtractBool());
+}
+
+INSTANTIATE_TEST_CASE_P(
+    /* no prefix */,
+    StorageAccessAPIForSiteExtensionExplicitlyDisabledBrowserTest,
+    testing::Bool());
+
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/bookmarks_helper.cc b/chrome/browser/sync/test/integration/bookmarks_helper.cc
index ca38c7bf..a0b6f07 100644
--- a/chrome/browser/sync/test/integration/bookmarks_helper.cc
+++ b/chrome/browser/sync/test/integration/bookmarks_helper.cc
@@ -152,7 +152,8 @@
                          size_t new_index) override {}
   void BookmarkNodeAdded(BookmarkModel* model,
                          const BookmarkNode* parent,
-                         size_t index) override {}
+                         size_t index,
+                         bool added_by_user) override {}
   void BookmarkNodeRemoved(BookmarkModel* model,
                            const BookmarkNode* parent,
                            size_t old_index,
@@ -923,7 +924,8 @@
 
 void AnyBookmarkChangeObserver::BookmarkNodeAdded(BookmarkModel* model,
                                                   const BookmarkNode* parent,
-                                                  size_t index) {
+                                                  size_t index,
+                                                  bool added_by_user) {
   cb_.Run();
 }
 
diff --git a/chrome/browser/sync/test/integration/bookmarks_helper.h b/chrome/browser/sync/test/integration/bookmarks_helper.h
index 75bbc78..81a34235 100644
--- a/chrome/browser/sync/test/integration/bookmarks_helper.h
+++ b/chrome/browser/sync/test/integration/bookmarks_helper.h
@@ -321,7 +321,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model,
                              const bookmarks::BookmarkNode* parent,
                              size_t old_index,
diff --git a/chrome/browser/sync/test/integration/single_client_workspace_desk_sync_test.cc b/chrome/browser/sync/test/integration/single_client_workspace_desk_sync_test.cc
index 3f6652d..a3959ce 100644
--- a/chrome/browser/sync/test/integration/single_client_workspace_desk_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_workspace_desk_sync_test.cc
@@ -140,7 +140,7 @@
   // Delete template 1.
   base::RunLoop loop;
   model->DeleteEntry(
-      kTestUuid1_.AsLowercaseString(),
+      kTestUuid1_,
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(DeskModel::DeleteEntryStatus::kOk, status);
         loop.Quit();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 716a69e7..c111d066 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1413,6 +1413,7 @@
       "toolbar/media_router_contextual_menu.h",
       "toolbar/recent_tabs_sub_menu_model.cc",
       "toolbar/recent_tabs_sub_menu_model.h",
+      "toolbar/toolbar_action_hover_card_types.h",
       "toolbar/toolbar_action_view_controller.h",
       "toolbar/toolbar_action_view_delegate.cc",
       "toolbar/toolbar_action_view_delegate.h",
@@ -2913,8 +2914,6 @@
       "webui/settings/chromeos/bluetooth_handler.h",
       "webui/settings/chromeos/bluetooth_section.cc",
       "webui/settings/chromeos/bluetooth_section.h",
-      "webui/settings/chromeos/change_picture_handler.cc",
-      "webui/settings/chromeos/change_picture_handler.h",
       "webui/settings/chromeos/constants/constants_util.cc",
       "webui/settings/chromeos/constants/constants_util.h",
       "webui/settings/chromeos/constants/routes_util.cc",
@@ -5054,6 +5053,10 @@
       "views/toolbar/side_panel_toolbar_button.h",
       "views/toolbar/toolbar_account_icon_container_view.cc",
       "views/toolbar/toolbar_account_icon_container_view.h",
+      "views/toolbar/toolbar_action_hover_card_bubble_view.cc",
+      "views/toolbar/toolbar_action_hover_card_bubble_view.h",
+      "views/toolbar/toolbar_action_hover_card_controller.cc",
+      "views/toolbar/toolbar_action_hover_card_controller.h",
       "views/toolbar/toolbar_action_view.cc",
       "views/toolbar/toolbar_action_view.h",
       "views/toolbar/toolbar_action_view_delegate_views.h",
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
index b42c2c36..e991c9be 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java
@@ -42,6 +42,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ui.appmenu.internal.R;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.chips.ChipView;
@@ -299,7 +300,8 @@
                 popupHeight, anchorView.getRootView().getLayoutDirection());
         mPopup.setContentView(contentView);
 
-        if (popupHeight + popupPosition[1] > visibleDisplayFrame.bottom) {
+        if (!ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled()
+                && popupHeight + popupPosition[1] > visibleDisplayFrame.bottom) {
             mPopup.setHeight(visibleDisplayFrame.height());
         }
 
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
index 965d17e..0fb91a7 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuHandlerImpl.java
@@ -20,6 +20,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver;
 import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
@@ -199,7 +200,14 @@
         registerViewBinders(customViewBinders, customViewTypeOffsetMap, adapter,
                 mDelegate.shouldShowIconBeforeItem());
 
-        Rect appRect = mAppRect.get();
+        Rect appRect;
+        if (ChromeFeatureList.sCctResizableWindowAboveNavbar.isEnabled()) {
+            // Get the height and width of the display.
+            appRect = new Rect();
+            mDecorView.getWindowVisibleDisplayFrame(appRect);
+        } else {
+            appRect = mAppRect.get();
+        }
 
         // Use full size of window for abnormal appRect.
         if (appRect.left < 0 && appRect.top < 0) {
@@ -208,7 +216,6 @@
             appRect.right = mDecorView.getWidth();
             appRect.bottom = mDecorView.getHeight();
         }
-
         Point pt = new Point();
         display.getSize(pt);
 
diff --git a/chrome/browser/ui/ash/arc_open_url_delegate_impl.cc b/chrome/browser/ui/ash/arc_open_url_delegate_impl.cc
index bd07d94..72a4237 100644
--- a/chrome/browser/ui/ash/arc_open_url_delegate_impl.cc
+++ b/chrome/browser/ui/ash/arc_open_url_delegate_impl.cc
@@ -78,8 +78,6 @@
      chromeos::settings::mojom::kBluetoothDevicesSubpagePath},
     {ChromePage::BLUETOOTHDEVICES,
      chromeos::settings::mojom::kBluetoothDevicesSubpagePath},
-    {ChromePage::CHANGEPICTURE,
-     chromeos::settings::mojom::kChangePictureSubpagePath},
     {ChromePage::CUPSPRINTERS,
      chromeos::settings::mojom::kPrintingDetailsSubpagePath},
     {ChromePage::DATETIME, chromeos::settings::mojom::kDateAndTimeSectionPath},
diff --git a/chrome/browser/ui/ash/arc_open_url_delegate_impl_browsertest.cc b/chrome/browser/ui/ash/arc_open_url_delegate_impl_browsertest.cc
index ad31eceb..d40e563 100644
--- a/chrome/browser/ui/ash/arc_open_url_delegate_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/arc_open_url_delegate_impl_browsertest.cc
@@ -306,9 +306,6 @@
       base_url.Resolve(
           chromeos::settings::mojom::kBluetoothDevicesSubpagePath));
   TestOpenOSSettingsChromePage(
-      ChromePage::CHANGEPICTURE,
-      base_url.Resolve(chromeos::settings::mojom::kChangePictureSubpagePath));
-  TestOpenOSSettingsChromePage(
       ChromePage::CUPSPRINTERS,
       base_url.Resolve(chromeos::settings::mojom::kPrintingDetailsSubpagePath));
   TestOpenOSSettingsChromePage(
diff --git a/chrome/browser/ui/ash/desks/desks_client.cc b/chrome/browser/ui/ash/desks/desks_client.cc
index 021c382b..f773520 100644
--- a/chrome/browser/ui/ash/desks/desks_client.cc
+++ b/chrome/browser/ui/ash/desks/desks_client.cc
@@ -258,7 +258,7 @@
                                     template_name, std::move(callback)));
 }
 
-void DesksClient::DeleteDeskTemplate(const std::string& template_uuid,
+void DesksClient::DeleteDeskTemplate(const base::GUID& template_uuid,
                                      DeleteDeskTemplateCallback callback) {
   if (!active_profile_) {
     std::move(callback).Run(std::string(kNoCurrentUserError));
diff --git a/chrome/browser/ui/ash/desks/desks_client.h b/chrome/browser/ui/ash/desks/desks_client.h
index 818238e..765c336f 100644
--- a/chrome/browser/ui/ash/desks/desks_client.h
+++ b/chrome/browser/ui/ash/desks/desks_client.h
@@ -88,7 +88,7 @@
   // to be removed,|callback| will be invoked with an empty error string.
   // TODO(crbug.com/1286515): This will be removed with the extension. Avoid
   // further uses of this method.
-  void DeleteDeskTemplate(const std::string& template_uuid,
+  void DeleteDeskTemplate(const base::GUID& template_uuid,
                           DeleteDeskTemplateCallback callback);
 
   using GetDeskTemplatesCallback =
diff --git a/chrome/browser/ui/ash/desks/desks_client_browsertest.cc b/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
index f10c730..b8eae77b 100644
--- a/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
+++ b/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
@@ -229,9 +229,8 @@
 void DeleteDeskTemplate(const base::GUID uuid) {
   base::RunLoop run_loop;
   DesksClient::Get()->DeleteDeskTemplate(
-      uuid.AsLowercaseString(),
-      base::BindLambdaForTesting(
-          [&](std::string error_string) { run_loop.Quit(); }));
+      uuid, base::BindLambdaForTesting(
+                [&](std::string error_string) { run_loop.Quit(); }));
   run_loop.Run();
 }
 
diff --git a/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc b/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
index 49ef60f..f8a534b 100644
--- a/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/exo_app_type_resolver.cc
@@ -90,6 +90,9 @@
       reinterpret_cast<exo::ProtectedNativePixmapQueryDelegate*>(
           &protected_native_pixmap_query_client_));
 
+  out_properties_container.SetProperty(
+      chromeos::kShouldHaveHighlightBorderOverlay, true);
+
   if (task_id.has_value())
     out_properties_container.SetProperty(app_restore::kWindowIdKey, *task_id);
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index 0424a9d..8be3f75 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -451,9 +451,9 @@
 
   if (selected_line_) {
     const Suggestion& suggestion = suggestions_[*selected_line_];
-    delegate_->DidSelectSuggestion(suggestion.main_text.value,
-                                   suggestion.frontend_id,
-                                   suggestion.GetPayload<std::string>());
+    delegate_->DidSelectSuggestion(
+        suggestion.main_text.value, suggestion.frontend_id,
+        suggestion.GetPayload<Suggestion::BackendId>());
   } else {
     delegate_->ClearPreviewedForm();
   }
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
index d8a36c6..ba110c6b 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
@@ -111,7 +111,7 @@
 
   void DidSelectSuggestion(const std::u16string& value,
                            int frontend_id,
-                           const std::string& backend_id) override {}
+                           const Suggestion::BackendId& backend_id) override {}
   bool RemoveSuggestion(const std::u16string& value, int frontend_id) override {
     return true;
   }
@@ -188,7 +188,7 @@
   ~MockAxTreeManager() = default;
 
   MOCK_CONST_METHOD2(GetNodeFromTree,
-                     ui::AXNode*(const ui::AXTreeID tree_id,
+                     ui::AXNode*(const ui::AXTreeID& tree_id,
                                  const int32_t node_id));
   MOCK_CONST_METHOD2(GetDelegate,
                      ui::AXPlatformNodeDelegate*(const ui::AXTreeID tree_id,
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index 21d4a9a5..67ada726 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -894,6 +894,12 @@
                   ->IsContentBlocked(ContentSettingsType::POPUPS));
 }
 
+IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
+                       DocumentPictureInPictureIsNotConsideredForBlocking) {
+  EXPECT_FALSE(blocked_content::ConsiderForPopupBlocking(
+      WindowOpenDisposition::NEW_PICTURE_IN_PICTURE));
+}
+
 class PopupBlockerFencedFrameTest : public PopupBlockerBrowserTest {
  public:
   PopupBlockerFencedFrameTest() = default;
diff --git a/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc b/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
index 22478d6..511dff85 100644
--- a/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_tab_helper.cc
@@ -123,7 +123,8 @@
 
 void BookmarkTabHelper::BookmarkNodeAdded(BookmarkModel* model,
                                           const BookmarkNode* parent,
-                                          size_t index) {
+                                          size_t index,
+                                          bool added_by_user) {
   UpdateStarredStateForCurrentURL();
 }
 
diff --git a/chrome/browser/ui/bookmarks/bookmark_tab_helper.h b/chrome/browser/ui/bookmarks/bookmark_tab_helper.h
index 60e76b1b..f1ff121 100644
--- a/chrome/browser/ui/bookmarks/bookmark_tab_helper.h
+++ b/chrome/browser/ui/bookmarks/bookmark_tab_helper.h
@@ -76,7 +76,8 @@
                            bool ids_reassigned) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc
index 55773ca..4af66b30 100644
--- a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc
+++ b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc
@@ -147,7 +147,8 @@
 void RecentlyUsedFoldersComboModel::BookmarkNodeAdded(
     BookmarkModel* model,
     const BookmarkNode* parent,
-    size_t index) {}
+    size_t index,
+    bool added_by_user) {}
 
 void RecentlyUsedFoldersComboModel::OnWillRemoveBookmarks(
     BookmarkModel* model,
diff --git a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
index 5eb3a1a..69047af 100644
--- a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
+++ b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
@@ -50,7 +50,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model,
                              const bookmarks::BookmarkNode* parent,
                              size_t old_index,
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
index dc922914..b1695e58 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
@@ -589,20 +589,28 @@
 
 // Test that the colored frames have the correct color when active and inactive.
 // Disabled; https://crbug.com/1322741.
-IN_PROC_BROWSER_TEST_F(NativeAppWindowCocoaBrowserTest, DISABLED_FrameColor) {
+IN_PROC_BROWSER_TEST_F(NativeAppWindowCocoaBrowserTest, FrameColor) {
+  EXPECT_EQ(NSApp.activationPolicy, NSApplicationActivationPolicyAccessory);
+
   // The hex values indicate an RGB color. When we get the NSColor later, the
   // components are CGFloats in the range [0, 1].
   extensions::AppWindow* app_window = CreateTestAppWindow(
       "{\"frame\": {\"color\": \"#FF0000\", \"inactiveColor\": \"#0000FF\"}}");
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
+
   // No color correction in the default case.
   [ns_window setColorSpace:[NSColorSpace sRGBColorSpace]];
 
-  int half_width = NSWidth([ns_window frame]) / 2;
+  // Make sure the window is inactive before color sampling.
+  ui::test::ScopedFakeNSWindowFocus fake_focus;
+  [ns_window resignMainWindow];
+  [ns_window resignKeyWindow];
 
   NSBitmapImageRep* bitmap = ScreenshotNSWindow(ns_window);
-  // The window is currently inactive so it should be blue (#0000FF).
+  // The window is currently inactive so it should be blue (#0000FF). We are
+  // assuming the Light appearance is being used.
   NSColor* expected_color = ColorInBitmapColorSpace(0xFF0000FF, bitmap);
+  int half_width = NSWidth([ns_window frame]) / 2;
   NSColor* color = [bitmap colorAtX:half_width y:5];
   CGFloat expected_components[4], color_components[4];
   [expected_color getComponents:expected_components];
@@ -611,11 +619,12 @@
   EXPECT_NEAR(expected_components[1], color_components[1], 0.01);
   EXPECT_NEAR(expected_components[2], color_components[2], 0.01);
 
-  ui::test::ScopedFakeNSWindowFocus fake_focus;
+  // Activate the window.
   [ns_window makeMainWindow];
 
   bitmap = ScreenshotNSWindow(ns_window);
-  // The window is now active so it should be red (#FF0000).
+  // The window is now active so it should be red (#FF0000). Again, this is
+  // assuming the Light appearance is being used.
   expected_color = ColorInBitmapColorSpace(0xFFFF0000, bitmap);
   color = [bitmap colorAtX:half_width y:5];
   [expected_color getComponents:expected_components];
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h
index bc5047b..f3306d3 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h
@@ -60,7 +60,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
index eed94ff..85b9b1cb 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
@@ -159,7 +159,8 @@
 
 void BookmarkMenuBridge::BookmarkNodeAdded(BookmarkModel* model,
                                            const BookmarkNode* parent,
-                                           size_t index) {
+                                           size_t index,
+                                           bool added_by_user) {
   InvalidateMenu();
 }
 
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index 1c64aa44..31ccadd 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -241,7 +241,9 @@
       kColorToolbarButtonIcon, kColorDownloadShelfBackground, 0x3A);
   mixer[kColorDownloadShelfForeground] = {kColorToolbarText};
   mixer[kColorDownloadStartedAnimationForeground] = {ui::kColorAccent};
-  mixer[kColorDownloadToolbarButtonActive] = {ui::kColorThrobber};
+  mixer[kColorDownloadToolbarButtonActive] =
+      ui::PickGoogleColor(ui::kColorThrobber, kColorToolbar,
+                          color_utils::kMinimumVisibleContrastRatio);
   mixer[kColorDownloadToolbarButtonInactive] = {kColorToolbarButtonIcon};
   mixer[kColorDownloadToolbarButtonRingBackground] = {
       SkColorSetA(kColorDownloadToolbarButtonInactive, 0x33)};
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 39aaa20..f531aa84 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -319,6 +319,15 @@
   view_delegate_->UpdateState();
 }
 
+void ExtensionActionViewController::UpdateHoverCard(
+    ToolbarActionView* action_view,
+    ToolbarActionHoverCardUpdateType update_type) {
+  if (!ExtensionIsValid())
+    return;
+
+  extensions_container_->UpdateToolbarActionHoverCard(action_view, update_type);
+}
+
 void ExtensionActionViewController::RegisterCommand() {
   if (!ExtensionIsValid())
     return;
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index 00aec3db..6b2f040 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -11,6 +11,7 @@
 #include "chrome/browser/extensions/extension_action_icon_factory.h"
 #include "chrome/browser/extensions/extension_context_menu_model.h"
 #include "chrome/browser/extensions/site_permissions_helper.h"
+#include "chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_host_observer.h"
@@ -85,6 +86,8 @@
   void ExecuteUserAction(InvocationSource source) override;
   void TriggerPopupForAPI(ShowPopupCallback callback) override;
   void UpdateState() override;
+  void UpdateHoverCard(ToolbarActionView* action_view,
+                       ToolbarActionHoverCardUpdateType update_type) override;
   void RegisterCommand() override;
   void UnregisterCommand() override;
 
diff --git a/chrome/browser/ui/extensions/extensions_container.h b/chrome/browser/ui/extensions/extensions_container.h
index bc1c909..b6e606d 100644
--- a/chrome/browser/ui/extensions/extensions_container.h
+++ b/chrome/browser/ui/extensions/extensions_container.h
@@ -10,9 +10,11 @@
 #include "base/callback_forward.h"
 #include "chrome/browser/extensions/extension_context_menu_model.h"
 #include "chrome/browser/ui/extensions/extension_popup_types.h"
+#include "chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h"
 
 class ToolbarActionViewController;
 class ToolbarActionsBarBubbleDelegate;
+class ToolbarActionView;
 
 // An interface for containers in the toolbar that host extensions.
 class ExtensionsContainer {
@@ -75,6 +77,11 @@
 
   // Whether there are any Extensions registered with the ExtensionsContainer.
   virtual bool HasAnyExtensions() const = 0;
+
+  // Updates the hover card for `action_view` based on `update_type`.
+  virtual void UpdateToolbarActionHoverCard(
+      ToolbarActionView* action_view,
+      ToolbarActionHoverCardUpdateType update_type) = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_CONTAINER_H_
diff --git a/chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h b/chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h
new file mode 100644
index 0000000..deaf217
--- /dev/null
+++ b/chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h
@@ -0,0 +1,16 @@
+// 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_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_TYPES_H_
+#define CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_TYPES_H_
+
+// TODO(crbug.com/1351778): Mergue with `TabSlotController::HoverCardUpdateType`
+// once the base hover card controller class once it's implemented.
+enum class ToolbarActionHoverCardUpdateType {
+  kHover,
+  kToolbarActionRemoved,
+  kEvent
+};
+
+#endif  // CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_TYPES_H_
diff --git a/chrome/browser/ui/toolbar/toolbar_action_view_controller.h b/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
index 2391124..4865821 100644
--- a/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
+++ b/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
@@ -10,6 +10,7 @@
 #include "chrome/browser/extensions/extension_context_menu_model.h"
 #include "chrome/browser/extensions/site_permissions_helper.h"
 #include "chrome/browser/ui/extensions/extension_popup_types.h"
+#include "chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h"
 #include "ui/gfx/image/image.h"
 
 namespace content {
@@ -25,6 +26,7 @@
 }
 
 class ToolbarActionViewDelegate;
+class ToolbarActionView;
 
 // The basic controller class for an action that is shown on the toolbar -
 // an extension action (like browser actions) or a component action (like
@@ -128,6 +130,10 @@
   // Updates the current state of the action.
   virtual void UpdateState() = 0;
 
+  // Updates the hover card for `action_view` based on `update_type`.
+  virtual void UpdateHoverCard(ToolbarActionView* action_view,
+                               ToolbarActionHoverCardUpdateType update_type) {}
+
   // Registers an accelerator. Called when the view is added to a widget.
   virtual void RegisterCommand() {}
 
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 4f453aaa..18763ff5 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -216,9 +216,6 @@
 const base::FeatureParam<bool> kTabSearchSearchIgnoreLocation{
     &kTabSearchFuzzySearch, "TabSearchSearchIgnoreLocation", false};
 
-const base::Feature kTabSearchMediaTabs{"TabSearchMediaTabs",
-                                        base::FEATURE_ENABLED_BY_DEFAULT};
-
 // If this feature parameter is enabled, show media tabs in both "Audio & Video"
 // section and "Open Tabs" section.
 const char kTabSearchAlsoShowMediaTabsinOpenTabsSectionParameterName[] =
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 8dfea86..3d7ec60 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -133,8 +133,6 @@
 // This means that it will not matter where in the string the pattern occurs.
 extern const base::FeatureParam<bool> kTabSearchSearchIgnoreLocation;
 
-extern const base::Feature kTabSearchMediaTabs;
-
 extern const char kTabSearchAlsoShowMediaTabsinOpenTabsSectionParameterName[];
 
 // Determines how close the match must be to the beginning of the string. Eg a
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 11eac50..a7a12bb 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -1189,7 +1189,8 @@
 
 void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
                                         const BookmarkNode* parent,
-                                        size_t index) {
+                                        size_t index,
+                                        bool added_by_user) {
   // See comment in BookmarkNodeMoved() for details on this.
   InvalidateDrop();
   if (BookmarkNodeAddedImpl(model, parent, index))
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h
index cf0cc603..de4eb76 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.h
@@ -201,7 +201,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t old_index,
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index 1adadfc..d2624268 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -215,7 +215,8 @@
 
 void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model,
                                            const BookmarkNode* parent,
-                                           size_t index) {
+                                           size_t index,
+                                           bool added_by_user) {
   Reset();
 }
 
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
index 249acff..8525c335 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
@@ -128,7 +128,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
                            const bookmarks::BookmarkNode* parent,
                            size_t index,
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 bc057699..44e1bd1 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
@@ -287,6 +287,11 @@
   controller_->OnButtonPressed();
 }
 
+void DownloadToolbarButtonView::OnThemeChanged() {
+  ToolbarButton::OnThemeChanged();
+  UpdateIcon();
+}
+
 std::unique_ptr<views::View> DownloadToolbarButtonView::CreateRowListView(
     std::vector<DownloadUIModel::DownloadUIModelPtr> model_list) {
   // Do not create empty partial view.
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
index 69bdbb4..86591e5 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
@@ -57,6 +57,7 @@
 
   // ToolbarButton:
   void UpdateIcon() override;
+  void OnThemeChanged() override;
 
   // DownloadBubbleNavigationHandler:
   void OpenPrimaryDialog() override;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 1e0f606..0b9580d3 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -22,9 +22,9 @@
 #include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
 #include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_tabbed_menu_coordinator.h"
-#include "chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
 #include "extensions/common/extension_features.h"
@@ -106,10 +106,17 @@
                         extensions_tabbed_menu_coordinator_.get()),
                     std::make_unique<ExtensionsRequestAccessButton>(browser_))
               : nullptr),
-      display_mode_(display_mode) {
+      display_mode_(display_mode),
+      action_hover_card_controller_(
+          std::make_unique<ToolbarActionHoverCardController>(this)) {
   // The container shouldn't show unless / until we have extensions available.
   SetVisible(false);
 
+  // So we only get enter/exit messages when the mouse enters/exits the whole
+  // container, even if it is entering/exiting a specific toolbar action view,
+  // too.
+  SetNotifyEnterExitOnChild(true);
+
   model_observation_.Observe(model_.get());
   permissions_manager_observation_.Observe(
       extensions::PermissionsManager::Get(browser_->profile()));
@@ -158,6 +165,10 @@
 }
 
 ExtensionsToolbarContainer::~ExtensionsToolbarContainer() {
+  // Eliminate the hover card first to avoid order-of-operation issues (e.g.
+  // avoid events during teardown).
+  action_hover_card_controller_.reset();
+
   // The child views hold pointers to the |actions_|, and thus need to be
   // destroyed before them.
   RemoveAllChildViews();
@@ -887,5 +898,28 @@
   extensions_controls_->UpdateControls(actions_, site_setting, web_contents);
 }
 
+void ExtensionsToolbarContainer::UpdateToolbarActionHoverCard(
+    ToolbarActionView* action_view,
+    ToolbarActionHoverCardUpdateType update_type) {
+  action_hover_card_controller_->UpdateHoverCard(action_view, update_type);
+}
+
+void ExtensionsToolbarContainer::OnMouseExited(const ui::MouseEvent& event) {
+  UpdateToolbarActionHoverCard(nullptr,
+                               ToolbarActionHoverCardUpdateType::kHover);
+}
+
+void ExtensionsToolbarContainer::OnMouseMoved(const ui::MouseEvent& event) {
+  // Since we set the container's "notify enter exit on child" to true, we can
+  // get notified when the mouse enters a child view only if it originates from
+  // outside the container. This means that we a) can know when the mouse enters
+  // a toolbar action view (which is handled in such class) and b) cannot
+  // know when the mouse leaves a toolbar action view and enters a toolbar
+  // control. Therefore, listening for on mouse moved in the container reflects
+  // moving the mouse from toolbar action view to toolbar controls.
+  UpdateToolbarActionHoverCard(nullptr,
+                               ToolbarActionHoverCardUpdateType::kHover);
+}
+
 BEGIN_METADATA(ExtensionsToolbarContainer, ToolbarIconContainerView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 810487c..8adfac90 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -132,6 +132,8 @@
   void OnDragExited() override;
   views::View::DropCallback GetDropCallback(
       const ui::DropTargetEvent& event) override;
+  void OnMouseExited(const ui::MouseEvent& event) override;
+  void OnMouseMoved(const ui::MouseEvent& event) override;
 
   // ExtensionsContainer:
   ToolbarActionViewController* GetActionForId(
@@ -155,6 +157,9 @@
       std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
   void ToggleExtensionsMenu() override;
   bool HasAnyExtensions() const override;
+  void UpdateToolbarActionHoverCard(
+      ToolbarActionView* action_view,
+      ToolbarActionHoverCardUpdateType update_type) override;
 
   // ToolbarActionView::Delegate:
   content::WebContents* GetCurrentWebContents() override;
@@ -170,6 +175,8 @@
                            const gfx::Point& p) override;
 
  private:
+  friend class ToolbarActionHoverCardBubbleViewUITest;
+
   // A struct representing the position and action being dragged.
   struct DropInfo;
 
@@ -299,6 +306,10 @@
   const raw_ptr<ExtensionsToolbarControls> extensions_controls_;
   DisplayMode display_mode_;
 
+  // Controller for showing the toolbar action hover card.
+  std::unique_ptr<ToolbarActionHoverCardController>
+      action_hover_card_controller_;
+
   // TODO(pbos): Create actions and icons only for pinned pinned / popped out
   // actions (lazily). Currently code expects GetActionForId() to return
   // actions for extensions that aren't visible.
diff --git a/chrome/browser/ui/views/frame/browser_frame_ash.cc b/chrome/browser/ui/views/frame/browser_frame_ash.cc
index 2a4540c..9a19240 100644
--- a/chrome/browser/ui/views/frame/browser_frame_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_ash.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/base/window_state_type.h"
 #include "components/app_restore/app_restore_info.h"
 #include "components/app_restore/app_restore_utils.h"
@@ -191,6 +192,8 @@
 
   params.init_properties_container.SetProperty(app_restore::kBrowserAppNameKey,
                                                browser->app_name());
+  params.init_properties_container.SetProperty(
+      chromeos::kShouldHaveHighlightBorderOverlay, true);
 
   // This is only needed for ash. For lacros, Exo tags the associated
   // ShellSurface as being of AppType::LACROS.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
index 74d748a..a75d64e 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
@@ -812,6 +812,12 @@
   if (!chromeos::features::IsDarkLightModeEnabled())
     return;
 
+  if (highlight_border_overlay_ ||
+      !GetWidget()->GetNativeWindow()->GetProperty(
+          chromeos::kShouldHaveHighlightBorderOverlay)) {
+    return;
+  }
+
   highlight_border_overlay_ =
       std::make_unique<HighlightBorderOverlay>(GetWidget());
 }
diff --git a/chrome/browser/ui/views/frame/desktop_browser_frame_lacros.cc b/chrome/browser/ui/views/frame/desktop_browser_frame_lacros.cc
index 10d0f53..c2597f9 100644
--- a/chrome/browser/ui/views/frame/desktop_browser_frame_lacros.cc
+++ b/chrome/browser/ui/views/frame/desktop_browser_frame_lacros.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/native_browser_frame_factory.h"
+#include "chromeos/ui/base/window_properties.h"
 
 DesktopBrowserFrameLacros::DesktopBrowserFrameLacros(
     BrowserFrame* browser_frame,
@@ -24,6 +25,8 @@
   Browser* browser = browser_view()->browser();
   params.restore_session_id = browser->session_id().id();
   params.restore_window_id = browser->create_params().restore_id;
+  params.init_properties_container.SetProperty(
+      chromeos::kShouldHaveHighlightBorderOverlay, true);
   return params;
 }
 
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc b/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
index 6b39dac..d0223e6 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/lens/lens_side_panel_coordinator_browsertest.cc
@@ -170,8 +170,15 @@
   base::UserActionTester user_action_tester;
 };
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel \
+  DISABLED_ImageSearchWithValidImageOpensUnifiedSidePanel
+#else
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel \
+  ImageSearchWithValidImageOpensUnifiedSidePanel
+#endif
 IN_PROC_BROWSER_TEST_F(SearchImageWithUnifiedSidePanel,
-                       ImageSearchWithValidImageOpensUnifiedSidePanel) {
+                       MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel) {
   SetupUnifiedSidePanel();
   EXPECT_TRUE(GetRightAlignedSidePanel()->GetVisible());
 
@@ -256,8 +263,15 @@
   }
 };
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel \
+  DISABLED_ImageSearchWithValidImageOpensUnifiedSidePanel
+#else
+#define MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel \
+  ImageSearchWithValidImageOpensUnifiedSidePanel
+#endif
 IN_PROC_BROWSER_TEST_F(SearchImageWithUnifiedSidePanelFooterDisabled,
-                       ImageSearchWithValidImageOpensUnifiedSidePanel) {
+                       MAYBE_ImageSearchWithValidImageOpensUnifiedSidePanel) {
   SetupUnifiedSidePanel();
   EXPECT_TRUE(GetRightAlignedSidePanel()->GetVisible());
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index d987aa8..b7d49bc 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -476,7 +476,7 @@
 
   header_combobox_ = header->AddChildView(CreateCombobox());
 
-  header->AddChildView(CreateControlButton(
+  auto* header_close_button = header->AddChildView(CreateControlButton(
       header.get(),
       base::BindRepeating(&SidePanelCoordinator::Close, base::Unretained(this)),
       views::kIcCloseIcon, gfx::Insets(),
@@ -485,6 +485,9 @@
       ChromeLayoutProvider::Get()->GetDistanceMetric(
           ChromeDistanceMetric::DISTANCE_SIDE_PANEL_HEADER_VECTOR_ICON_SIZE)));
 
+  header_combobox_->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+  header_close_button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+
   return header;
 }
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_resize_area.cc b/chrome/browser/ui/views/side_panel/side_panel_resize_area.cc
index c409c74..fcd8b0e 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_resize_area.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_resize_area.cc
@@ -24,7 +24,7 @@
   const gfx::Size preferred_resize_handle_size = gfx::Size(16, 24);
   SetPreferredSize(preferred_resize_handle_size);
   SetCanProcessEventsWithinSubtree(false);
-  SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
+  SetFocusBehavior(FocusBehavior::ALWAYS);
   FocusRing::Install(this);
 }
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
index 240161f43..0d6fad81 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
@@ -38,19 +38,6 @@
   static const base::NoDestructor<std::vector<LabInfo>> lab_info_([]() {
     std::vector<LabInfo> lab_info;
 
-    // Tab Search Media Tabs
-    std::vector<std::u16string> tab_search_media_tabs_variation_description = {
-        l10n_util::GetStringUTF16(
-            IDS_MEDIA_TABS_ALSO_SHOWN_IN_OPEN_TABS_SECTION)};
-
-    lab_info.emplace_back(LabInfo(
-        flag_descriptions::kTabSearchMediaTabsId,
-        l10n_util::GetStringUTF16(IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_NAME),
-        l10n_util::GetStringUTF16(
-            IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_DESCRIPTION),
-        "chrome-labs-tab-search-media-tabs", version_info::Channel::BETA,
-        tab_search_media_tabs_variation_description));
-
     // Tab Scrolling.
     std::vector<std::u16string> tab_scrolling_variation_descriptions = {
         l10n_util::GetStringUTF16(IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH),
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc b/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
index 876d77ec..bd476bd 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
@@ -52,8 +52,8 @@
   // kSidePanelSelected = 4,
   // kLensRegionSearchSelected = 5,
   kWebUITabStripSelected = 6,
-  kTabSearchMediaTabsSelected = 7,
-  kMaxValue = kTabSearchMediaTabsSelected,
+  // kTabSearchMediaTabsSelected = 7,
+  kMaxValue = kWebUITabStripSelected,
 };
 
 void EmitToHistogram(const std::u16string& selected_lab_state,
@@ -82,8 +82,6 @@
     if (internal_name == flag_descriptions::kWebUITabStripFlagId)
       return ChromeLabsSelectedLab::kWebUITabStripSelected;
 #endif
-    if (internal_name == flag_descriptions::kTabSearchMediaTabsId)
-      return ChromeLabsSelectedLab::kTabSearchMediaTabsSelected;
 
     return ChromeLabsSelectedLab::kUnspecifiedSelected;
   };
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc
new file mode 100644
index 0000000..18548fb
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc
@@ -0,0 +1,265 @@
+// Copyright (c) 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/toolbar/toolbar_action_hover_card_bubble_view.h"
+
+#include "base/feature_list.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/chrome_typography.h"
+#include "extensions/common/extension_features.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/layout/flex_layout.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "ui/base/win/shell.h"
+#endif
+
+namespace {
+
+// Maximum number of lines that labels can occupy.
+constexpr int kHoverCardTitleMaxLines = 2;
+
+// Hover card margins.
+// TODO(crbug.com/1351778): Move to a base hover card class.
+constexpr int kHorizontalMargin = 18;
+constexpr int kVerticalMargin = 10;
+constexpr auto kTitleMargins =
+    gfx::Insets::VH(kVerticalMargin, kHorizontalMargin);
+
+bool CustomShadowsSupported() {
+#if BUILDFLAG(IS_WIN)
+  return ui::win::IsAeroGlassEnabled();
+#else
+  return true;
+#endif
+}
+
+// Label that renders its background in a solid color. Placed in front of a
+// normal label either by being later in the draw order or on a layer, it can
+// be used to animate a fade-out.
+class SolidLabel : public views::Label {
+ public:
+  METADATA_HEADER(SolidLabel);
+  using Label::Label;
+  SolidLabel() = default;
+  ~SolidLabel() override = default;
+
+ protected:
+  // views::Label:
+  void OnPaintBackground(gfx::Canvas* canvas) override {
+    canvas->DrawColor(GetBackgroundColor());
+  }
+};
+
+BEGIN_METADATA(SolidLabel, views::Label)
+END_METADATA
+
+// Label that exposes the CreateRenderText() method, so that we can use
+// ToolbarActionHoverCardView::FilenameElider to do a two-line elision of
+// filenames.
+class RenderTextFactoryLabel : public views::Label {
+ public:
+  using Label::CreateRenderText;
+  using Label::Label;
+};
+
+}  // namespace
+
+// ToolbarActionHoverCardBubbleView::FadeLabel:
+// ----------------------------------------------------------
+
+// This view overlays and fades out an old version of the text of a label,
+// while displaying the new text underneath. It is used to fade out the old
+// value of the title and domain labels on the hover card when the tab switches
+// or the tab title changes.
+// TODO(crbug.com/1354321): ToolbarActionHoverCarBubbleView has the same
+// FadeLabel. Move it to its own shared file.
+class ToolbarActionHoverCardBubbleView::FadeLabel : public views::View {
+ public:
+  FadeLabel(int context, int num_lines) {
+    primary_label_ = AddChildView(std::make_unique<RenderTextFactoryLabel>(
+        std::u16string(), context, views::style::STYLE_PRIMARY));
+    primary_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    primary_label_->SetVerticalAlignment(gfx::ALIGN_TOP);
+    primary_label_->SetMultiLine(num_lines > 1);
+    if (num_lines > 1)
+      primary_label_->SetMaxLines(num_lines);
+
+    label_fading_out_ = AddChildView(std::make_unique<SolidLabel>(
+        std::u16string(), context, views::style::STYLE_PRIMARY));
+    label_fading_out_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    label_fading_out_->SetVerticalAlignment(gfx::ALIGN_TOP);
+    label_fading_out_->SetMultiLine(num_lines > 1);
+    if (num_lines > 1)
+      label_fading_out_->SetMaxLines(num_lines);
+    label_fading_out_->GetViewAccessibility().OverrideIsIgnored(true);
+
+    SetLayoutManager(std::make_unique<views::FillLayout>());
+  }
+
+  ~FadeLabel() override = default;
+
+  void SetText(std::u16string text, absl::optional<bool> is_filename) {
+    if (was_filename_.has_value())
+      SetMultilineParams(label_fading_out_, was_filename_.value());
+    label_fading_out_->SetText(primary_label_->GetText());
+    if (is_filename.has_value())
+      SetMultilineParams(primary_label_, is_filename.value());
+    was_filename_ = is_filename;
+    primary_label_->SetText(text);
+  }
+
+  // Sets the fade-out of the label as |percent| in the range [0, 1]. Since
+  // FadeLabel is designed to mask new text with the old and then fade away, the
+  // higher the percentage the less opaque the label.
+  void SetFade(double percent) {
+    percent_ = std::min(1.0, percent);
+    if (percent_ == 1.0)
+      label_fading_out_->SetText(std::u16string());
+    const SkAlpha alpha = base::saturated_cast<SkAlpha>(
+        std::numeric_limits<SkAlpha>::max() * (1.0 - percent_));
+    label_fading_out_->SetBackgroundColor(
+        SkColorSetA(label_fading_out_->GetBackgroundColor(), alpha));
+    label_fading_out_->SetEnabledColor(
+        SkColorSetA(label_fading_out_->GetEnabledColor(), alpha));
+  }
+
+  std::u16string GetText() const { return primary_label_->GetText(); }
+
+ protected:
+  // views::View:
+  gfx::Size GetMaximumSize() const override {
+    return gfx::Tween::SizeValueBetween(percent_,
+                                        label_fading_out_->GetPreferredSize(),
+                                        primary_label_->GetPreferredSize());
+  }
+
+  gfx::Size CalculatePreferredSize() const override {
+    return primary_label_->GetPreferredSize();
+  }
+
+  gfx::Size GetMinimumSize() const override {
+    return primary_label_->GetMinimumSize();
+  }
+
+  int GetHeightForWidth(int width) const override {
+    return primary_label_->GetHeightForWidth(width);
+  }
+
+ private:
+  static void SetMultilineParams(views::Label* label, bool is_filename) {
+    label->SetElideBehavior(is_filename ? gfx::NO_ELIDE : gfx::ELIDE_TAIL);
+  }
+
+  raw_ptr<RenderTextFactoryLabel> primary_label_;
+  raw_ptr<SolidLabel> label_fading_out_;
+  absl::optional<bool> was_filename_;
+  double percent_ = 1.0;
+};
+
+// ToolbarActionHoverCardBubbleView:
+// ----------------------------------------------------------
+
+// TODO(crbug.com/1351778): Add content based on `action_view`.
+ToolbarActionHoverCardBubbleView::ToolbarActionHoverCardBubbleView(
+    ToolbarActionView* action_view)
+    : BubbleDialogDelegateView(action_view,
+                               views::BubbleBorder::TOP_LEFT,
+                               views::BubbleBorder::STANDARD_SHADOW) {
+  DCHECK(base::FeatureList::IsEnabled(
+      extensions_features::kExtensionsMenuAccessControl));
+
+  // Remove dialog's default buttons.
+  SetButtons(ui::DIALOG_BUTTON_NONE);
+
+  // Remove the accessible role so that hover cards are not read when they
+  // appear because tabs handle accessibility text.
+  SetAccessibleRole(ax::mojom::Role::kNone);
+
+  // We'll do all of our own layout inside the bubble, so no need to inset this
+  // view inside the client view.
+  set_margins(gfx::Insets());
+
+  // Set so that when hovering over a toolbar action in a inactive window that
+  // window will not become active. Setting this to false creates the need to
+  // explicitly hide the hovercard on press, touch, and keyboard events.
+  SetCanActivate(false);
+#if BUILDFLAG(IS_MAC)
+  set_accept_events(false);
+#endif
+
+  // Set so that the toolbar action hover card is not focus traversable when
+  // keyboard navigating through the tab strip.
+  set_focus_traversable_from_anchor_view(false);
+
+  // Set up content.
+  title_label_ = AddChildView(std::make_unique<FadeLabel>(
+      CONTEXT_TAB_HOVER_CARD_TITLE, kHoverCardTitleMaxLines));
+  // TODO(crbug.com/1351778): Use 'alert_label' for extension's site access
+  // information.
+  UpdateCardContent();
+
+  // Set up layout.
+  views::FlexLayout* const layout =
+      SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical);
+  layout->SetMainAxisAlignment(views::LayoutAlignment::kStart);
+  layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+  layout->SetCollapseMargins(true);
+
+  title_label_->SetProperty(views::kMarginsKey, kTitleMargins);
+  title_label_->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
+                               views::MaximumFlexSizeRule::kScaleToMaximum)
+          .WithOrder(2));
+
+  if (CustomShadowsSupported()) {
+    corner_radius_ = ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
+        views::Emphasis::kHigh);
+  }
+
+  // Set up widget.
+  views::BubbleDialogDelegateView::CreateBubble(this);
+  set_adjust_if_offscreen(true);
+
+  GetBubbleFrameView()->SetPreferredArrowAdjustment(
+      views::BubbleFrameView::PreferredArrowAdjustment::kOffset);
+  GetBubbleFrameView()->set_hit_test_transparent(true);
+
+  if (using_rounded_corners())
+    GetBubbleFrameView()->SetCornerRadius(corner_radius_.value());
+
+  // Start in the fully "faded-in" position so that whatever text we initially
+  // display is visible.
+  SetTextFade(1.0);
+}
+
+void ToolbarActionHoverCardBubbleView::UpdateCardContent() {
+  title_label_->SetText(u"Extension name", absl::nullopt);
+}
+
+void ToolbarActionHoverCardBubbleView::SetTextFade(double percent) {
+  title_label_->SetFade(percent);
+}
+
+void ToolbarActionHoverCardBubbleView::OnThemeChanged() {
+  BubbleDialogDelegateView::OnThemeChanged();
+
+  // Bubble closes if the theme changes to the point where the border has to be
+  // regenerated. See crbug.com/1140256
+  if (using_rounded_corners() != CustomShadowsSupported()) {
+    GetWidget()->Close();
+    return;
+  }
+}
+
+ToolbarActionHoverCardBubbleView::~ToolbarActionHoverCardBubbleView() = default;
+
+BEGIN_METADATA(ToolbarActionHoverCardBubbleView,
+               views::BubbleDialogDelegateView)
+END_METADATA
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h
new file mode 100644
index 0000000..5cbb84e
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h
@@ -0,0 +1,46 @@
+// 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_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_BUBBLE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_BUBBLE_VIEW_H_
+
+#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+
+class ToolbarActionView;
+
+// Dialog that displays a hover card with extensions information.
+class ToolbarActionHoverCardBubbleView
+    : public views::BubbleDialogDelegateView {
+ public:
+  METADATA_HEADER(ToolbarActionHoverCardBubbleView);
+  explicit ToolbarActionHoverCardBubbleView(ToolbarActionView* action_view);
+  ToolbarActionHoverCardBubbleView(const ToolbarActionHoverCardBubbleView&) =
+      delete;
+  ToolbarActionHoverCardBubbleView& operator=(
+      const ToolbarActionHoverCardBubbleView&) = delete;
+  ~ToolbarActionHoverCardBubbleView() override;
+
+  // Updates the hover card content.
+  // TODO(crbug.com/1351778): Update content based on a given `action_view`.
+  void UpdateCardContent();
+
+  // Update the text fade to the given percent, which should be between 0 and 1.
+  void SetTextFade(double percent);
+
+ private:
+  class FadeLabel;
+
+  bool using_rounded_corners() const { return corner_radius_.has_value(); }
+
+  // views::BubbleDialogDelegateView:
+  void OnThemeChanged() override;
+
+  raw_ptr<FadeLabel> title_label_ = nullptr;
+
+  absl::optional<int> corner_radius_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
new file mode 100644
index 0000000..1a323bd
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
@@ -0,0 +1,259 @@
+// 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/toolbar/toolbar_action_hover_card_bubble_view.h"
+
+#include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "extensions/common/extension_features.h"
+#include "ui/events/types/event_type.h"
+#include "ui/gfx/animation/animation_test_api.h"
+#include "ui/views/test/widget_test.h"
+
+namespace {
+
+// Similar to views::test::WidgetDestroyedWaiter but waiting after the widget
+// has been closed is a no-op rather than an error.
+// TODO(crbug.com/1354661): Move SafeWidgetDestroyedWaiter to a shared file
+// since it's used by multiple tests.
+class SafeWidgetDestroyedWaiter : public views::WidgetObserver {
+ public:
+  explicit SafeWidgetDestroyedWaiter(views::Widget* widget) {
+    observation_.Observe(widget);
+  }
+
+  // views::WidgetObserver:
+  void OnWidgetDestroyed(views::Widget* widget) override {
+    observation_.Reset();
+    if (!quit_closure_.is_null())
+      std::move(quit_closure_).Run();
+  }
+
+  void Wait() {
+    if (!observation_.IsObserving())
+      return;
+    DCHECK(quit_closure_.is_null());
+    quit_closure_ = run_loop_.QuitClosure();
+    run_loop_.Run();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  base::OnceClosure quit_closure_;
+  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
+      this};
+};
+
+}  // namespace
+
+class ToolbarActionHoverCardBubbleViewUITest : public ExtensionsToolbarUITest {
+ public:
+  ToolbarActionHoverCardBubbleViewUITest()
+      : animation_mode_reset_(gfx::AnimationTestApi::SetRichAnimationRenderMode(
+            gfx::Animation::RichAnimationRenderMode::FORCE_DISABLED)) {
+    ToolbarActionHoverCardController::disable_animations_for_testing_ = true;
+    scoped_feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControl);
+  }
+  ToolbarActionHoverCardBubbleViewUITest(
+      const ToolbarActionHoverCardBubbleViewUITest&) = delete;
+  ToolbarActionHoverCardBubbleViewUITest& operator=(
+      const ToolbarActionHoverCardBubbleViewUITest&) = delete;
+  ~ToolbarActionHoverCardBubbleViewUITest() override = default;
+
+  ToolbarActionHoverCardBubbleView* hover_card() {
+    return GetExtensionsToolbarContainer()
+        ->action_hover_card_controller_->hover_card_;
+  }
+
+  void HoverMouseOverActionView(ToolbarActionView* action_view) {
+    // We don't use ToolbarActionView::OnMouseEntered here to invoke the hover
+    // card because that path is disabled in browser tests. If we enabled it,
+    // the real mouse might interfere with the test.
+    GetExtensionsToolbarContainer()->UpdateToolbarActionHoverCard(
+        action_view, ToolbarActionHoverCardUpdateType::kHover);
+  }
+
+  void ClickMouseOnActionView(ToolbarActionView* action_view) {
+    ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                               base::TimeTicks(), ui::EF_NONE, 0);
+    action_view->OnMousePressed(mouse_event);
+  }
+
+  void MouseExitsFromExtensionsContainer() {
+    ui::MouseEvent mouse_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
+                               base::TimeTicks(), ui::EF_NONE, 0);
+    GetExtensionsToolbarContainer()->OnMouseExited(mouse_event);
+  }
+
+  void MouseMovesInExtensionsContainer() {
+    ui::MouseEvent mouse_event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
+                               base::TimeTicks(), ui::EF_NONE, 0);
+    GetExtensionsToolbarContainer()->OnMouseMoved(mouse_event);
+  }
+
+  scoped_refptr<const extensions::Extension> LoadExtensionAndPinIt(
+      const std::string& path) {
+    scoped_refptr<const extensions::Extension> extension =
+        LoadTestExtension(path);
+
+    ToolbarActionsModel* const toolbar_model =
+        ToolbarActionsModel::Get(browser()->profile());
+    toolbar_model->SetActionVisibility(extension->id(), true);
+    GetExtensionsToolbarContainer()->GetWidget()->LayoutRootViewIfNecessary();
+    return extension;
+  }
+
+  // DialogBrowserTest:
+  void ShowUi(const std::string& name) override {
+    LoadExtensionAndPinIt("extensions/simple_with_popup");
+    auto action_views = GetVisibleToolbarActionViews();
+    ASSERT_EQ(action_views.size(), 1u);
+
+    HoverMouseOverActionView(action_views[0]);
+    views::test::WidgetVisibleWaiter(hover_card()->GetWidget()).Wait();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+  std::unique_ptr<base::AutoReset<gfx::Animation::RichAnimationRenderMode>>
+      animation_mode_reset_;
+};
+
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest, InvokeUi) {
+  ShowAndVerifyUi();
+}
+
+// Verify hover card is visible while hovering and not visible outside of the
+// extensions container.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetVisibleOnHover) {
+  ShowUi("");
+  views::Widget* const widget = hover_card()->GetWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+
+  MouseExitsFromExtensionsContainer();
+  EXPECT_FALSE(widget->IsVisible());
+}
+
+// Verify anchor is correctly updated when moving hover from one action view to
+// another.
+// TODO(crbug.com/1351778): Once implemented, verify hover card content
+// matches corresponding action view.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetAnchoredToCorrectActionView) {
+  LoadExtensionAndPinIt("extensions/simple_with_popup");
+  LoadExtensionAndPinIt("extensions/simple_with_icon");
+  auto action_views = GetVisibleToolbarActionViews();
+  ASSERT_EQ(action_views.size(), 2u);
+
+  HoverMouseOverActionView(action_views[0]);
+  views::Widget* const widget = hover_card()->GetWidget();
+  views::test::WidgetVisibleWaiter(widget).Wait();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+  EXPECT_EQ(hover_card()->GetAnchorView(),
+            static_cast<views::View*>(action_views[0]));
+
+  // Note that the widget is the same because it transitions from one action
+  // view to the other.
+  HoverMouseOverActionView(action_views[1]);
+  views::test::WidgetVisibleWaiter(widget).Wait();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+  EXPECT_EQ(hover_card()->GetAnchorView(),
+            static_cast<views::View*>(action_views[1]));
+}
+
+// Verify hover card is not visible when mouse moves inside the extensions
+// container to a button that is not a toolbar icon view (which has its own 'on
+// mouse moved' event listener).
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetNotVisibleOnExtensionsControl) {
+  ShowUi("");
+  views::Widget* const widget = hover_card()->GetWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+
+  MouseMovesInExtensionsContainer();
+  EXPECT_FALSE(widget->IsVisible());
+}
+
+// Verify hover card is not visible after clicking on a toolbar action view.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetNotVisibleOnToolbarActionViewClick) {
+  ShowUi("");
+  views::Widget* const widget = hover_card()->GetWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+
+  auto action_views = GetVisibleToolbarActionViews();
+  ASSERT_EQ(action_views.size(), 1u);
+
+  ClickMouseOnActionView(action_views[0]);
+  EXPECT_FALSE(widget->IsVisible());
+}
+
+// Verify hover card is not visible on focus, similar to tooltip behavior.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetNotVisibleOnFocus) {
+  LoadExtensionAndPinIt("extensions/simple_with_popup");
+  auto action_views = GetVisibleToolbarActionViews();
+  ASSERT_EQ(action_views.size(), 1u);
+
+  GetExtensionsToolbarContainer()->GetFocusManager()->SetFocusedView(
+      action_views[0]);
+  EXPECT_EQ(hover_card(), nullptr);
+}
+
+// Verify that the hover card is not visible when any key is pressed.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewUITest,
+                       WidgetNotVisibleOnAnyKeyPressInSameWindow) {
+  ShowUi("");
+  views::Widget* const widget = hover_card()->GetWidget();
+  ASSERT_TRUE(widget);
+  EXPECT_TRUE(widget->IsVisible());
+
+  // Verify that the hover card widget is destroyed sometime between now and
+  // when we check afterwards. Depending on platform, the destruction could be
+  // synchronous or asynchronous.
+  SafeWidgetDestroyedWaiter widget_destroyed_waiter(widget);
+  EXPECT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_DOWN, false,
+                                              false, false, false));
+
+  // Note, fade in/out animations are disabled for testing so this should be
+  // relatively quick.
+  widget_destroyed_waiter.Wait();
+  EXPECT_EQ(hover_card(), nullptr);
+}
+
+class ToolbarActionHoverCardBubbleViewDisabledFeatureUITest
+    : public ToolbarActionHoverCardBubbleViewUITest {
+ public:
+  ToolbarActionHoverCardBubbleViewDisabledFeatureUITest() {
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitAndDisableFeature(
+        extensions_features::kExtensionsMenuAccessControl);
+  }
+};
+
+// Verify hover card is not visible on toolbar action view hover when the
+// feature is disabled.
+IN_PROC_BROWSER_TEST_F(ToolbarActionHoverCardBubbleViewDisabledFeatureUITest,
+                       WidgetNotVisibleWhenDisabledFeature) {
+  LoadExtensionAndPinIt("extensions/simple_with_popup");
+  auto action_views = GetVisibleToolbarActionViews();
+  ASSERT_EQ(action_views.size(), 1u);
+
+  HoverMouseOverActionView(action_views[0]);
+  EXPECT_EQ(hover_card(), nullptr);
+}
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc
new file mode 100644
index 0000000..ecf9d24e
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc
@@ -0,0 +1,349 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
+
+#include "base/bind.h"
+#include "base/callback_list.h"
+#include "base/feature_list.h"
+#include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
+#include "extensions/common/extension_features.h"
+#include "ui/events/event_observer.h"
+#include "ui/views/event_monitor.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+constexpr base::TimeDelta kTriggerDelay = base::Milliseconds(300);
+constexpr base::TimeDelta kHoverCardSlideDuration = base::Milliseconds(200);
+
+}  // namespace
+
+// static
+bool ToolbarActionHoverCardController::disable_animations_for_testing_ = false;
+
+//-------------------------------------------------------------------
+// ToolbarActionHoverCardController::EventSniffer
+
+// Listens in on the browser event stream and hides an associated hover card
+// on any keypress, mouse click, or gesture.
+class ToolbarActionHoverCardController::EventSniffer
+    : public ui::EventObserver {
+ public:
+  explicit EventSniffer(ToolbarActionHoverCardController* controller)
+      : controller_(controller) {
+    // Note that null is a valid value for the second parameter here; if for
+    // some reason there is no native window it simply falls back to
+    // application-wide event-sniffing, which for this case is better than not
+    // watching events at all.
+    event_monitor_ = views::EventMonitor::CreateWindowMonitor(
+        this,
+        controller_->extensions_container_->GetWidget()->GetNativeWindow(),
+        {ui::ET_KEY_PRESSED, ui::ET_KEY_RELEASED, ui::ET_MOUSE_PRESSED,
+         ui::ET_MOUSE_RELEASED, ui::ET_GESTURE_BEGIN, ui::ET_GESTURE_END});
+  }
+
+  ~EventSniffer() override = default;
+
+ protected:
+  // ui::EventObserver:
+  void OnEvent(const ui::Event& event) override {
+    controller_->UpdateHoverCard(nullptr,
+                                 ToolbarActionHoverCardUpdateType::kEvent);
+  }
+
+ private:
+  const raw_ptr<ToolbarActionHoverCardController> controller_;
+  std::unique_ptr<views::EventMonitor> event_monitor_;
+};
+
+//-------------------------------------------------------------------
+// ToolbarActionHoverCardController
+
+ToolbarActionHoverCardController::ToolbarActionHoverCardController(
+    ExtensionsToolbarContainer* extensions_container)
+    : extensions_container_(extensions_container) {}
+
+ToolbarActionHoverCardController::~ToolbarActionHoverCardController() = default;
+
+// static
+bool ToolbarActionHoverCardController::UseAnimations() {
+  return gfx::Animation::ShouldRenderRichAnimation();
+}
+
+bool ToolbarActionHoverCardController::IsHoverCardVisible() const {
+  return hover_card_ != nullptr && hover_card_->GetWidget() &&
+         !hover_card_->GetWidget()->IsClosed();
+}
+
+bool ToolbarActionHoverCardController::IsHoverCardShowingForAction(
+    ToolbarActionView* action_view) const {
+  return IsHoverCardVisible() && !fade_animator_->IsFadingOut() &&
+         GetTargetAnchorView() == action_view;
+}
+
+void ToolbarActionHoverCardController::UpdateHoverCard(
+    ToolbarActionView* action_view,
+    ToolbarActionHoverCardUpdateType update_type) {
+  if (!base::FeatureList::IsEnabled(
+          extensions_features::kExtensionsMenuAccessControl)) {
+    return;
+  }
+
+  // TODO(crbug.com/1351778): Check if we need to handle never displaying a
+  // hover card for a toolbar action that is closing (pin was removed).
+
+  // Update this ASAP so that if we try to fade-in and we have the wrong target
+  // then when the fade timer elapses we won't incorrectly try to fade in on the
+  // wrong action view.
+  if (target_action_view_ != action_view) {
+    delayed_show_timer_.Stop();
+    target_action_view_observation_.Reset();
+    if (action_view)
+      target_action_view_observation_.Observe(action_view);
+    target_action_view_ = action_view;
+  }
+
+  // If there's nothing to attach to then there's no point in creating a card.
+  if (!hover_card_ && (!action_view || !extensions_container_->GetWidget()))
+    return;
+
+  switch (update_type) {
+    case ToolbarActionHoverCardUpdateType::kHover:
+      if (!action_view)
+        last_mouse_exit_timestamp_ = base::TimeTicks::Now();
+      break;
+    case ToolbarActionHoverCardUpdateType::kToolbarActionRemoved:
+      // Should not have an action view associated.
+      DCHECK(!action_view);
+      break;
+    case ToolbarActionHoverCardUpdateType::kEvent:
+      // No special action taken for this type of event.
+      break;
+  }
+
+  if (action_view)
+    UpdateOrShowHoverCard(action_view, update_type);
+  else
+    HideHoverCard();
+}
+
+// TODO(crbug.com/1351778): Fix inkdrop when hovering over `action_view`.
+void ToolbarActionHoverCardController::UpdateOrShowHoverCard(
+    ToolbarActionView* action_view,
+    ToolbarActionHoverCardUpdateType update_type) {
+  // Close is asynchronous, so make sure that if we're closing we clear out all
+  // of our data *now* rather than waiting for the deletion message.
+  if (hover_card_ && hover_card_->GetWidget()->IsClosed())
+    OnViewIsDeleting(hover_card_);
+
+  // Cancel any pending fades.
+  if (hover_card_ && fade_animator_->IsFadingOut()) {
+    fade_animator_->CancelFadeOut();
+  }
+
+  if (hover_card_) {
+    // Card should never exist without an anchor.
+    DCHECK(hover_card_->GetAnchorView());
+    // TODO(crbug.com/1351778): Update card contents for `action_view` since the
+    // card was visible.
+
+    // If widget is already visible and anchored to the correct action view we
+    // should not try to reset the anchor view or reshow.
+    if (!UseAnimations() || (hover_card_->GetAnchorView() == action_view &&
+                             !slide_animator_->is_animating())) {
+      slide_animator_->SnapToAnchorView(action_view);
+    } else {
+      slide_animator_->AnimateToAnchorView(action_view);
+    }
+    return;
+  }
+
+  // Maybe make hover card visible. Disabling animations for testing also
+  // eliminates the show timer, lest the tests have to be significantly more
+  // complex and time-consuming.
+  const bool is_initial = !ShouldShowImmediately(action_view);
+  if (is_initial && !disable_animations_for_testing_) {
+    delayed_show_timer_.Start(
+        FROM_HERE, kTriggerDelay,
+        base::BindOnce(&ToolbarActionHoverCardController::ShowHoverCard,
+                       weak_ptr_factory_.GetWeakPtr(), true, action_view));
+  } else {
+    // Just in case, cancel the timer. This shouldn't cancel a delayed capture
+    // since delayed capture only happens when the hover card already exists,
+    // and this code is only invoked if there is no hover card yet.
+    delayed_show_timer_.Stop();
+    DCHECK_EQ(target_action_view_, action_view);
+    ShowHoverCard(is_initial, action_view);
+  }
+}
+
+void ToolbarActionHoverCardController::CreateHoverCard(
+    ToolbarActionView* action_view) {
+  hover_card_ = new ToolbarActionHoverCardBubbleView(action_view);
+  hover_card_observation_.Observe(hover_card_.get());
+  event_sniffer_ = std::make_unique<EventSniffer>(this);
+
+  slide_animator_ = std::make_unique<views::BubbleSlideAnimator>(hover_card_);
+  slide_animator_->SetSlideDuration(kHoverCardSlideDuration);
+  slide_progressed_subscription_ =
+      slide_animator_->AddSlideProgressedCallback(base::BindRepeating(
+          &ToolbarActionHoverCardController::OnSlideAnimationProgressed,
+          weak_ptr_factory_.GetWeakPtr()));
+  slide_complete_subscription_ =
+      slide_animator_->AddSlideCompleteCallback(base::BindRepeating(
+          &ToolbarActionHoverCardController::OnSlideAnimationComplete,
+          weak_ptr_factory_.GetWeakPtr()));
+
+  fade_animator_ =
+      std::make_unique<views::WidgetFadeAnimator>(hover_card_->GetWidget());
+  fade_complete_subscription_ =
+      fade_animator_->AddFadeCompleteCallback(base::BindRepeating(
+          &ToolbarActionHoverCardController::OnFadeAnimationEnded,
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ToolbarActionHoverCardController::ShowHoverCard(
+    bool is_initial,
+    const ToolbarActionView* intended_action_view) {
+  // Make sure the hover card isn't accidentally shown if it's already visible
+  // or if the anchor is gone or changed.
+  if (hover_card_ || target_action_view_ != intended_action_view ||
+      !TargetActionViewIsValid())
+    return;
+
+  CreateHoverCard(target_action_view_);
+  // TODO(crbug.com/1351778): Update card contents for `target_action_view`.
+  slide_animator_->UpdateTargetBounds();
+  // TODO(crbug.com/1351778): Do we need to fix widget stack order? Revisit
+  // this, specially after adding IPH.
+
+  if (!is_initial || !UseAnimations()) {
+    hover_card_->GetWidget()->Show();
+    return;
+  }
+
+  fade_animator_->FadeIn();
+}
+
+void ToolbarActionHoverCardController::HideHoverCard() {
+  if (!hover_card_ || hover_card_->GetWidget()->IsClosed())
+    return;
+
+  // Cancel any pending fade-in.
+  if (fade_animator_->IsFadingIn()) {
+    fade_animator_->CancelFadeIn();
+  }
+
+  // This needs to be called whether we're doing a fade or a pop out.
+  slide_animator_->StopAnimation();
+  if (!UseAnimations()) {
+    hover_card_->GetWidget()->Close();
+    return;
+  }
+  if (fade_animator_->IsFadingOut())
+    return;
+
+  fade_animator_->FadeOut();
+}
+
+bool ToolbarActionHoverCardController::ShouldShowImmediately(
+    const ToolbarActionView* action_view) const {
+  // If less than `kShowWithoutDelayTimeBuffer` time has passed since the hover
+  // card was last visible then it is shown immediately. This is to account for
+  // if hover unintentionally leaves the extensions container.
+  constexpr base::TimeDelta kShowWithoutDelayTimeBuffer =
+      base::Milliseconds(300);
+  base::TimeDelta elapsed_time =
+      base::TimeTicks::Now() - last_mouse_exit_timestamp_;
+
+  bool within_delay_time_buffer = !last_mouse_exit_timestamp_.is_null() &&
+                                  elapsed_time <= kShowWithoutDelayTimeBuffer;
+  // Hover cards should be shown without delay if triggered within the time
+  // buffer.
+  // TODO(crbug.com/1351778): Should hover cards be shown if the action view
+  // is keyboard focused?
+  return within_delay_time_buffer;
+}
+
+const views::View* ToolbarActionHoverCardController::GetTargetAnchorView()
+    const {
+  if (!hover_card_)
+    return nullptr;
+  if (slide_animator_->is_animating())
+    return slide_animator_->desired_anchor_view();
+  return hover_card_->GetAnchorView();
+}
+
+bool ToolbarActionHoverCardController::TargetActionViewIsValid() const {
+  // TODO(crbug.com/1351778): Explore more conditions where an action view is no
+  // longer valid.
+  return target_action_view_ && target_action_view_->GetVisible();
+}
+
+void ToolbarActionHoverCardController::OnFadeAnimationEnded(
+    views::WidgetFadeAnimator* animator,
+    views::WidgetFadeAnimator::FadeType fade_type) {
+  if (fade_type == views::WidgetFadeAnimator::FadeType::kFadeOut)
+    hover_card_->GetWidget()->Close();
+}
+
+void ToolbarActionHoverCardController::OnSlideAnimationProgressed(
+    views::BubbleSlideAnimator* animator,
+    double value) {
+  // TODO(crbug.com/1351778): Set text fade for hover card once content is
+  // updated based on the toolbar action.
+}
+
+void ToolbarActionHoverCardController::OnSlideAnimationComplete(
+    views::BubbleSlideAnimator* animator) {
+  // TODO(crbug.com/1351778): Set text fade for hover card once content is
+  // updated based on the toolbar action.
+}
+
+void ToolbarActionHoverCardController::OnViewIsDeleting(
+    views::View* observed_view) {
+  if (hover_card_ == observed_view) {
+    delayed_show_timer_.Stop();
+    hover_card_observation_.Reset();
+    event_sniffer_.reset();
+    slide_progressed_subscription_ = base::CallbackListSubscription();
+    slide_complete_subscription_ = base::CallbackListSubscription();
+    fade_complete_subscription_ = base::CallbackListSubscription();
+    slide_animator_.reset();
+    fade_animator_.reset();
+    hover_card_ = nullptr;
+  } else if (target_action_view_ == observed_view) {
+    UpdateHoverCard(nullptr,
+                    ToolbarActionHoverCardUpdateType::kToolbarActionRemoved);
+    // These postconditions should always be met after calling
+    // UpdateHoverCard(nullptr, ...)
+    DCHECK(!target_action_view_);
+    DCHECK(!target_action_view_observation_.IsObserving());
+  }
+}
+
+void ToolbarActionHoverCardController::OnViewVisibilityChanged(
+    views::View* observed_view,
+    views::View* starting_view) {
+  // Only care about target action view becoming invisible.
+  if (observed_view != target_action_view_)
+    return;
+  // Visibility comes from `starting_view` or the widget, if no starting view;
+  // see documentation for ViewObserver::OnViewVisibilityChanged().
+  const bool visible = starting_view
+                           ? starting_view->GetVisible()
+                           : (observed_view->GetWidget() &&
+                              observed_view->GetWidget()->IsVisible());
+  // If visibility changed to false, treat it as if the target action view had
+  // gone away.
+  if (!visible)
+    OnViewIsDeleting(observed_view);
+}
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h
new file mode 100644
index 0000000..a30de7e8
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.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_UI_VIEWS_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_CONTROLLER_H_
+#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_CONTROLLER_H_
+
+#include <memory>
+
+#include "base/callback_list.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/raw_ptr.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/ui/toolbar/toolbar_action_hover_card_types.h"
+#include "ui/views/animation/bubble_slide_animator.h"
+#include "ui/views/animation/widget_fade_animator.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
+
+class ToolbarActionHoverCardBubbleView;
+class ExtensionsToolbarContainer;
+class ToolbarActionView;
+
+// Controls how hover cards are shown and hidden for toolbar actions.
+class ToolbarActionHoverCardController : public views::ViewObserver {
+ public:
+  explicit ToolbarActionHoverCardController(
+      ExtensionsToolbarContainer* extensions_container);
+  ~ToolbarActionHoverCardController() override;
+
+  // Returns whether hover card animations should be shown on the current
+  // device.
+  static bool UseAnimations();
+
+  bool IsHoverCardVisible() const;
+  bool IsHoverCardShowingForAction(ToolbarActionView* action_view) const;
+  void UpdateHoverCard(ToolbarActionView* action_view,
+                       ToolbarActionHoverCardUpdateType update_type);
+
+ private:
+  friend class ToolbarActionHoverCardBubbleViewUITest;
+
+  class EventSniffer;
+
+  void UpdateOrShowHoverCard(ToolbarActionView* action_view,
+                             ToolbarActionHoverCardUpdateType update_type);
+  void CreateHoverCard(ToolbarActionView* action_view);
+  void ShowHoverCard(bool is_initial, const ToolbarActionView* action_view);
+  void HideHoverCard();
+
+  bool ShouldShowImmediately(const ToolbarActionView* action_view) const;
+
+  const views::View* GetTargetAnchorView() const;
+
+  // Determines if `target_action_view` is still valid. Call this when entering
+  // ToolbarActionHoverCardController from an asynchronous callback.
+  bool TargetActionViewIsValid() const;
+
+  // Animator events:
+  void OnFadeAnimationEnded(views::WidgetFadeAnimator* animator,
+                            views::WidgetFadeAnimator::FadeType fade_type);
+  void OnSlideAnimationProgressed(views::BubbleSlideAnimator* animator,
+                                  double value);
+  void OnSlideAnimationComplete(views::BubbleSlideAnimator* animator);
+
+  // views::ViewObserver:
+  void OnViewIsDeleting(views::View* observed_view) override;
+  void OnViewVisibilityChanged(views::View* observed_view,
+                               views::View* starting_view) override;
+
+  // Timestamp of the last time the hover card is hidden by the mouse leaving
+  // the tab strip. This is used for reshowing the hover card without delay if
+  // the mouse reenters within a given amount of time.
+  base::TimeTicks last_mouse_exit_timestamp_;
+
+  raw_ptr<ToolbarActionView> target_action_view_ = nullptr;
+  const raw_ptr<ExtensionsToolbarContainer> extensions_container_;
+  raw_ptr<ToolbarActionHoverCardBubbleView> hover_card_ = nullptr;
+
+  base::ScopedObservation<views::View, views::ViewObserver>
+      hover_card_observation_{this};
+  base::ScopedObservation<views::View, views::ViewObserver>
+      target_action_view_observation_{this};
+  std::unique_ptr<EventSniffer> event_sniffer_;
+
+  // Used to animate the tab hover card's opacity when visible or not.
+  std::unique_ptr<views::WidgetFadeAnimator> fade_animator_;
+  // Fade animations interfere with browser tests so we disable them in tests.
+  static bool disable_animations_for_testing_;
+
+  // Used to animate the tab hover card's movement between tabs.
+  std::unique_ptr<views::BubbleSlideAnimator> slide_animator_;
+
+  base::CallbackListSubscription fade_complete_subscription_;
+  base::CallbackListSubscription slide_progressed_subscription_;
+  base::CallbackListSubscription slide_complete_subscription_;
+
+  // Ensure that this timer is destroyed before anything else is cleaned up.
+  base::OneShotTimer delayed_show_timer_;
+  base::WeakPtrFactory<ToolbarActionHoverCardController> weak_ptr_factory_{
+      this};
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_ACTION_HOVER_CARD_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
index 2885304c..a3189860 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
@@ -8,6 +8,7 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -19,10 +20,12 @@
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/extensions/extension_context_menu_controller.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/notification_source.h"
+#include "extensions/common/extension_features.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -126,6 +129,26 @@
   return MenuButton::OnKeyPressed(event);
 }
 
+// Linux enter/leave events are sometimes flaky, so we don't want to "miss"
+// an enter event and fail to hover the button. This is effectively a no-op if
+// the button is already showing the hover card (crbug.com/1326272).
+void ToolbarActionView::OnMouseMoved(const ui::MouseEvent& event) {
+  MaybeUpdateHoverCardStatus(event);
+}
+
+void ToolbarActionView::OnMouseEntered(const ui::MouseEvent& event) {
+  MaybeUpdateHoverCardStatus(event);
+}
+
+void ToolbarActionView::MaybeUpdateHoverCardStatus(
+    const ui::MouseEvent& event) {
+  if (!GetWidget()->IsMouseEventsEnabled())
+    return;
+
+  view_controller_->UpdateHoverCard(this,
+                                    ToolbarActionHoverCardUpdateType::kHover);
+}
+
 content::WebContents* ToolbarActionView::GetCurrentWebContents() const {
   return delegate_->GetCurrentWebContents();
 }
@@ -144,7 +167,10 @@
     SetImageModel(views::Button::STATE_NORMAL,
                   ui::ImageModel::FromImageSkia(icon));
 
-  SetTooltipText(view_controller_->GetTooltip(web_contents));
+  if (!base::FeatureList::IsEnabled(
+          extensions_features::kExtensionsMenuAccessControl)) {
+    SetTooltipText(view_controller_->GetTooltip(web_contents));
+  }
 
   Layout();  // We need to layout since we may have added an icon as a result.
   SchedulePaint();
@@ -163,6 +189,8 @@
 }
 
 bool ToolbarActionView::OnMousePressed(const ui::MouseEvent& event) {
+  view_controller_->UpdateHoverCard(nullptr,
+                                    ToolbarActionHoverCardUpdateType::kEvent);
   if (event.IsOnlyLeftMouseButton()) {
     if (view_controller()->IsShowingPopup()) {
       // Left-clicking the button should always hide the popup.  In most cases,
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.h b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
index 708ba85..0df01a5 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
@@ -7,6 +7,7 @@
 
 #include "base/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/context_menu_controller.h"
@@ -54,12 +55,16 @@
   ToolbarActionView& operator=(const ToolbarActionView&) = delete;
   ~ToolbarActionView() override;
 
+  void MaybeUpdateHoverCardStatus(const ui::MouseEvent& event);
+
   // views::MenuButton:
   gfx::Rect GetAnchorBoundsInScreen() const override;
   std::unique_ptr<views::LabelButtonBorder> CreateDefaultBorder()
       const override;
   bool IsTriggerableEvent(const ui::Event& event) override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
+  void OnMouseMoved(const ui::MouseEvent& event) override;
+  void OnMouseEntered(const ui::MouseEvent& event) override;
 
   // ToolbarActionViewDelegateViews:
   content::WebContents* GetCurrentWebContents() const override;
@@ -76,6 +81,8 @@
   int GetDragOperationsForTest(const gfx::Point& point);
 
  private:
+  friend class ToolbarActionHoverCardBubbleViewUITest;
+
   // views::MenuButton:
   gfx::Size CalculatePreferredSize() const override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
@@ -110,7 +117,6 @@
   // doesn't hide on mouse press and immediately reshow on mouse release.
   bool suppress_next_release_ = false;
 
-
   // This controller is responsible for showing the context menu for an
   // extension.
   std::unique_ptr<ExtensionContextMenuController> context_menu_controller_;
diff --git a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc b/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc
deleted file mode 100644
index 3837a29..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc
+++ /dev/null
@@ -1,417 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/base64.h"
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/command_line.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/no_destructor.h"
-#include "base/path_service.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/task/thread_pool.h"
-#include "base/values.h"
-#include "chrome/browser/ash/accessibility/accessibility_manager.h"
-#include "chrome/browser/ash/camera_presence_notifier.h"
-#include "chrome/browser/ash/login/users/avatar/user_image_manager.h"
-#include "chrome/browser/ash/login/users/chrome_user_manager.h"
-#include "chrome/browser/ash/login/users/default_user_image/default_user_images.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager_factory.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/chrome_select_file_policy.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/grit/browser_resources.h"
-#include "chrome/grit/generated_resources.h"
-#include "chromeos/ash/components/audio/sounds.h"
-#include "components/user_manager/user.h"
-#include "components/user_manager/user_image/user_image.h"
-#include "components/user_manager/user_manager.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_ui.h"
-#include "content/public/common/url_constants.h"
-#include "net/base/data_url.h"
-#include "services/audio/public/cpp/sounds/sounds_manager.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/base/webui/web_ui_util.h"
-#include "ui/views/widget/widget.h"
-#include "url/gurl.h"
-
-namespace chromeos {
-namespace settings {
-namespace {
-
-using ::ash::AccessibilityManager;
-using ::ash::PlaySoundOption;
-using ::content::BrowserThread;
-
-}  // namespace
-
-ChangePictureHandler::ChangePictureHandler()
-    : previous_image_index_(user_manager::User::USER_IMAGE_INVALID),
-      camera_presence_notifier_(
-          base::BindRepeating(&ChangePictureHandler::SetCameraPresent,
-                              base::Unretained(this))) {
-  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
-  audio::SoundsManager* manager = audio::SoundsManager::Get();
-  manager->Initialize(static_cast<int>(Sound::kObjectDelete),
-                      bundle.GetRawDataResource(IDR_SOUND_OBJECT_DELETE_WAV));
-  manager->Initialize(static_cast<int>(Sound::kCameraSnap),
-                      bundle.GetRawDataResource(IDR_SOUND_CAMERA_SNAP_WAV));
-}
-
-ChangePictureHandler::~ChangePictureHandler() {
-  if (IsJavascriptAllowed()) {
-    ::ash::personalization_app::PersonalizationAppManagerFactory::
-        GetForBrowserContext(web_ui()->GetWebContents()->GetBrowserContext())
-            ->MaybeStartHatsTimer(
-                ::ash::personalization_app::HatsSurveyType::kAvatar);
-  }
-}
-
-void ChangePictureHandler::RegisterMessages() {
-  web_ui()->RegisterMessageCallback(
-      "chooseFile", base::BindRepeating(&ChangePictureHandler::HandleChooseFile,
-                                        base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "photoTaken", base::BindRepeating(&ChangePictureHandler::HandlePhotoTaken,
-                                        base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "discardPhoto",
-      base::BindRepeating(&ChangePictureHandler::HandleDiscardPhoto,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "onChangePicturePageInitialized",
-      base::BindRepeating(&ChangePictureHandler::HandlePageInitialized,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "selectImage",
-      base::BindRepeating(&ChangePictureHandler::HandleSelectImage,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "requestSelectedImage",
-      base::BindRepeating(&ChangePictureHandler::HandleRequestSelectedImage,
-                          base::Unretained(this)));
-}
-
-void ChangePictureHandler::OnJavascriptAllowed() {
-  user_manager_observation_.Observe(user_manager::UserManager::Get());
-  camera_presence_notifier_.Start();
-}
-
-void ChangePictureHandler::OnJavascriptDisallowed() {
-  DCHECK(user_manager_observation_.IsObservingSource(
-      user_manager::UserManager::Get()));
-  user_manager_observation_.Reset();
-
-  camera_presence_notifier_.Stop();
-
-  user_image_file_selector_.reset();
-}
-
-void ChangePictureHandler::SendDefaultImages() {
-  base::Value::Dict result;
-  result.Set("current_default_images",
-             default_user_image::GetCurrentImageSetAsListValue());
-  FireWebUIListener("default-images-changed", result);
-}
-
-void ChangePictureHandler::HandleChooseFile(const base::Value::List& args) {
-  DCHECK(args.empty());
-  user_image_file_selector_ =
-      std::make_unique<ash::UserImageFileSelector>(web_ui());
-  user_image_file_selector_->SelectFile(
-      base::BindOnce(&ChangePictureHandler::FileSelected,
-                     weak_ptr_factory_.GetWeakPtr()),
-      base::BindOnce(&ChangePictureHandler::FileSelectionCanceled,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void ChangePictureHandler::HandleDiscardPhoto(const base::Value::List& args) {
-  DCHECK(args.empty());
-  AccessibilityManager::Get()->PlayEarcon(
-      Sound::kObjectDelete, PlaySoundOption::kOnlyIfSpokenFeedbackEnabled);
-}
-
-void ChangePictureHandler::HandlePhotoTaken(const base::Value::List& args) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  AccessibilityManager::Get()->PlayEarcon(
-      Sound::kCameraSnap, PlaySoundOption::kOnlyIfSpokenFeedbackEnabled);
-
-  if (args.size() != 1 || !args[0].is_string())
-    NOTREACHED();
-  const std::string& image_url = args[0].GetString();
-  DCHECK(!image_url.empty());
-
-  std::string raw_data;
-  base::StringPiece url(image_url);
-  const char kDataUrlPrefix[] = "data:image/png;base64,";
-  const size_t kDataUrlPrefixLength = std::size(kDataUrlPrefix) - 1;
-  if (!base::StartsWith(url, kDataUrlPrefix) ||
-      !base::Base64Decode(url.substr(kDataUrlPrefixLength), &raw_data)) {
-    LOG(WARNING) << "Invalid image URL";
-    return;
-  }
-
-  // Use |raw_data| as image but first verify that it can be decoded.
-  user_photo_ = gfx::ImageSkia();
-  std::vector<unsigned char> photo_data(raw_data.begin(), raw_data.end());
-  user_photo_data_ = base::RefCountedBytes::TakeVector(&photo_data);
-
-  ImageDecoder::Cancel(this);
-  ImageDecoder::Start(this, std::move(raw_data));
-}
-
-void ChangePictureHandler::HandlePageInitialized(
-    const base::Value::List& args) {
-  DCHECK(args.empty());
-
-  AllowJavascript();
-
-  SendDefaultImages();
-  SendSelectedImage();
-  UpdateProfileImage();
-}
-
-void ChangePictureHandler::SendSelectedImage() {
-  const user_manager::User* user = GetUser();
-  DCHECK(user->GetAccountId().is_valid());
-
-  previous_image_index_ = user->image_index();
-  switch (previous_image_index_) {
-    case user_manager::User::USER_IMAGE_EXTERNAL: {
-      // User has image from camera/file, record it and add to the image list.
-      previous_image_ = user->GetImage();
-      previous_image_format_ = user->image_format();
-      if (previous_image_format_ == user_manager::UserImage::FORMAT_PNG &&
-          user->has_image_bytes()) {
-        previous_image_bytes_ = user->image_bytes();
-        SendOldImage(webui::GetPngDataUrl(previous_image_bytes_->front(),
-                                          previous_image_bytes_->size()));
-      } else {
-        previous_image_bytes_ = nullptr;
-        DCHECK(previous_image_.IsThreadSafe());
-        // Post a task because GetBitmapDataUrl does PNG encoding, which is
-        // slow for large images.
-        base::ThreadPool::PostTaskAndReplyWithResult(
-            FROM_HERE, {base::TaskPriority::USER_BLOCKING},
-            base::BindOnce(&webui::GetBitmapDataUrl, *previous_image_.bitmap()),
-            base::BindOnce(&ChangePictureHandler::SendOldImage,
-                           weak_ptr_factory_.GetWeakPtr()));
-      }
-      break;
-    }
-    case user_manager::User::USER_IMAGE_PROFILE: {
-      // User has their Profile image as the current image.
-      SendProfileImage(user->GetImage(), true);
-      break;
-    }
-    default: {
-      if (default_user_image::IsInCurrentImageSet(previous_image_index_)) {
-        // User has image from the current set of default images.
-        base::Value image_url(
-            default_user_image::GetDefaultImageUrl(previous_image_index_)
-                .spec());
-        FireWebUIListener("selected-image-changed", image_url);
-      } else {
-        // User has a deprecated default image, send it for preview.
-        previous_image_ = user->GetImage();
-        previous_image_bytes_ = nullptr;
-        previous_image_format_ = user_manager::UserImage::FORMAT_UNKNOWN;
-
-        base::Value::Dict result;
-        result.Set("url",
-                   default_user_image::GetDefaultImageUrl(previous_image_index_)
-                       .spec());
-        auto source_info =
-            default_user_image::GetDeprecatedDefaultImageSourceInfo(
-                previous_image_index_);
-        if (source_info.has_value()) {
-          result.Set("author", std::move(source_info.value().author));
-          result.Set("website", source_info.value().website.spec());
-        }
-        FireWebUIListener("preview-deprecated-image", result);
-      }
-    }
-  }
-}
-
-void ChangePictureHandler::SendProfileImage(const gfx::ImageSkia& image,
-                                            bool should_select) {
-  base::Value data_url(webui::GetBitmapDataUrl(*image.bitmap()));
-  base::Value select(should_select);
-  FireWebUIListener("profile-image-changed", data_url, select);
-}
-
-void ChangePictureHandler::UpdateProfileImage() {
-  auto* user_image_manager =
-      ChromeUserManager::Get()->GetUserImageManager(GetUser()->GetAccountId());
-  // If we have a downloaded profile image and haven't sent it in
-  // |SendSelectedImage|, send it now (without selecting).
-  if (previous_image_index_ != user_manager::User::USER_IMAGE_PROFILE &&
-      !user_image_manager->DownloadedProfileImage().isNull()) {
-    SendProfileImage(user_image_manager->DownloadedProfileImage(), false);
-  }
-  user_image_manager->DownloadProfileImage();
-}
-
-void ChangePictureHandler::SendOldImage(std::string&& image_url) {
-  FireWebUIListener("old-image-changed", base::Value(image_url));
-}
-
-void ChangePictureHandler::HandleSelectImage(const base::Value::List& args) {
-  if (args.size() != 2 || !args[0].is_string() || !args[1].is_string()) {
-    NOTREACHED();
-    return;
-  }
-  const std::string& image_url = args[0].GetString();
-  const std::string& image_type = args[1].GetString();
-  // |image_url| may be empty unless |image_type| is "default".
-  DCHECK(!image_type.empty());
-
-  auto* user_image_manager =
-      ChromeUserManager::Get()->GetUserImageManager(GetUser()->GetAccountId());
-  bool waiting_for_camera_photo = false;
-
-  // Track the index of previous selected message to be compared with the index
-  // of the new image.
-  int previous_image_index = GetUser()->image_index();
-
-  if (image_type == "old") {
-    // Previous image (from camera or manually uploaded) re-selected.
-    DCHECK(!previous_image_.isNull());
-    std::unique_ptr<user_manager::UserImage> user_image;
-    if (previous_image_format_ == user_manager::UserImage::FORMAT_PNG &&
-        previous_image_bytes_) {
-      user_image = std::make_unique<user_manager::UserImage>(
-          previous_image_, previous_image_bytes_, previous_image_format_);
-      user_image->MarkAsSafe();
-    } else {
-      user_image = user_manager::UserImage::CreateAndEncode(
-          previous_image_, user_manager::UserImage::FORMAT_JPEG);
-    }
-    user_image_manager->SaveUserImage(std::move(user_image));
-
-    VLOG(1) << "Selected old user image";
-  } else if (image_type == "default") {
-    int image_index = user_manager::User::USER_IMAGE_INVALID;
-    if (default_user_image::IsDefaultImageUrl(image_url, &image_index)) {
-      // One of the default user images.
-      user_image_manager->SaveUserDefaultImageIndex(image_index);
-
-      VLOG(1) << "Selected default user image: " << image_index;
-    } else {
-      LOG(WARNING) << "Invalid image_url for default image type: " << image_url;
-    }
-  } else if (image_type == "profile") {
-    // Profile image selected. Could be previous (old) user image.
-    user_image_manager->SaveUserImageFromProfileImage();
-  } else {
-    NOTREACHED() << "Unexpected image type: " << image_type;
-  }
-
-  int image_index = GetUser()->image_index();
-  // `previous_image_index` is used instead of `previous_image_index_` as the
-  // latter has the same value of `image_index` after new image is selected.
-  if (previous_image_index != image_index) {
-    ash::UserImageManager::RecordUserImageChanged(
-        ash::UserImageManager::ImageIndexToHistogramIndex(image_index));
-  }
-
-  // Ignore the result of the previous decoding if it's no longer needed.
-  if (!waiting_for_camera_photo)
-    ImageDecoder::Cancel(this);
-}
-
-void ChangePictureHandler::HandleRequestSelectedImage(
-    const base::Value::List& args) {
-  SendSelectedImage();
-}
-
-void ChangePictureHandler::FileSelected(const base::FilePath& path) {
-  auto* user_image_manager =
-      ChromeUserManager::Get()->GetUserImageManager(GetUser()->GetAccountId());
-
-  // Log an impression if image is selected from a file.
-  ash::UserImageManager::RecordUserImageChanged(
-      default_user_image::kHistogramImageExternal);
-
-  user_image_manager->SaveUserImageFromFile(path);
-  VLOG(1) << "Selected image from file";
-}
-
-void ChangePictureHandler::FileSelectionCanceled() {
-  SendSelectedImage();
-}
-
-void ChangePictureHandler::SetImageFromCamera(
-    const gfx::ImageSkia& photo,
-    base::RefCountedBytes* photo_bytes) {
-  std::unique_ptr<user_manager::UserImage> user_image =
-      std::make_unique<user_manager::UserImage>(
-          photo, photo_bytes, user_manager::UserImage::FORMAT_PNG);
-  user_image->MarkAsSafe();
-  ChromeUserManager::Get()
-      ->GetUserImageManager(GetUser()->GetAccountId())
-      ->SaveUserImage(std::move(user_image));
-
-  // Log an impression if image is taken from photo.
-  ash::UserImageManager::RecordUserImageChanged(
-      default_user_image::kHistogramImageFromCamera);
-  VLOG(1) << "Selected camera photo";
-}
-
-void ChangePictureHandler::SetCameraPresent(bool present) {
-  FireWebUIListener("camera-presence-changed", base::Value(present));
-}
-
-void ChangePictureHandler::OnUserImageChanged(const user_manager::User& user) {
-  // Not initialized yet.
-  if (previous_image_index_ == user_manager::User::USER_IMAGE_INVALID)
-    return;
-  SendSelectedImage();
-}
-
-void ChangePictureHandler::OnUserProfileImageUpdated(
-    const user_manager::User& user,
-    const gfx::ImageSkia& profile_image) {
-  // User profile image has been updated.
-  SendProfileImage(profile_image, false);
-}
-
-void ChangePictureHandler::OnImageDecoded(const SkBitmap& decoded_image) {
-  user_photo_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
-  SetImageFromCamera(user_photo_, user_photo_data_.get());
-}
-
-void ChangePictureHandler::OnDecodeImageFailed() {
-  NOTREACHED() << "Failed to decode PNG image from WebUI";
-}
-
-const user_manager::User* ChangePictureHandler::GetUser() {
-  Profile* profile = Profile::FromWebUI(web_ui());
-  const user_manager::User* user =
-      ProfileHelper::Get()->GetUserByProfile(profile);
-  if (!user)
-    return user_manager::UserManager::Get()->GetActiveUser();
-  return user;
-}
-
-}  // namespace settings
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h b/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h
deleted file mode 100644
index 275a877..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h
+++ /dev/null
@@ -1,136 +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.
-
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_CHANGE_PICTURE_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_CHANGE_PICTURE_HANDLER_H_
-
-#include "base/memory/weak_ptr.h"
-#include "base/scoped_observation.h"
-#include "chrome/browser/ash/camera_presence_notifier.h"
-#include "chrome/browser/ash/login/users/avatar/user_image_file_selector.h"
-#include "chrome/browser/image_decoder/image_decoder.h"
-#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
-#include "components/user_manager/user_manager.h"
-#include "ui/gfx/image/image_skia.h"
-
-namespace user_manager {
-class User;
-}
-
-namespace chromeos {
-
-namespace settings {
-
-// ChromeOS user image settings page UI handler.
-class ChangePictureHandler : public ::settings::SettingsPageUIHandler,
-                             public user_manager::UserManager::Observer,
-                             public ImageDecoder::ImageRequest {
- public:
-  ChangePictureHandler();
-
-  ChangePictureHandler(const ChangePictureHandler&) = delete;
-  ChangePictureHandler& operator=(const ChangePictureHandler&) = delete;
-
-  ~ChangePictureHandler() override;
-
-  // WebUIMessageHandler implementation.
-  void RegisterMessages() override;
-  void OnJavascriptAllowed() override;
-  void OnJavascriptDisallowed() override;
-
- private:
-  friend class ChangePictureHandlerTest;
-
-  // Sends list of available default images to the page.
-  void SendDefaultImages();
-
-  // Sends current selection to the page.
-  void SendSelectedImage();
-
-  // Sends the profile image to the page. If |should_select| is true then
-  // the profile image element is selected.
-  void SendProfileImage(const gfx::ImageSkia& image, bool should_select);
-
-  // Starts profile image update and shows the last downloaded profile image,
-  // if any, on the page. Shouldn't be called before |SendProfileImage|.
-  void UpdateProfileImage();
-
-  // Sends the previous user image from camera or file to the page.
-  void SendOldImage(std::string&& image_url);
-
-  // Updates UI with camera presence state.
-  void SetCameraPresent(bool present);
-
-  // Opens a file selection dialog to choose user image from file.
-  void HandleChooseFile(const base::Value::List& args);
-
-  // Handles photo taken with WebRTC UI.
-  void HandlePhotoTaken(const base::Value::List& args);
-
-  // Handles 'discard-photo' button click.
-  void HandleDiscardPhoto(const base::Value::List& args);
-
-  // Gets the list of available user images and sends it to the page.
-  void HandleGetAvailableImages(const base::Value::List& args);
-
-  // Handles page initialized event.
-  void HandlePageInitialized(const base::Value::List& args);
-
-  // Selects one of the available images as user's.
-  void HandleSelectImage(const base::Value::List& args);
-
-  // Requests the currently selected image.
-  void HandleRequestSelectedImage(const base::Value::List& args);
-
-  void FileSelected(const base::FilePath& path);
-
-  void FileSelectionCanceled();
-
-  // user_manager::UserManager::Observer implementation.
-  void OnUserImageChanged(const user_manager::User& user) override;
-  void OnUserProfileImageUpdated(const user_manager::User& user,
-                                 const gfx::ImageSkia& profile_image) override;
-
-  // Sets user image to photo taken from camera.
-  void SetImageFromCamera(const gfx::ImageSkia& photo,
-                          base::RefCountedBytes* image_bytes);
-
-  // Overriden from ImageDecoder::ImageRequest:
-  void OnImageDecoded(const SkBitmap& decoded_image) override;
-  void OnDecodeImageFailed() override;
-
-  // Returns user related to current WebUI. If this user doesn't exist,
-  // returns active user.
-  const user_manager::User* GetUser();
-
-  // Previous user image from camera/file and its data URL.
-  gfx::ImageSkia previous_image_;
-  scoped_refptr<base::RefCountedBytes> previous_image_bytes_;
-  user_manager::UserImage::ImageFormat previous_image_format_ =
-      user_manager::UserImage::FORMAT_UNKNOWN;
-
-  // Index of the previous user image.
-  int previous_image_index_;
-
-  // Last user photo, if taken.
-  gfx::ImageSkia user_photo_;
-
-  // Data for |user_photo_|.
-  scoped_refptr<base::RefCountedBytes> user_photo_data_;
-
-  base::ScopedObservation<user_manager::UserManager,
-                          user_manager::UserManager::Observer>
-      user_manager_observation_{this};
-
-  ash::CameraPresenceNotifier camera_presence_notifier_;
-
-  std::unique_ptr<ash::UserImageFileSelector> user_image_file_selector_;
-
-  base::WeakPtrFactory<ChangePictureHandler> weak_ptr_factory_{this};
-};
-
-}  // namespace settings
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_CHANGE_PICTURE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/change_picture_handler_unittest.cc
deleted file mode 100644
index bc0832c..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler_unittest.cc
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h"
-
-#include <memory>
-
-#include "base/files/file_path.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/values.h"
-#include "chrome/browser/ash/login/users/avatar/user_image_manager.h"
-#include "chrome/browser/ash/login/users/default_user_image/default_user_images.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/ash/web_applications/personalization_app/mock_personalization_app_manager.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager_factory.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile_manager.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
-#include "content/public/browser/audio_service.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_task_environment.h"
-#include "content/public/test/test_web_ui.h"
-#include "services/audio/public/cpp/sounds/sounds_manager.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-
-namespace chromeos {
-namespace settings {
-
-namespace {
-
-std::unique_ptr<KeyedService> MakeMockPersonalizationAppManager(
-    content::BrowserContext* context) {
-  return std::make_unique<::testing::NiceMock<
-      ::ash::personalization_app::MockPersonalizationAppManager>>();
-}
-
-}  // namespace
-
-class ChangePictureHandlerTest : public testing::Test {
- public:
-  ChangePictureHandlerTest()
-      : profile_manager_(TestingBrowserProcess::GetGlobal()),
-        user_manager_enabler_(std::make_unique<ash::FakeChromeUserManager>()) {}
-  ~ChangePictureHandlerTest() override = default;
-
-  void SetUp() override {
-    audio::SoundsManager::Create(content::GetAudioServiceStreamFactoryBinder());
-
-    ASSERT_TRUE(profile_manager_.SetUp());
-    account_id_ = AccountId::FromUserEmail("lala@example.com");
-
-    user_manager::User* user = GetFakeUserManager()->AddUser(account_id_);
-
-    testing_profile_ = profile_manager_.CreateTestingProfile(
-        account_id_.GetUserEmail(),
-        {{ash::personalization_app::PersonalizationAppManagerFactory::
-              GetInstance(),
-          base::BindRepeating(&MakeMockPersonalizationAppManager)}});
-
-    ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
-                                                            testing_profile_);
-
-    // Note that user profiles are created after user login in reality.
-    GetFakeUserManager()->LoginUser(account_id_);
-    GetFakeUserManager()->UserLoggedIn(account_id_, user->username_hash(),
-                                       /*browser_restart=*/false,
-                                       /*is_child=*/false);
-
-    web_ui_ = std::make_unique<content::TestWebUI>();
-    web_contents_ = content::WebContents::Create(
-        content::WebContents::CreateParams(testing_profile_));
-    web_ui_->set_web_contents(web_contents_.get());
-
-    handler_ = std::make_unique<ChangePictureHandler>();
-    handler_->set_web_ui(web_ui_.get());
-    handler_->AllowJavascript();
-    handler_->RegisterMessages();
-
-    request_ = handler_.get();
-  }
-
-  void TearDown() override {
-    request_ = nullptr;
-    handler_.reset();
-    web_contents_.reset();
-    web_ui_.reset();
-    GetFakeUserManager()->Shutdown();
-    testing_profile_ = nullptr;
-    profile_manager_.DeleteAllTestingProfiles();
-    audio::SoundsManager::Shutdown();
-  }
-
-  content::TestWebUI* web_ui() { return web_ui_.get(); }
-
-  ash::FakeChromeUserManager* GetFakeUserManager() const {
-    return static_cast<ash::FakeChromeUserManager*>(
-        user_manager::UserManager::Get());
-  }
-
-  const base::HistogramTester& histogram_tester() const {
-    return histogram_tester_;
-  }
-
-  void SelectNewDefaultImage(int default_image_index) {
-    base::Value::List args;
-    args.Append(
-        default_user_image::GetDefaultImageUrl(default_image_index).spec());
-    args.Append("default");
-
-    web_ui_->HandleReceivedMessage("selectImage", args);
-  }
-
-  void SelectProfileImage() {
-    base::Value::List args;
-    args.Append("empty url");
-    args.Append("profile");
-
-    web_ui_->HandleReceivedMessage("selectImage", args);
-  }
-
-  void SelectImageFromFile(const base::FilePath& path) {
-    handler_->FileSelected(path);
-  }
-
-  void CancelFileSelection() { handler_->FileSelectionCanceled(); }
-
-  void OnCameraImageDecoded() {
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(1, 1);
-
-    std::vector<unsigned char> data;
-    data.push_back('a');
-    handler_->user_photo_data_ = base::RefCountedBytes::TakeVector(&data);
-
-    request_->OnImageDecoded(bitmap);
-  }
-
-  ash::UserImageManager* GetUserImageManager() {
-    return GetFakeUserManager()->GetUserImageManager(account_id_);
-  }
-
-  void ResetHandler() { handler_.reset(); }
-
-  ChangePictureHandler* handler() { return handler_.get(); }
-
-  ::testing::NiceMock<
-      ::ash::personalization_app::MockPersonalizationAppManager>*
-  MockPersonalizationAppManager() {
-    return static_cast<::testing::NiceMock<
-        ::ash::personalization_app::MockPersonalizationAppManager>*>(
-        ::ash::personalization_app::PersonalizationAppManagerFactory::
-            GetForBrowserContext(
-                web_ui()->GetWebContents()->GetBrowserContext()));
-  }
-
- private:
-  content::BrowserTaskEnvironment task_environment_{
-      content::BrowserTaskEnvironment::REAL_IO_THREAD};
-  std::unique_ptr<content::TestWebUI> web_ui_;
-  std::unique_ptr<content::WebContents> web_contents_;
-  std::unique_ptr<ChangePictureHandler> handler_;
-  base::HistogramTester histogram_tester_;
-  AccountId account_id_;
-  TestingProfile* testing_profile_;
-  TestingProfileManager profile_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-  ImageDecoder::ImageRequest* request_;
-};
-
-TEST_F(ChangePictureHandlerTest,
-       ShouldSendUmaMetricWhenNewDefaultImageIsSelected) {
-  const int default_image_index =
-      default_user_image::GetRandomDefaultImageIndex();
-  SelectNewDefaultImage(default_image_index);
-
-  auto* user_image_manager = GetUserImageManager();
-
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(default_image_index), 1);
-}
-
-TEST_F(ChangePictureHandlerTest,
-       ShouldNotSendUmaMetricWhenDefaultImageIsReselected) {
-  const int default_image_index =
-      default_user_image::GetRandomDefaultImageIndex();
-  auto* user_image_manager = GetUserImageManager();
-
-  SelectNewDefaultImage(default_image_index);
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(default_image_index), 1);
-
-  // Selecting the same default image should not log another impression.
-  SelectNewDefaultImage(default_image_index);
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(default_image_index), 1);
-}
-
-TEST_F(ChangePictureHandlerTest, ShoulSendUmaMetricWhenProfileImageIsSelected) {
-  const int default_image_index =
-      default_user_image::GetRandomDefaultImageIndex();
-  auto* user_image_manager = GetUserImageManager();
-
-  // User selects a new default image.
-  SelectNewDefaultImage(default_image_index);
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(default_image_index), 1);
-
-  // User selects the profile image.
-  SelectProfileImage();
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(
-          user_manager::User::USER_IMAGE_PROFILE),
-      1);
-}
-
-TEST_F(ChangePictureHandlerTest,
-       ShoulNotSendUmaMetricWhenProfileImageIsReselected) {
-  auto* user_image_manager = GetUserImageManager();
-  // User has profile image by default, thus reselecting profile does not log an
-  // impression
-  SelectProfileImage();
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(
-          user_manager::User::USER_IMAGE_PROFILE),
-      0);
-}
-
-TEST_F(ChangePictureHandlerTest,
-       ShouldSendUmaMetricWhenImageIsSelectedFromFile) {
-  auto* user_image_manager = GetUserImageManager();
-
-  const base::FilePath base_file_path("/this/is/a/test/directory/Base Name");
-  const base::FilePath dir_path = base_file_path.AppendASCII("dir1");
-  const base::FilePath file_path = dir_path.AppendASCII("file1.txt");
-  SelectImageFromFile(file_path);
-
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      user_image_manager->ImageIndexToHistogramIndex(
-          user_manager::User::USER_IMAGE_EXTERNAL),
-      1);
-}
-
-TEST_F(ChangePictureHandlerTest, ShouldSendUmaMetricWhenCameraImageIsDecoded) {
-  // Camera image is decoded
-  OnCameraImageDecoded();
-  histogram_tester().ExpectBucketCount(
-      ash::UserImageManager::kUserImageChangedHistogramName,
-      default_user_image::kHistogramImageFromCamera, 1);
-}
-
-TEST_F(ChangePictureHandlerTest,
-       ShouldSelectTheCurrentUserImageIfFileSelectionIsCanceled) {
-  // keep the current call size so we can check what happened after our test
-  // method call.
-  auto number_of_calls_before_cancel = web_ui()->call_data().size();
-  CancelFileSelection();
-  // reset back to previous profile image.
-  EXPECT_EQ(web_ui()
-                ->call_data()
-                .at(number_of_calls_before_cancel)
-                ->arg1()
-                ->GetString(),
-            "profile-image-changed");
-}
-
-TEST_F(ChangePictureHandlerTest, CallsMaybeStartHatsTimerOnDestruction) {
-  EXPECT_CALL(
-      *MockPersonalizationAppManager(),
-      MaybeStartHatsTimer(::ash::personalization_app::HatsSurveyType::kAvatar))
-      .Times(1);
-
-  ResetHandler();
-}
-
-TEST_F(ChangePictureHandlerTest,
-       DoesNotCallMaybeStartHatsTimerOnDestructionIfJavascriptDisallowed) {
-  handler()->DisallowJavascript();
-
-  EXPECT_CALL(
-      *MockPersonalizationAppManager(),
-      MaybeStartHatsTimer(::ash::personalization_app::HatsSurveyType::kAvatar))
-      .Times(0);
-
-  ResetHandler();
-}
-
-}  // namespace settings
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
index 3fe3d92..7e2c220 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
@@ -79,7 +79,7 @@
   kAudio = 408,
 
   // Personalization section.
-  kChangePicture = 500,
+  // 500 was used for kChangePicture. Do not reuse.
   // 501 was used for kAmbientMode. Do not reuse.
   // Note: Value 502 was for deprecated kAmbientModePhotos. Do not reuse.
   // 503 was used for kAmbientModeGooglePhotosAlbum. Do not reuse.
@@ -205,7 +205,6 @@
 
 // Personalization section.
 const string kPersonalizationSectionPath = "personalization";
-const string kChangePictureSubpagePath = "changePicture";
 
 // Search and Assistant section.
 const string kSearchAndAssistantSectionPath = "osSearch";
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/routes_util.cc b/chrome/browser/ui/webui/settings/chromeos/constants/routes_util.cc
index 26201d1..4f61db7a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/routes_util.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/routes_util.cc
@@ -60,7 +60,6 @@
 
       // Personalization section.
       chromeos::settings::mojom::kPersonalizationSectionPath,
-      chromeos::settings::mojom::kChangePictureSubpagePath,
 
       // Search and Assistant section.
       chromeos::settings::mojom::kSearchAndAssistantSectionPath,
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index e5b6a1c..756a56b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -144,7 +144,7 @@
   kOpenWallpaper = 500,
   // 501 was used for kAmbientModeOnOff. Do not reuse.
   // 502 was used for kAmbientModeSource. Do not reuse.
-  kChangeDeviceAccountImage = 503,
+  // 503 was used for kChangeDeviceAccountImage. Do not reuse.
   // Note: Values 504, 505, and 506 were for deprecated
   // kAmbientModeUpdatePhotosContainers, kDarkModeOnOff and
   // kDarkModeThemed respectively.
diff --git a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
index d4669efa..91bf454 100644
--- a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
@@ -8,7 +8,6 @@
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
-#include "chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/personalization_hub_handler.h"
 #include "chrome/common/chrome_features.h"
@@ -40,17 +39,6 @@
        {.setting = mojom::Setting::kOpenWallpaper},
        {IDS_OS_SETTINGS_TAG_CHANGE_WALLPAPER_ALT1,
         IDS_OS_SETTINGS_TAG_CHANGE_WALLPAPER_ALT2, SearchConcept::kAltTagEnd}},
-      {IDS_OS_SETTINGS_TAG_CHANGE_DEVICE_ACCOUNT_IMAGE,
-       mojom::kChangePictureSubpagePath,
-       mojom::SearchResultIcon::kAvatar,
-       mojom::SearchResultDefaultRank::kMedium,
-       mojom::SearchResultType::kSetting,
-       {.setting = mojom::Setting::kChangeDeviceAccountImage},
-       {IDS_OS_SETTINGS_TAG_CHANGE_DEVICE_ACCOUNT_IMAGE_ALT1,
-        IDS_OS_SETTINGS_TAG_CHANGE_DEVICE_ACCOUNT_IMAGE_ALT2,
-        IDS_OS_SETTINGS_TAG_CHANGE_DEVICE_ACCOUNT_IMAGE_ALT3,
-        IDS_OS_SETTINGS_TAG_CHANGE_DEVICE_ACCOUNT_IMAGE_ALT4,
-        SearchConcept::kAltTagEnd}},
   });
   return *tags;
 }
@@ -115,8 +103,6 @@
 }
 
 void PersonalizationSection::AddHandlers(content::WebUI* web_ui) {
-  web_ui->AddMessageHandler(
-      std::make_unique<chromeos::settings::ChangePictureHandler>());
   if (ash::features::IsPersonalizationHubEnabled()) {
     web_ui->AddMessageHandler(
         std::make_unique<chromeos::settings::PersonalizationHubHandler>());
@@ -148,14 +134,6 @@
 void PersonalizationSection::RegisterHierarchy(
     HierarchyGenerator* generator) const {
   generator->RegisterTopLevelSetting(mojom::Setting::kOpenWallpaper);
-
-  // Change picture.
-  generator->RegisterTopLevelSubpage(
-      IDS_OS_SETTINGS_CHANGE_PICTURE_TITLE, mojom::Subpage::kChangePicture,
-      mojom::SearchResultIcon::kAvatar, mojom::SearchResultDefaultRank::kMedium,
-      mojom::kChangePictureSubpagePath);
-  generator->RegisterNestedSetting(mojom::Setting::kChangeDeviceAccountImage,
-                                   mojom::Subpage::kChangePicture);
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
index 5c7e1c0..c488ae68 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
@@ -166,46 +166,44 @@
   ResolveJavascriptCallback(callback_id, GetInterceptionParametersValue());
 }
 
-base::Value DiceWebSigninInterceptHandler::GetAccountInfoValue(
+base::Value::Dict DiceWebSigninInterceptHandler::GetAccountInfoValue(
     const AccountInfo& info) {
   std::string picture_url_to_load =
       info.account_image.IsEmpty()
           ? profiles::GetPlaceholderAvatarIconUrl()
           : webui::GetBitmapDataUrl(info.account_image.AsBitmap());
-  base::Value account_info_value(base::Value::Type::DICTIONARY);
-  account_info_value.SetBoolKey("isManaged", IsManaged(info));
-  account_info_value.SetStringKey("pictureUrl", picture_url_to_load);
+  base::Value::Dict account_info_value;
+  account_info_value.Set("isManaged", IsManaged(info));
+  account_info_value.Set("pictureUrl", picture_url_to_load);
   return account_info_value;
 }
 
-base::Value DiceWebSigninInterceptHandler::GetInterceptionParametersValue() {
-  base::Value parameters(base::Value::Type::DICTIONARY);
-  parameters.SetStringKey("headerText", GetHeaderText());
-  parameters.SetStringKey("bodyTitle", GetBodyTitle());
-  parameters.SetStringKey("bodyText", GetBodyText());
-  parameters.SetStringKey("confirmButtonLabel", GetConfirmButtonLabel());
-  parameters.SetStringKey("cancelButtonLabel", GetCancelButtonLabel());
-  parameters.SetStringKey("managedDisclaimerText", GetManagedDisclaimerText());
-  parameters.SetBoolKey("showGuestOption",
-                        bubble_parameters_.show_guest_option);
-  parameters.SetKey("interceptedAccount",
-                    GetAccountInfoValue(intercepted_account()));
-  parameters.SetKey("primaryAccount", GetAccountInfoValue(primary_account()));
-  parameters.SetStringKey("interceptedProfileColor",
-                          color_utils::SkColorToRgbaString(
-                              bubble_parameters_.profile_highlight_color));
-  parameters.SetStringKey(
-      "primaryProfileColor",
-      color_utils::SkColorToRgbaString(
-          GetProfileHighlightColor(Profile::FromWebUI(web_ui()))));
-  parameters.SetBoolKey("useV2Design", GetShouldUseV2Design());
-  parameters.SetBoolKey("showManagedDisclaimer",
-                        bubble_parameters_.show_managed_disclaimer);
+base::Value::Dict
+DiceWebSigninInterceptHandler::GetInterceptionParametersValue() {
+  base::Value::Dict parameters;
+  parameters.Set("headerText", GetHeaderText());
+  parameters.Set("bodyTitle", GetBodyTitle());
+  parameters.Set("bodyText", GetBodyText());
+  parameters.Set("confirmButtonLabel", GetConfirmButtonLabel());
+  parameters.Set("cancelButtonLabel", GetCancelButtonLabel());
+  parameters.Set("managedDisclaimerText", GetManagedDisclaimerText());
+  parameters.Set("showGuestOption", bubble_parameters_.show_guest_option);
+  parameters.Set("interceptedAccount",
+                 GetAccountInfoValue(intercepted_account()));
+  parameters.Set("primaryAccount", GetAccountInfoValue(primary_account()));
+  parameters.Set("interceptedProfileColor",
+                 color_utils::SkColorToRgbaString(
+                     bubble_parameters_.profile_highlight_color));
+  parameters.Set("primaryProfileColor",
+                 color_utils::SkColorToRgbaString(
+                     GetProfileHighlightColor(Profile::FromWebUI(web_ui()))));
+  parameters.Set("useV2Design", GetShouldUseV2Design());
+  parameters.Set("showManagedDisclaimer",
+                 bubble_parameters_.show_managed_disclaimer);
 
-  parameters.SetStringKey(
-      "headerTextColor",
-      color_utils::SkColorToRgbaString(GetProfileForegroundTextColor(
-          bubble_parameters_.profile_highlight_color)));
+  parameters.Set("headerTextColor",
+                 color_utils::SkColorToRgbaString(GetProfileForegroundTextColor(
+                     bubble_parameters_.profile_highlight_color)));
   return parameters;
 }
 
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
index f9526a3..e6c807b 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
@@ -52,8 +52,8 @@
   void HandlePageLoaded(const base::Value::List& args);
 
   // Gets the values sent to javascript.
-  base::Value GetAccountInfoValue(const AccountInfo& info);
-  base::Value GetInterceptionParametersValue();
+  base::Value::Dict GetAccountInfoValue(const AccountInfo& info);
+  base::Value::Dict GetInterceptionParametersValue();
 
   // The dialog string is different when the device is managed. This function
   // returns whether the version for managed devices should be used.
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
index 682ac73..fbd2959 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
@@ -204,7 +204,7 @@
   DiceWebSigninInterceptHandlerTest()
       : profile_manager_(TestingBrowserProcess::GetGlobal()) {}
 
-  base::Value GetInterceptionParameters() {
+  base::Value::Dict GetInterceptionParameters() {
     Profile* profile = profile_manager_.CreateTestingProfile("Primary Profile");
     // Resetting the platform authority to NONE, as not all platforms have the
     // same value in browser tests. See https://crbug.com/1324377.
@@ -230,17 +230,15 @@
   void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); }
 
  protected:
-  void ExpectStringsMatch(const base::Value& parameters,
+  void ExpectStringsMatch(const base::Value::Dict& parameters,
                           const BubbleStrings& expected_strings) {
-    EXPECT_EQ(*parameters.FindStringKey("headerText"),
+    EXPECT_EQ(*parameters.FindString("headerText"),
               expected_strings.header_text);
-    EXPECT_EQ(*parameters.FindStringKey("bodyTitle"),
-              expected_strings.body_title);
-    EXPECT_EQ(*parameters.FindStringKey("bodyText"),
-              expected_strings.body_text);
-    EXPECT_EQ(*parameters.FindStringKey("confirmButtonLabel"),
+    EXPECT_EQ(*parameters.FindString("bodyTitle"), expected_strings.body_title);
+    EXPECT_EQ(*parameters.FindString("bodyText"), expected_strings.body_text);
+    EXPECT_EQ(*parameters.FindString("confirmButtonLabel"),
               expected_strings.confirm_button_label);
-    EXPECT_EQ(*parameters.FindStringKey("cancelButtonLabel"),
+    EXPECT_EQ(*parameters.FindString("cancelButtonLabel"),
               expected_strings.cancel_button_label);
   }
 
@@ -252,17 +250,17 @@
 };
 
 TEST_P(DiceWebSigninInterceptHandlerTest, CheckStrings) {
-  base::Value parameters = GetInterceptionParameters();
+  base::Value::Dict parameters = GetInterceptionParameters();
 
-  EXPECT_FALSE(*parameters.FindBoolKey("useV2Design"));
+  EXPECT_FALSE(*parameters.FindBool("useV2Design"));
   ExpectStringsMatch(parameters, GetParam().expected_strings.Run());
 }
 
 TEST_P(DiceWebSigninInterceptHandlerTest, CheckStrings_V2) {
   base::test::ScopedFeatureList feature_list{kSigninInterceptBubbleV2};
-  base::Value parameters = GetInterceptionParameters();
+  base::Value::Dict parameters = GetInterceptionParameters();
 
-  EXPECT_TRUE(*parameters.FindBoolKey("useV2Design"));
+  EXPECT_TRUE(*parameters.FindBool("useV2Design"));
   ExpectStringsMatch(parameters, GetParam().expected_strings_v2.Run());
 }
 
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
index 6bc3be0..8dcdf55 100644
--- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
+++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
@@ -256,11 +256,11 @@
   FireWebUIListener("on-profile-info-changed", GetProfileInfoValue());
 }
 
-base::Value EnterpriseProfileWelcomeHandler::GetProfileInfoValue() {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetStringKey("backgroundColor", color_utils::SkColorToRgbaString(
-                                           GetHighlightColor(profile_color_)));
-  dict.SetStringKey("pictureUrl", GetPictureUrl());
+base::Value::Dict EnterpriseProfileWelcomeHandler::GetProfileInfoValue() {
+  base::Value::Dict dict;
+  dict.Set("backgroundColor",
+           color_utils::SkColorToRgbaString(GetHighlightColor(profile_color_)));
+  dict.Set("pictureUrl", GetPictureUrl());
 
   std::string title =
       l10n_util::GetStringUTF8(IDS_ENTERPRISE_PROFILE_WELCOME_TITLE);
@@ -271,54 +271,51 @@
 
   switch (type_) {
     case EnterpriseProfileWelcomeUI::ScreenType::kEntepriseAccountSyncEnabled:
-      dict.SetBoolKey("showEnterpriseBadge", true);
+      dict.Set("showEnterpriseBadge", true);
       subtitle = GetManagedAccountTitle(entry, domain_name_);
       enterprise_info = l10n_util::GetStringUTF8(
           IDS_ENTERPRISE_PROFILE_WELCOME_MANAGED_DESCRIPTION_WITH_SYNC);
-      dict.SetStringKey(
-          "proceedLabel",
-          l10n_util::GetStringUTF8(IDS_PROFILE_PICKER_IPH_NEXT_BUTTON_LABEL));
+      dict.Set("proceedLabel", l10n_util::GetStringUTF8(
+                                   IDS_PROFILE_PICKER_IPH_NEXT_BUTTON_LABEL));
       break;
     case EnterpriseProfileWelcomeUI::ScreenType::kEntepriseAccountSyncDisabled:
-      dict.SetBoolKey("showEnterpriseBadge", true);
+      dict.Set("showEnterpriseBadge", true);
       subtitle = GetManagedAccountTitle(entry, domain_name_);
       enterprise_info = l10n_util ::GetStringUTF8(
           IDS_ENTERPRISE_PROFILE_WELCOME_MANAGED_DESCRIPTION_WITHOUT_SYNC);
-      dict.SetStringKey("proceedLabel", l10n_util::GetStringUTF8(IDS_DONE));
+      dict.Set("proceedLabel", l10n_util::GetStringUTF8(IDS_DONE));
       break;
     case EnterpriseProfileWelcomeUI::ScreenType::kConsumerAccountSyncDisabled:
-      dict.SetBoolKey("showEnterpriseBadge", false);
+      dict.Set("showEnterpriseBadge", false);
       subtitle = GetManagedDeviceTitle();
       enterprise_info =
           l10n_util::GetStringUTF8(IDS_SYNC_DISABLED_CONFIRMATION_DETAILS);
-      dict.SetStringKey("proceedLabel", l10n_util::GetStringUTF8(IDS_DONE));
+      dict.Set("proceedLabel", l10n_util::GetStringUTF8(IDS_DONE));
       break;
     case EnterpriseProfileWelcomeUI::ScreenType::kEnterpriseAccountCreation:
       title = l10n_util::GetStringUTF8(
           profile_creation_required_by_policy_
               ? IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE
               : IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE);
-      dict.SetBoolKey("showEnterpriseBadge", false);
+      dict.Set("showEnterpriseBadge", false);
       subtitle = GetManagedAccountTitleWithEmail(entry, domain_name_, email_);
       enterprise_info = l10n_util::GetStringUTF8(
           IDS_ENTERPRISE_PROFILE_WELCOME_MANAGED_DESCRIPTION_WITH_SYNC);
-      dict.SetStringKey(
-          "proceedLabel",
-          l10n_util::GetStringUTF8(
-              profile_creation_required_by_policy_
-                  ? IDS_ENTERPRISE_PROFILE_WELCOME_CREATE_PROFILE_BUTTON
-                  : IDS_WELCOME_SIGNIN_VIEW_SIGNIN));
+      dict.Set("proceedLabel",
+               l10n_util::GetStringUTF8(
+                   profile_creation_required_by_policy_
+                       ? IDS_ENTERPRISE_PROFILE_WELCOME_CREATE_PROFILE_BUTTON
+                       : IDS_WELCOME_SIGNIN_VIEW_SIGNIN));
 #if !BUILDFLAG(IS_CHROMEOS)
-      dict.SetBoolKey(
-          "checkLinkDataCheckboxByDefault",
-          show_link_data_option_ &&
-              g_browser_process->local_state()->GetBoolean(
-                  prefs::kEnterpriseProfileCreationKeepBrowsingData));
+      dict.Set("checkLinkDataCheckboxByDefault",
+               show_link_data_option_ &&
+                   g_browser_process->local_state()->GetBoolean(
+                       prefs::kEnterpriseProfileCreationKeepBrowsingData));
 #endif
       break;
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
     case EnterpriseProfileWelcomeUI::ScreenType::kLacrosEnterpriseWelcome:
-      dict.SetBoolKey("showEnterpriseBadge", true);
+      dict.Set("showEnterpriseBadge", true);
       enterprise_info =
           GetLacrosFirstRunManagedAccountInfo(entry, domain_name_);
       [[fallthrough]];
@@ -326,18 +323,18 @@
       title = GetLacrosWelcomeTitle();
       subtitle = l10n_util::GetStringFUTF8(
           IDS_PRIMARY_PROFILE_FIRST_RUN_SUBTITLE, email_);
-      dict.SetStringKey("proceedLabel",
-                        l10n_util::GetStringUTF8(
-                            IDS_PRIMARY_PROFILE_FIRST_RUN_NEXT_BUTTON_LABEL));
+      dict.Set("proceedLabel",
+               l10n_util::GetStringUTF8(
+                   IDS_PRIMARY_PROFILE_FIRST_RUN_NEXT_BUTTON_LABEL));
       show_cancel_button = false;
       break;
 #endif
   }
 
-  dict.SetStringKey("title", title);
-  dict.SetStringKey("subtitle", subtitle);
-  dict.SetStringKey("enterpriseInfo", enterprise_info);
-  dict.SetBoolKey("showCancelButton", show_cancel_button);
+  dict.Set("title", title);
+  dict.Set("subtitle", subtitle);
+  dict.Set("enterpriseInfo", enterprise_info);
+  dict.Set("showCancelButton", show_cancel_button);
 
   return dict;
 }
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
index 6517ea4..af82826a 100644
--- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
+++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
@@ -92,7 +92,7 @@
   void UpdateProfileInfo(const base::FilePath& profile_path);
 
   // Computes the profile info (avatar and colors) to be sent to the WebUI.
-  base::Value GetProfileInfoValue();
+  base::Value::Dict GetProfileInfoValue();
 
   // Returns the ProfilesAttributesEntry associated with the current profile.
   ProfileAttributesEntry* GetProfileEntry() const;
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler.cc b/chrome/browser/ui/webui/signin/inline_login_handler.cc
index 8a7d4e0..f15d418 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler.cc
@@ -158,7 +158,7 @@
   params.Set("readOnlyEmail", !read_only_email.empty());
 
   SetExtraInitParams(params);
-  FireWebUIListener("load-auth-extension", base::Value(std::move(params)));
+  FireWebUIListener("load-auth-extension", params);
 }
 
 void InlineLoginHandler::HandleCompleteLoginMessage(
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
index 413d672..ef3f0de 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_chromeos.cc
@@ -105,20 +105,19 @@
   return default_icon.GetRepresentation(1.0f).GetBitmap();
 }
 
-base::Value GaiaAccountToValue(const ::account_manager::Account& account,
-                               const AccountInfo& account_info) {
+base::Value::Dict GaiaAccountToValue(const ::account_manager::Account& account,
+                                     const AccountInfo& account_info) {
   DCHECK_EQ(account.key.account_type(), account_manager::AccountType::kGaia);
   DCHECK(!account_info.IsEmpty());
 
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetKey(kAccountKeyId, base::Value(account.key.id()));
-  dict.SetKey(kAccountKeyEmail, base::Value(account.raw_email));
-  dict.SetKey(kAccountKeyFullName, base::Value(account_info.full_name));
-  dict.SetKey(kAccountKeyImage,
-              base::Value(webui::GetBitmapDataUrl(
-                  account_info.account_image.IsEmpty()
-                      ? GetDefaultAccountIcon()
-                      : account_info.account_image.AsBitmap())));
+  base::Value::Dict dict;
+  dict.Set(kAccountKeyId, account.key.id());
+  dict.Set(kAccountKeyEmail, account.raw_email);
+  dict.Set(kAccountKeyFullName, account_info.full_name);
+  dict.Set(kAccountKeyImage, webui::GetBitmapDataUrl(
+                                 account_info.account_image.IsEmpty()
+                                     ? GetDefaultAccountIcon()
+                                     : account_info.account_image.AsBitmap()));
 
   return dict;
 }
@@ -270,18 +269,16 @@
 
 void InlineLoginHandlerChromeOS::SetExtraInitParams(base::Value::Dict& params) {
   const GaiaUrls* const gaia_urls = GaiaUrls::GetInstance();
-  params.Set("clientId", base::Value(gaia_urls->oauth2_chrome_client_id()));
+  params.Set("clientId", gaia_urls->oauth2_chrome_client_id());
 
   const GURL& url = gaia_urls->embedded_setup_chromeos_url(2U);
-  params.Set("gaiaPath", base::Value(url.path().substr(1)));
+  params.Set("gaiaPath", url.path().substr(1));
 
-  params.Set(
-      "platformVersion",
-      base::Value(version_loader::GetVersion(version_loader::VERSION_SHORT)));
-  params.Set("constrained", base::Value("1"));
-  params.Set("flow",
-             base::Value(GetInlineLoginFlowName(Profile::FromWebUI(web_ui()),
-                                                params.FindString("email"))));
+  params.Set("platformVersion",
+             version_loader::GetVersion(version_loader::VERSION_SHORT));
+  params.Set("constrained", "1");
+  params.Set("flow", GetInlineLoginFlowName(Profile::FromWebUI(web_ui()),
+                                            params.FindString("email")));
   params.Set("dontResizeNonEmbeddedPages", true);
   params.Set("enableGaiaActionButtons", true);
 
@@ -385,7 +382,7 @@
   params.Set("deviceType", ui::GetChromeOSDeviceName());
   params.Set("signinBlockedByPolicy", !hosted_domain.empty() ? true : false);
 
-  FireWebUIListener("show-signin-error-page", base::Value(std::move(params)));
+  FireWebUIListener("show-signin-error-page", params);
 }
 
 void InlineLoginHandlerChromeOS::ShowIncognitoAndCloseDialog(
@@ -406,7 +403,7 @@
 void InlineLoginHandlerChromeOS::OnGetAccounts(
     const std::string& callback_id,
     const std::vector<::account_manager::Account>& accounts) {
-  base::ListValue account_emails;
+  base::Value::List account_emails;
   for (const auto& account : accounts) {
     if (account.key.account_type() ==
         ::account_manager::AccountType::kActiveDirectory) {
@@ -417,8 +414,7 @@
     }
   }
 
-  ResolveJavascriptCallback(base::Value(callback_id),
-                            std::move(account_emails));
+  ResolveJavascriptCallback(base::Value(callback_id), account_emails);
 }
 
 void InlineLoginHandlerChromeOS::GetAccountsNotAvailableInArc(
@@ -446,7 +442,7 @@
     const std::string& callback_id,
     const std::vector<::account_manager::Account>& accounts,
     const base::flat_set<account_manager::Account>& arc_accounts) {
-  base::Value result(base::Value::Type::LIST);
+  base::Value::List result;
   auto* identity_manager =
       IdentityManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()));
   for (const auto& account : accounts) {
@@ -462,7 +458,7 @@
       result.Append(GaiaAccountToValue(account, maybe_account_info));
     }
   }
-  ResolveJavascriptCallback(base::Value(callback_id), std::move(result));
+  ResolveJavascriptCallback(base::Value(callback_id), result);
 }
 
 void InlineLoginHandlerChromeOS::MakeAvailableInArcAndCloseDialog(
diff --git a/chrome/browser/ui/webui/signin/profile_customization_handler.cc b/chrome/browser/ui/webui/signin/profile_customization_handler.cc
index bc0ec9cb..00e5937 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_customization_handler.cc
@@ -123,27 +123,24 @@
   FireWebUIListener("on-profile-info-changed", GetProfileInfoValue());
 }
 
-base::Value ProfileCustomizationHandler::GetProfileInfoValue() {
+base::Value::Dict ProfileCustomizationHandler::GetProfileInfoValue() {
   ProfileAttributesEntry* entry = GetProfileEntry();
 
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetStringKey(
-      "backgroundColor",
-      color_utils::SkColorToRgbaString(
-          entry->GetProfileThemeColors().profile_highlight_color));
+  base::Value::Dict dict;
+  dict.Set("backgroundColor",
+           color_utils::SkColorToRgbaString(
+               entry->GetProfileThemeColors().profile_highlight_color));
   const int avatar_icon_size = kAvatarSize * web_ui()->GetDeviceScaleFactor();
   gfx::Image icon =
       profiles::GetSizedAvatarIcon(entry->GetAvatarIcon(avatar_icon_size),
                                    avatar_icon_size, avatar_icon_size);
-  dict.SetStringKey("pictureUrl", webui::GetBitmapDataUrl(icon.AsBitmap()));
-  dict.SetBoolKey("isManaged",
-                  AccountInfo::IsManaged(entry->GetHostedDomain()));
+  dict.Set("pictureUrl", webui::GetBitmapDataUrl(icon.AsBitmap()));
+  dict.Set("isManaged", AccountInfo::IsManaged(entry->GetHostedDomain()));
   std::u16string gaia_name = entry->GetGAIANameToDisplay();
   if (gaia_name.empty())
     gaia_name = entry->GetLocalProfileName();
-  dict.SetStringKey(
-      "welcomeTitle",
-      l10n_util::GetStringFUTF8(IDS_PROFILE_CUSTOMIZATION_WELCOME, gaia_name));
+  dict.Set("welcomeTitle", l10n_util::GetStringFUTF8(
+                               IDS_PROFILE_CUSTOMIZATION_WELCOME, gaia_name));
   return dict;
 }
 
diff --git a/chrome/browser/ui/webui/signin/profile_customization_handler.h b/chrome/browser/ui/webui/signin/profile_customization_handler.h
index 82b30da..4af8192 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_handler.h
+++ b/chrome/browser/ui/webui/signin/profile_customization_handler.h
@@ -60,7 +60,7 @@
   void UpdateProfileInfo(const base::FilePath& profile_path);
 
   // Computes the profile info (avatar and colors) to be sent to the WebUI.
-  base::Value GetProfileInfoValue();
+  base::Value::Dict GetProfileInfoValue();
 
   // Returns the ProfilesAttributesEntry associated with the current profile.
   ProfileAttributesEntry* GetProfileEntry() const;
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index ac46ead..d2bbbd2 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -121,7 +121,7 @@
   }
 }
 
-base::Value GetAutogeneratedProfileThemeInfoValue(
+base::Value::Dict GetAutogeneratedProfileThemeInfoValue(
     int color_id,
     absl::optional<SkColor> color,
     const ui::ColorProvider& color_provider,
@@ -129,26 +129,24 @@
     SkColor active_tab_color,
     SkColor frame_text_color,
     float scale_factor) {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetIntKey("colorId", color_id);
+  base::Value::Dict dict;
+  dict.Set("colorId", color_id);
   if (color.has_value())
-    dict.SetIntKey("color", *color);
-  dict.SetStringKey("themeFrameColor",
-                    color_utils::SkColorToRgbaString(frame_color));
-  dict.SetStringKey("themeShapeColor",
-                    color_utils::SkColorToRgbaString(active_tab_color));
-  dict.SetStringKey("themeFrameTextColor",
-                    color_utils::SkColorToRgbaString(frame_text_color));
+    dict.Set("color", static_cast<int>(*color));
+  dict.Set("themeFrameColor", color_utils::SkColorToRgbaString(frame_color));
+  dict.Set("themeShapeColor",
+           color_utils::SkColorToRgbaString(active_tab_color));
+  dict.Set("themeFrameTextColor",
+           color_utils::SkColorToRgbaString(frame_text_color));
   gfx::Image icon = profiles::GetPlaceholderAvatarIconWithColors(
       /*fill_color=*/frame_color,
       /*stroke_color=*/GetAvatarStrokeColor(color_provider, frame_color),
       kProfileCreationAvatarSize * scale_factor);
-  dict.SetStringKey("themeGenericAvatar",
-                    webui::GetBitmapDataUrl(icon.AsBitmap()));
+  dict.Set("themeGenericAvatar", webui::GetBitmapDataUrl(icon.AsBitmap()));
   return dict;
 }
 
-base::Value CreateDefaultProfileThemeInfo(
+base::Value::Dict CreateDefaultProfileThemeInfo(
     const ui::ColorProvider& color_provider,
     float scale_factor) {
   SkColor frame_color = color_provider.GetColor(ui::kColorFrameActive);
@@ -160,7 +158,7 @@
       active_tab_color, frame_text_color, scale_factor);
 }
 
-base::Value CreateAutogeneratedProfileThemeInfo(
+base::Value::Dict CreateAutogeneratedProfileThemeInfo(
     int color_id,
     SkColor color,
     const ui::ColorProvider& color_provider,
@@ -193,31 +191,31 @@
   }
 }
 
-base::Value CreateProfileEntry(const ProfileAttributesEntry* entry,
-                               int avatar_icon_size) {
-  base::Value profile_entry(base::Value::Type::DICTIONARY);
-  profile_entry.SetKey("profilePath", base::FilePathToValue(entry->GetPath()));
-  profile_entry.SetStringKey("localProfileName", entry->GetLocalProfileName());
-  profile_entry.SetBoolKey(
-      "isSyncing", entry->GetSigninState() ==
-                       SigninState::kSignedInWithConsentedPrimaryAccount);
-  profile_entry.SetBoolKey("needsSignin", entry->IsSigninRequired());
+base::Value::Dict CreateProfileEntry(const ProfileAttributesEntry* entry,
+                                     int avatar_icon_size) {
+  base::Value::Dict profile_entry;
+  profile_entry.Set("profilePath", base::FilePathToValue(entry->GetPath()));
+  profile_entry.Set("localProfileName", entry->GetLocalProfileName());
+  profile_entry.Set("isSyncing",
+                    entry->GetSigninState() ==
+                        SigninState::kSignedInWithConsentedPrimaryAccount);
+  profile_entry.Set("needsSignin", entry->IsSigninRequired());
   // GAIA full name/user name can be empty, if the profile is not signed in to
   // chrome.
-  profile_entry.SetStringKey("gaiaName", entry->GetGAIAName());
-  profile_entry.SetStringKey("userName", entry->GetUserName());
-  profile_entry.SetBoolKey("isManaged",
-                           AccountInfo::IsManaged(entry->GetHostedDomain()));
+  profile_entry.Set("gaiaName", entry->GetGAIAName());
+  profile_entry.Set("userName", entry->GetUserName());
+  profile_entry.Set("isManaged",
+                    AccountInfo::IsManaged(entry->GetHostedDomain()));
   gfx::Image icon =
       profiles::GetSizedAvatarIcon(entry->GetAvatarIcon(avatar_icon_size),
                                    avatar_icon_size, avatar_icon_size);
   std::string icon_url = webui::GetBitmapDataUrl(icon.AsBitmap());
-  profile_entry.SetStringKey("avatarIcon", icon_url);
+  profile_entry.Set("avatarIcon", icon_url);
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  profile_entry.SetBoolKey("isPrimaryLacrosProfile",
-                           Profile::IsMainProfilePath(entry->GetPath()));
+  profile_entry.Set("isPrimaryLacrosProfile",
+                    Profile::IsMainProfilePath(entry->GetPath()));
 #else
-  profile_entry.SetBoolKey("isPrimaryLacrosProfile", false);
+  profile_entry.Set("isPrimaryLacrosProfile", false);
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   return profile_entry;
 }
@@ -598,7 +596,7 @@
     ThemeService* theme_service =
         ThemeServiceFactory::GetForProfile(Profile::FromBrowserContext(
             web_ui()->GetWebContents()->GetBrowserContext()));
-    base::Value profile_dict;
+    base::Value::Dict profile_dict;
     if (theme_service->UsingAutogeneratedTheme()) {
       // We'll never use `profile_dict` for showing the color picker so we can
       // pass in kManuallyPickedColorId to simplify the code.
@@ -611,16 +609,16 @@
           web_ui()->GetWebContents()->GetColorProvider(),
           web_ui()->GetDeviceScaleFactor());
     }
-    ResolveJavascriptCallback(callback_id, std::move(profile_dict));
+    ResolveJavascriptCallback(callback_id, profile_dict);
     return;
   }
 #endif
   chrome_colors::ColorInfo color_info = GenerateNewProfileColor();
-  base::Value dict = CreateAutogeneratedProfileThemeInfo(
+  base::Value::Dict dict = CreateAutogeneratedProfileThemeInfo(
       color_info.id, color_info.color,
       web_ui()->GetWebContents()->GetColorProvider(),
       web_ui()->GetDeviceScaleFactor());
-  ResolveJavascriptCallback(callback_id, std::move(dict));
+  ResolveJavascriptCallback(callback_id, dict);
 }
 
 void ProfilePickerHandler::HandleGetProfileThemeInfo(
@@ -631,7 +629,7 @@
   const base::Value::Dict& user_theme_choice = args[1].GetDict();
   int color_id = user_theme_choice.FindInt("colorId").value();
   absl::optional<SkColor> color = user_theme_choice.FindDouble("color");
-  base::Value dict;
+  base::Value::Dict dict;
   switch (color_id) {
     case kDefaultThemeColorId:
       dict = CreateDefaultProfileThemeInfo(
@@ -650,7 +648,7 @@
           web_ui()->GetDeviceScaleFactor());
       break;
   }
-  ResolveJavascriptCallback(callback_id, std::move(dict));
+  ResolveJavascriptCallback(callback_id, dict);
 }
 
 void ProfilePickerHandler::HandleGetAvailableIcons(
@@ -658,9 +656,8 @@
   AllowJavascript();
   CHECK_EQ(1U, args.size());
   const base::Value& callback_id = args[0];
-  ResolveJavascriptCallback(
-      callback_id,
-      base::Value(profiles::GetCustomProfileAvatarIconsAndLabels()));
+  ResolveJavascriptCallback(callback_id,
+                            profiles::GetCustomProfileAvatarIconsAndLabels());
 }
 
 void ProfilePickerHandler::HandleCreateProfile(const base::Value::List& args) {
@@ -701,8 +698,8 @@
           ->GetProfileAttributesStorage()
           .GetProfileAttributesWithPath(profile_path);
   CHECK(entry);
-  base::Value dict = CreateProfileEntry(entry, avatar_icon_size);
-  ResolveJavascriptCallback(callback_id, std::move(dict));
+  base::Value::Dict dict = CreateProfileEntry(entry, avatar_icon_size);
+  ResolveJavascriptCallback(callback_id, dict);
 }
 
 void ProfilePickerHandler::HandleConfirmProfileSwitch(
@@ -877,16 +874,16 @@
 void ProfilePickerHandler::OnProfileStatisticsReceived(
     const base::FilePath& profile_path,
     profiles::ProfileCategoryStats result) {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetKey("profilePath", base::FilePathToValue(profile_path));
-  base::Value stats(base::Value::Type::DICTIONARY);
+  base::Value::Dict dict;
+  dict.Set("profilePath", base::FilePathToValue(profile_path));
+  base::Value::Dict stats;
   // Categories are defined in |kProfileStatisticsCategories|
   // {"BrowsingHistory", "Passwords", "Bookmarks", "Autofill"}.
   for (const auto& item : result) {
-    stats.SetIntKey(item.category, item.count);
+    stats.Set(item.category, item.count);
   }
-  dict.SetKey("statistics", std::move(stats));
-  FireWebUIListener("profile-statistics-received", std::move(dict));
+  dict.Set("statistics", std::move(stats));
+  FireWebUIListener("profile-statistics-received", dict);
 }
 
 void ProfilePickerHandler::HandleSelectAccountLacros(
@@ -1051,8 +1048,8 @@
   return entries;
 }
 
-base::Value ProfilePickerHandler::GetProfilesList() {
-  base::Value profiles_list(base::Value::Type::LIST);
+base::Value::List ProfilePickerHandler::GetProfilesList() {
+  base::Value::List profiles_list;
   std::vector<ProfileAttributesEntry*> entries = GetProfileAttributes();
   const int avatar_icon_size =
       kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor();
@@ -1218,23 +1215,23 @@
 void ProfilePickerHandler::SendAvailableAccounts(
     std::vector<GetAccountInformationHelper::GetAccountInformationResult>
         accounts) {
-  base::Value accounts_list(base::Value::Type::LIST);
+  base::Value::List accounts_list;
   for (const GetAccountInformationHelper::GetAccountInformationResult& account :
        accounts) {
     // TODO(https://crbug/1226050): Filter out items with no email as items
     // without an email are impossible to use. The email should be always
     // available, unless the mojo connection fails. This requires more robust
     // unit-tests.
-    base::Value account_dict(base::Value::Type::DICTIONARY);
-    account_dict.SetStringKey("gaiaId", account.gaia);
-    account_dict.SetStringKey("email", account.email);
-    account_dict.SetStringKey("name", account.full_name);
+    base::Value::Dict account_dict;
+    account_dict.Set("gaiaId", account.gaia);
+    account_dict.Set("email", account.email);
+    account_dict.Set("name", account.full_name);
     SkBitmap account_bitmap = GetAvailableAccountBitmap(account.account_image);
-    account_dict.SetStringKey("accountImageUrl",
-                              webui::GetBitmapDataUrl(account_bitmap));
+    account_dict.Set("accountImageUrl",
+                     webui::GetBitmapDataUrl(account_bitmap));
     accounts_list.Append(std::move(account_dict));
   }
-  FireWebUIListener("available-accounts-changed", std::move(accounts_list));
+  FireWebUIListener("available-accounts-changed", accounts_list);
 }
 
 void ProfilePickerHandler::OnLacrosSignedInProfileCreated(
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.h b/chrome/browser/ui/webui/signin/profile_picker_handler.h
index eeeae9e..6e2c6408 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.h
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.h
@@ -112,7 +112,7 @@
                                 bool create_shortcut,
                                 Profile* profile);
   void PushProfilesList();
-  base::Value GetProfilesList();
+  base::Value::List GetProfilesList();
   // Adds a profile with `profile_path` to `profiles_order_`.
   void AddProfileToList(const base::FilePath& profile_path);
   // Removes a profile with `profile_path` from `profiles_order_`. Returns
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc b/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc
index 26c21da..7f55ab4 100644
--- a/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_handler.cc
@@ -168,9 +168,9 @@
   GURL picture_gurl_with_options = signin::GetAvatarImageURLWithOptions(
       picture_gurl, kProfileImageSize, false /* no_silhouette */);
 
-  base::Value value(base::Value::Type::DICTIONARY);
-  value.SetKey("src", base::Value(picture_gurl_with_options.spec()));
-  value.SetKey("showEnterpriseBadge", base::Value(info.IsManaged()));
+  base::Value::Dict value;
+  value.Set("src", picture_gurl_with_options.spec());
+  value.Set("showEnterpriseBadge", info.IsManaged());
 
   AllowJavascript();
   FireWebUIListener("account-info-changed", value);
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index 99914ea..4582b55 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -152,13 +152,6 @@
   if (!sent_initial_payload_) {
     sent_initial_payload_ = true;
     int tab_count = 0;
-    int media_tab_count = 0;
-    for (const auto& window : profile_tabs->windows) {
-      tab_count += window->tabs.size();
-      media_tab_count += base::ranges::count_if(
-          window->tabs.begin(), window->tabs.end(),
-          [](const auto& tab) { return tab->alert_states.size() > 0; });
-    }
     base::UmaHistogramCounts100("Tabs.TabSearch.NumWindowsOnOpen",
                                 profile_tabs->windows.size());
     base::UmaHistogramCounts10000("Tabs.TabSearch.NumTabsOnOpen", tab_count);
@@ -170,8 +163,6 @@
         "Tabs.TabSearch.RecentlyClosedSectionToggleStateOnOpen",
         expand_preference ? TabSearchRecentlyClosedToggleAction::kExpand
                           : TabSearchRecentlyClosedToggleAction::kCollapse);
-    base::UmaHistogramCounts10000("Tabs.TabSearch.NumMediaTabsOnOpen",
-                                  media_tab_count);
   }
 
   std::move(callback).Run(std::move(profile_tabs));
@@ -478,7 +469,6 @@
   tab_data->last_active_elapsed_text =
       GetLastActiveElapsedText(last_active_time_ticks);
 
-  if (base::FeatureList::IsEnabled(features::kTabSearchMediaTabs)) {
     std::vector<TabAlertState> alert_states =
         chrome::GetTabAlertStatesForContents(contents);
     // Currently, we only report media alert states.
@@ -489,9 +479,8 @@
                                    alert == TabAlertState::AUDIO_PLAYING ||
                                    alert == TabAlertState::AUDIO_MUTING;
                           });
-  }
 
-  return tab_data;
+    return tab_data;
 }
 
 tab_search::mojom::RecentlyClosedTabPtr
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
index 9b876467..9267d70 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/test/bind.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/timer/mock_timer.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -381,8 +380,6 @@
 }
 
 TEST_F(TabSearchPageHandlerTest, MediaTabsTest) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kTabSearchMediaTabs);
   std::unique_ptr<content::WebContents> test_web_contents(
       content::WebContentsTester::CreateTestWebContents(
           content::WebContents::CreateParams(profile())));
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
index 97f1637..fe21dcc 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
@@ -72,12 +72,6 @@
       "useMetricsReporter",
       base::FeatureList::IsEnabled(features::kTabSearchUseMetricsReporter));
 
-  source->AddBoolean(
-      "alsoShowMediaTabsinOpenTabsSection",
-      GetFieldTrialParamByFeatureAsBool(
-          features::kTabSearchMediaTabs,
-          features::kTabSearchAlsoShowMediaTabsinOpenTabsSectionParameterName,
-          false));
   source->AddBoolean("searchIgnoreLocation",
                      features::kTabSearchSearchIgnoreLocation.Get());
   source->AddInteger("searchDistance",
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util.cc b/chrome/browser/ui/webui/whats_new/whats_new_util.cc
index cea48abb..dc39ea1 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_util.cc
+++ b/chrome/browser/ui/webui/whats_new/whats_new_util.cc
@@ -103,12 +103,14 @@
 }
 
 GURL GetServerURL(bool may_redirect) {
-  return may_redirect
-             ? net::AppendQueryParameter(
-                   GURL(kChromeWhatsNewURL), "version",
-                   base::NumberToString(CHROME_VERSION_MAJOR))
-             : GURL(kChromeWhatsNewURL)
-                   .Resolve(base::StringPrintf("m%d", CHROME_VERSION_MAJOR));
+  const GURL url =
+      may_redirect
+          ? net::AppendQueryParameter(
+                GURL(kChromeWhatsNewURL), "version",
+                base::NumberToString(CHROME_VERSION_MAJOR))
+          : GURL(kChromeWhatsNewURL)
+                .Resolve(base::StringPrintf("m%d", CHROME_VERSION_MAJOR));
+  return net::AppendQueryParameter(url, "internal", "true");
 }
 
 GURL GetWebUIStartupURL() {
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util_unittest.cc b/chrome/browser/ui/webui/whats_new/whats_new_util_unittest.cc
new file mode 100644
index 0000000..53a46bd
--- /dev/null
+++ b/chrome/browser/ui/webui/whats_new/whats_new_util_unittest.cc
@@ -0,0 +1,26 @@
+// 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/webui/whats_new/whats_new_util.h"
+
+#include <stddef.h>
+
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "chrome/common/chrome_version.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(WhatsNewUtil, GetServerURL) {
+  const std::string expected_no_redirect = base::StringPrintf(
+      "https://www.google.com/chrome/whats-new/m%d?internal=true",
+      CHROME_VERSION_MAJOR);
+  const std::string expected_redirect = base::StringPrintf(
+      "https://www.google.com/chrome/whats-new/?version=%d&internal=true",
+      CHROME_VERSION_MAJOR);
+
+  EXPECT_EQ(expected_no_redirect,
+            whats_new::GetServerURL(false).possibly_invalid_spec());
+  EXPECT_EQ(expected_redirect,
+            whats_new::GetServerURL(true).possibly_invalid_spec());
+}
diff --git a/chrome/browser/webid/federated_identity_active_session_permission_context.cc b/chrome/browser/webid/federated_identity_active_session_permission_context.cc
index 78ca5c9c90..99f386a 100644
--- a/chrome/browser/webid/federated_identity_active_session_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_active_session_permission_context.cc
@@ -28,25 +28,25 @@
     ~FederatedIdentityActiveSessionPermissionContext() = default;
 
 bool FederatedIdentityActiveSessionPermissionContext::HasActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
-  return HasPermission(relying_party, relying_party, identity_provider,
-                       account_identifier);
+  return HasPermission(relying_party_requester, relying_party_requester,
+                       identity_provider, account_identifier);
 }
 
 void FederatedIdentityActiveSessionPermissionContext::GrantActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
-  GrantPermission(relying_party, relying_party, identity_provider,
-                  account_identifier);
+  GrantPermission(relying_party_requester, relying_party_requester,
+                  identity_provider, account_identifier);
 }
 
 void FederatedIdentityActiveSessionPermissionContext::RevokeActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
-  RevokePermission(relying_party, relying_party, identity_provider,
-                   account_identifier);
+  RevokePermission(relying_party_requester, relying_party_requester,
+                   identity_provider, account_identifier);
 }
diff --git a/chrome/browser/webid/federated_identity_active_session_permission_context.h b/chrome/browser/webid/federated_identity_active_session_permission_context.h
index 2a78f546..85e6531b 100644
--- a/chrome/browser/webid/federated_identity_active_session_permission_context.h
+++ b/chrome/browser/webid/federated_identity_active_session_permission_context.h
@@ -32,13 +32,13 @@
       const FederatedIdentityActiveSessionPermissionContext&) = delete;
 
   // content::FederatedIdentityActiveSessionPermissionContextDelegate:
-  bool HasActiveSession(const url::Origin& relying_party,
+  bool HasActiveSession(const url::Origin& relying_party_requester,
                         const url::Origin& identity_provider,
                         const std::string& account_identifier) override;
-  void GrantActiveSession(const url::Origin& relying_party,
+  void GrantActiveSession(const url::Origin& relying_party_requester,
                           const url::Origin& identity_provider,
                           const std::string& account_identifier) override;
-  void RevokeActiveSession(const url::Origin& relying_party,
+  void RevokeActiveSession(const url::Origin& relying_party_requester,
                            const url::Origin& identity_provider,
                            const std::string& account_identifier) override;
 };
diff --git a/chrome/browser/webid/federated_identity_api_permission_context.cc b/chrome/browser/webid/federated_identity_api_permission_context.cc
index 23976fa..bc452944 100644
--- a/chrome/browser/webid/federated_identity_api_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_api_permission_context.cc
@@ -32,7 +32,7 @@
 
 content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus
 FederatedIdentityApiPermissionContext::GetApiPermissionStatus(
-    const url::Origin& rp_origin) {
+    const url::Origin& relying_party_embedder) {
   if (!base::FeatureList::IsEnabled(features::kFedCm))
     return PermissionStatus::BLOCKED_VARIATIONS;
 
@@ -42,9 +42,10 @@
   if (cookie_settings_->ShouldBlockThirdPartyCookies())
     return PermissionStatus::BLOCKED_THIRD_PARTY_COOKIES_BLOCKED;
 
-  const GURL rp_url = rp_origin.GetURL();
+  const GURL rp_embedder_url = relying_party_embedder.GetURL();
   const ContentSetting setting = host_content_settings_map_->GetContentSetting(
-      rp_url, rp_url, ContentSettingsType::FEDERATED_IDENTITY_API);
+      rp_embedder_url, rp_embedder_url,
+      ContentSettingsType::FEDERATED_IDENTITY_API);
   switch (setting) {
     case CONTENT_SETTING_ALLOW:
       break;
@@ -56,32 +57,34 @@
   }
 
   if (permission_autoblocker_->IsEmbargoed(
-          rp_url, ContentSettingsType::FEDERATED_IDENTITY_API)) {
+          rp_embedder_url, ContentSettingsType::FEDERATED_IDENTITY_API)) {
     return PermissionStatus::BLOCKED_EMBARGO;
   }
   return PermissionStatus::GRANTED;
 }
 
 void FederatedIdentityApiPermissionContext::RecordDismissAndEmbargo(
-    const url::Origin& rp_origin) {
-  const GURL rp_url = rp_origin.GetURL();
-  // If content setting is allowed for `rp_url`, reset it.
+    const url::Origin& relying_party_embedder) {
+  const GURL rp_embedder_url = relying_party_embedder.GetURL();
+  // If content setting is allowed for `rp_embedder_url`, reset it.
   // See crbug.com/1340127 for why the resetting is not conditional on the
   // default content setting state.
   const ContentSetting setting = host_content_settings_map_->GetContentSetting(
-      rp_url, rp_url, ContentSettingsType::FEDERATED_IDENTITY_API);
+      rp_embedder_url, rp_embedder_url,
+      ContentSettingsType::FEDERATED_IDENTITY_API);
   if (setting == CONTENT_SETTING_ALLOW) {
     host_content_settings_map_->SetContentSettingDefaultScope(
-        rp_url, rp_url, ContentSettingsType::FEDERATED_IDENTITY_API,
-        CONTENT_SETTING_DEFAULT);
+        rp_embedder_url, rp_embedder_url,
+        ContentSettingsType::FEDERATED_IDENTITY_API, CONTENT_SETTING_DEFAULT);
   }
   permission_autoblocker_->RecordDismissAndEmbargo(
-      rp_url, ContentSettingsType::FEDERATED_IDENTITY_API,
+      rp_embedder_url, ContentSettingsType::FEDERATED_IDENTITY_API,
       false /* dismissed_prompt_was_quiet */);
 }
 
 void FederatedIdentityApiPermissionContext::RemoveEmbargoAndResetCounts(
-    const url::Origin& rp_origin) {
+    const url::Origin& relying_party_embedder) {
   permission_autoblocker_->RemoveEmbargoAndResetCounts(
-      rp_origin.GetURL(), ContentSettingsType::FEDERATED_IDENTITY_API);
+      relying_party_embedder.GetURL(),
+      ContentSettingsType::FEDERATED_IDENTITY_API);
 }
diff --git a/chrome/browser/webid/federated_identity_api_permission_context.h b/chrome/browser/webid/federated_identity_api_permission_context.h
index 18d427f..8339c41 100644
--- a/chrome/browser/webid/federated_identity_api_permission_context.h
+++ b/chrome/browser/webid/federated_identity_api_permission_context.h
@@ -39,9 +39,11 @@
 
   // content::FederatedIdentityApiPermissionContextDelegate:
   content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus
-  GetApiPermissionStatus(const url::Origin& rp_origin) override;
-  void RecordDismissAndEmbargo(const url::Origin& rp_origin) override;
-  void RemoveEmbargoAndResetCounts(const url::Origin& rp_origin) override;
+  GetApiPermissionStatus(const url::Origin& relying_party_embedder) override;
+  void RecordDismissAndEmbargo(
+      const url::Origin& relying_party_embedder) override;
+  void RemoveEmbargoAndResetCounts(
+      const url::Origin& relying_party_embedder) override;
 
  private:
   const raw_ptr<HostContentSettingsMap> host_content_settings_map_;
diff --git a/chrome/browser/webid/federated_identity_sharing_permission_context.cc b/chrome/browser/webid/federated_identity_sharing_permission_context.cc
index 4754788a..e6bdfdb 100644
--- a/chrome/browser/webid/federated_identity_sharing_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_sharing_permission_context.cc
@@ -28,16 +28,19 @@
     ~FederatedIdentitySharingPermissionContext() = default;
 
 bool FederatedIdentitySharingPermissionContext::HasSharingPermission(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
     const url::Origin& identity_provider,
     const std::string& account_id) {
-  return HasPermission(relying_party, relying_party, identity_provider,
-                       account_id);
+  return HasPermission(relying_party_requester, relying_party_embedder,
+                       identity_provider, account_id);
 }
 
 void FederatedIdentitySharingPermissionContext::GrantSharingPermission(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
     const url::Origin& identity_provider,
     const std::string& account_id) {
-  GrantPermission(relying_party, relying_party, identity_provider, account_id);
+  GrantPermission(relying_party_requester, relying_party_embedder,
+                  identity_provider, account_id);
 }
diff --git a/chrome/browser/webid/federated_identity_sharing_permission_context.h b/chrome/browser/webid/federated_identity_sharing_permission_context.h
index 747e88b8..5c7a296 100644
--- a/chrome/browser/webid/federated_identity_sharing_permission_context.h
+++ b/chrome/browser/webid/federated_identity_sharing_permission_context.h
@@ -32,10 +32,12 @@
       const FederatedIdentitySharingPermissionContext&) = delete;
 
   // content::FederatedIdentitySharingPermissionContextDelegate:
-  bool HasSharingPermission(const url::Origin& relying_party,
+  bool HasSharingPermission(const url::Origin& relying_party_requester,
+                            const url::Origin& relying_party_embedder,
                             const url::Origin& identity_provider,
                             const std::string& account_id) override;
-  void GrantSharingPermission(const url::Origin& relying_party,
+  void GrantSharingPermission(const url::Origin& relying_party_requester,
+                              const url::Origin& relying_party_embedder,
                               const url::Origin& identity_provider,
                               const std::string& account_id) override;
 };
diff --git a/chrome/browser/webshare/share_service_browsertest.cc b/chrome/browser/webshare/share_service_browsertest.cc
index 4e18f25..7fd02a1 100644
--- a/chrome/browser/webshare/share_service_browsertest.cc
+++ b/chrome/browser/webshare/share_service_browsertest.cc
@@ -15,8 +15,10 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/safe_browsing/core/browser/db/fake_database_manager.h"
 #include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -165,3 +167,61 @@
   EXPECT_EQ("share failed: NotAllowedError: Permission denied",
             content::EvalJs(contents, "share_pdf_file()"));
 }
+
+class ShareServicePrerenderBrowserTest : public ShareServiceBrowserTest {
+ public:
+  ShareServicePrerenderBrowserTest()
+      : prerender_helper_(
+            base::BindRepeating(&ShareServicePrerenderBrowserTest::web_contents,
+                                base::Unretained(this))) {}
+  ~ShareServicePrerenderBrowserTest() override = default;
+
+ protected:
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  content::test::PrerenderTestHelper prerender_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(ShareServicePrerenderBrowserTest, Text) {
+  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL("/empty.html")));
+
+  content::WebContents* const contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Start a prerender.
+  const GURL kPrerenderUrl =
+      embedded_test_server()->GetURL("/webshare/index.html");
+  const int kPrerenderHostId = prerender_helper_.AddPrerender((kPrerenderUrl));
+  ASSERT_EQ(prerender_helper_.GetHostForUrl(kPrerenderUrl), kPrerenderHostId);
+
+  content::RenderFrameHost* prerender_rfh =
+      prerender_helper_.GetPrerenderedMainFrameHost(kPrerenderHostId);
+  EXPECT_EQ(prerender_rfh->GetLifecycleState(),
+            content::RenderFrameHost::LifecycleState::kPrerendering);
+  const std::string script = "share_text('hello')";
+  const content::EvalJsResult prerendered_result =
+      content::EvalJs(prerender_rfh, script);
+  EXPECT_EQ(
+      "share failed: NotAllowedError: Failed to execute 'share' on "
+      "'Navigator': Must be handling a user gesture to perform a share "
+      "request.",
+      prerendered_result);
+  histogram_tester.ExpectBucketCount(kWebShareApiCountMetric,
+                                     WebShareMethod::kShare, 0);
+
+  // Activate the prerendered page.
+  prerender_helper_.NavigatePrimaryPage(kPrerenderUrl);
+  EXPECT_EQ(prerender_rfh->GetLifecycleState(),
+            content::RenderFrameHost::LifecycleState::kActive);
+  ASSERT_EQ(kPrerenderUrl, contents->GetLastCommittedURL());
+  const content::EvalJsResult activated_result =
+      content::EvalJs(prerender_rfh, script);
+  EXPECT_EQ("share succeeded", activated_result);
+  histogram_tester.ExpectBucketCount(kWebShareApiCountMetric,
+                                     WebShareMethod::kShare, 1);
+}
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index c8b5704..f500e22 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1660910358-3979b2240422bff46d6633737995e90099e11c4e.profdata
+chrome-linux-main-1660931915-e3d237b7ef4f5e5f5b092bf2ee5dbef7471660b5.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index d40fe43c..7bbe3785 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1660910358-dbbb692954d2f6c5c460d1e18b608df67d4b7bba.profdata
+chrome-mac-arm-main-1660931915-434ee39902a2a49541bf1e885174b731ed5873e1.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 701188a..fd8c346 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1660910358-c6646bdc2ee417472d5c644bb06a3e2f1932d24b.profdata
+chrome-mac-main-1660931915-01748db33f1d1293e8643b2d0242151ac08d534c.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 7e38ab6..06ebaeb6 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1660921165-2821267ae30bb34326dd7cb61af0ee5f5916fa36.profdata
+chrome-win32-main-1660931915-829fed840eefe53900738c1b6b18a780456e26b4.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index be7f096b..8aa54861 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1660910358-f8f0ae66331a69857533cab712c957eb33416a16.profdata
+chrome-win64-main-1660931915-c3a2098161c9e48dacfae89eda246a32068f0f1e.profdata
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 29c0e6b..44b10b24 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -682,6 +682,7 @@
 #if !BUILDFLAG(IS_ANDROID)
     kChromeUIWebAppInternalsHost,
 #endif
+    content::kChromeUIPrivateAggregationInternalsHost,
     content::kChromeUIAttributionInternalsHost,
     content::kChromeUIBlobInternalsHost,
     content::kChromeUIDinoHost,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6841235..f0997bf1 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3847,6 +3847,9 @@
         "../browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc",
         "../browser/ash/policy/networking/network_policy_application_browsertest.cc",
         "../browser/ash/policy/networking/policy_certs_browsertest.cc",
+        "../browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.cc",
+        "../browser/ash/policy/reporting/metrics_reporting/metric_browsertest_utils.h",
+        "../browser/ash/policy/reporting/metrics_reporting/network/network_info_sampler_browsertest.cc",
         "../browser/ash/policy/reporting/metrics_reporting/usb/usb_events_browsertest.cc",
         "../browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter_browsertest.cc",
         "../browser/ash/policy/status_collector/child_status_collector_browsertest.cc",
@@ -7059,6 +7062,7 @@
       "../browser/extensions/api/document_scan/document_scan_api_unittest.cc",
       "../browser/media/platform_verification_chromeos_unittest.cc",
       "../browser/policy/system_features_disable_list_policy_handler_unittest.cc",
+      "../browser/speech/tts_crosapi_util_unittest.cc",
       "../browser/ui/webui/certificate_provisioning_ui_handler_unittest.cc",
       "chromeos/printing/fake_local_printer_chromeos.cc",
       "chromeos/printing/fake_local_printer_chromeos.h",
@@ -8359,6 +8363,7 @@
       "../browser/ui/webui/app_home/mock_app_home_page.cc",
       "../browser/ui/webui/app_home/mock_app_home_page.h",
       "../browser/ui/webui/ntp/app_launcher_handler_unittest.cc",
+      "../browser/ui/webui/whats_new/whats_new_util_unittest.cc",
     ]
 
     deps += [
@@ -9521,6 +9526,7 @@
         "../browser/ui/views/test/view_event_test_base.cc",
         "../browser/ui/views/test/view_event_test_base.h",
         "../browser/ui/views/toolbar/reload_button_browsertest.cc",
+        "../browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc",
         "../browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc",
         "../browser/ui/views/translate/translate_bubble_test_utils_views.cc",
         "../browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc",
diff --git a/chrome/test/base/chromeos/ash_browser_test_starter.cc b/chrome/test/base/chromeos/ash_browser_test_starter.cc
index 1095c18..2ba1420d 100644
--- a/chrome/test/base/chromeos/ash_browser_test_starter.cc
+++ b/chrome/test/base/chromeos/ash_browser_test_starter.cc
@@ -41,7 +41,8 @@
 
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   scoped_feature_list_.InitWithFeatures(
-      {chromeos::features::kLacrosSupport, chromeos::features::kLacrosPrimary},
+      {chromeos::features::kLacrosSupport, chromeos::features::kLacrosPrimary,
+       chromeos::features::kLacrosOnly},
       {});
   command_line->AppendSwitch("enable-wayland-server");
   command_line->AppendSwitch("no-startup-window");
diff --git a/chrome/test/data/safe_browsing/visual_model_android.tflite b/chrome/test/data/safe_browsing/visual_model_android.tflite
index 8478ec7..922c0d8d 100644
--- a/chrome/test/data/safe_browsing/visual_model_android.tflite
+++ b/chrome/test/data/safe_browsing/visual_model_android.tflite
Binary files differ
diff --git a/chrome/test/data/webui/cr_components/help_bubble_mixin_test.ts b/chrome/test/data/webui/cr_components/help_bubble_mixin_test.ts
index ce99e3bf..4d27d000 100644
--- a/chrome/test/data/webui/cr_components/help_bubble_mixin_test.ts
+++ b/chrome/test/data/webui/cr_components/help_bubble_mixin_test.ts
@@ -6,7 +6,7 @@
 import 'chrome://resources/cr_components/help_bubble/help_bubble.js';
 
 import {HelpBubbleElement} from 'chrome://resources/cr_components/help_bubble/help_bubble.js';
-import {HelpBubbleClientCallbackRouter, HelpBubbleClientRemote, HelpBubbleHandlerInterface, HelpBubbleParams, HelpBubblePosition} from 'chrome://resources/cr_components/help_bubble/help_bubble.mojom-webui.js';
+import {HelpBubbleArrowPosition, HelpBubbleClientCallbackRouter, HelpBubbleClientRemote, HelpBubbleHandlerInterface, HelpBubbleParams} from 'chrome://resources/cr_components/help_bubble/help_bubble.mojom-webui.js';
 import {HelpBubbleMixin, HelpBubbleMixinInterface} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin.js';
 import {HelpBubbleProxy, HelpBubbleProxyImpl} from 'chrome://resources/cr_components/help_bubble/help_bubble_proxy.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -158,7 +158,7 @@
   const defaultParams: HelpBubbleParams = new HelpBubbleParams();
   defaultParams.nativeIdentifier = PARAGRAPH_NATIVE_ID;
   defaultParams.closeButtonAltText = CLOSE_BUTTON_ALT_TEXT;
-  defaultParams.position = HelpBubblePosition.ABOVE;
+  defaultParams.position = HelpBubbleArrowPosition.BOTTOM_CENTER;
   defaultParams.bodyText = 'This is a help bubble.';
   defaultParams.buttons = [];
 
@@ -258,7 +258,7 @@
         const params: HelpBubbleParams = new HelpBubbleParams();
         params.nativeIdentifier = 'This is an unregistered identifier';
         params.closeButtonAltText = CLOSE_BUTTON_ALT_TEXT;
-        params.position = HelpBubblePosition.ABOVE;
+        params.position = HelpBubbleArrowPosition.BOTTOM_CENTER;
         params.bodyText = 'This is a help bubble.';
         params.buttons = [];
 
@@ -372,7 +372,7 @@
   const paramsWithTitle: HelpBubbleParams = new HelpBubbleParams();
   paramsWithTitle.nativeIdentifier = TITLE_NATIVE_ID;
   paramsWithTitle.closeButtonAltText = CLOSE_BUTTON_ALT_TEXT;
-  paramsWithTitle.position = HelpBubblePosition.BELOW;
+  paramsWithTitle.position = HelpBubbleArrowPosition.TOP_CENTER;
   paramsWithTitle.bodyText = 'This is another help bubble.';
   paramsWithTitle.titleText = 'This is a title';
   paramsWithTitle.buttons = [];
@@ -411,7 +411,7 @@
   const paramsWithProgress: HelpBubbleParams = new HelpBubbleParams();
   paramsWithProgress.nativeIdentifier = LIST_NATIVE_ID;
   paramsWithProgress.closeButtonAltText = CLOSE_BUTTON_ALT_TEXT;
-  paramsWithProgress.position = HelpBubblePosition.BELOW;
+  paramsWithProgress.position = HelpBubbleArrowPosition.TOP_CENTER;
   paramsWithProgress.bodyText = 'This is another help bubble.';
   paramsWithProgress.progress = {current: 1, total: 3};
   paramsWithProgress.buttons = [];
@@ -471,7 +471,7 @@
   const buttonParams: HelpBubbleParams = new HelpBubbleParams();
   buttonParams.nativeIdentifier = PARAGRAPH_NATIVE_ID;
   buttonParams.closeButtonAltText = CLOSE_BUTTON_ALT_TEXT;
-  buttonParams.position = HelpBubblePosition.BELOW;
+  buttonParams.position = HelpBubbleArrowPosition.TOP_CENTER;
   buttonParams.bodyText = 'This is another help bubble.';
   buttonParams.titleText = 'This is a title';
   buttonParams.buttons = [
diff --git a/chrome/test/data/webui/cr_components/help_bubble_test.ts b/chrome/test/data/webui/cr_components/help_bubble_test.ts
index 61ecd124..70e3138 100644
--- a/chrome/test/data/webui/cr_components/help_bubble_test.ts
+++ b/chrome/test/data/webui/cr_components/help_bubble_test.ts
@@ -7,7 +7,7 @@
 
 import {CrButtonElement} from '//resources/cr_elements/cr_button/cr_button.js';
 import {HELP_BUBBLE_DISMISSED_EVENT, HelpBubbleDismissedEvent, HelpBubbleElement} from 'chrome://resources/cr_components/help_bubble/help_bubble.js';
-import {HelpBubbleButtonParams, HelpBubblePosition} from 'chrome://resources/cr_components/help_bubble/help_bubble.mojom-webui.js';
+import {HelpBubbleArrowPosition, HelpBubbleButtonParams} from 'chrome://resources/cr_components/help_bubble/help_bubble.mojom-webui.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isVisible, waitAfterNextRender} from 'chrome://webui-test/test_util.js';
 
@@ -80,7 +80,7 @@
 
   test('help bubble shows and anchors correctly', () => {
     helpBubble.anchorId = 'p1';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.show();
 
@@ -98,7 +98,7 @@
 
   test('help bubble titles shows', () => {
     helpBubble.anchorId = 'p1';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.titleText = HELP_BUBBLE_TITLE;
     helpBubble.show();
@@ -116,7 +116,7 @@
 
   test('help bubble titles hides when no title set', () => {
     helpBubble.anchorId = 'p1';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.show();
 
@@ -129,7 +129,7 @@
 
   test('help bubble closes', () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.show();
 
@@ -147,7 +147,7 @@
 
   test('help bubble open close open', () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.show();
     helpBubble.hide();
@@ -184,7 +184,7 @@
     };
     helpBubble.addEventListener(HELP_BUBBLE_DISMISSED_EVENT, callback);
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.show();
     await waitAfterNextRender(helpBubble);
@@ -196,7 +196,7 @@
 
   test('help bubble adds one button', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = [{text: 'button1', isDefault: false}];
     helpBubble.show();
@@ -214,7 +214,7 @@
 
   test('help bubble adds several buttons', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = [
       {text: 'button1', isDefault: false},
@@ -238,7 +238,7 @@
 
   test('help bubble adds default button', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = [{text: 'button1', isDefault: true}];
     helpBubble.show();
@@ -258,7 +258,7 @@
 
   test('help bubble adds default button among several', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = THREE_BUTTONS_MIDDLE_DEFAULT;
     helpBubble.show();
@@ -303,7 +303,7 @@
     };
     helpBubble.addEventListener(HELP_BUBBLE_DISMISSED_EVENT, callback);
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = THREE_BUTTONS_MIDDLE_DEFAULT;
 
@@ -323,7 +323,7 @@
 
   test('help bubble with no progress doesn\'t show progress', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.buttons = THREE_BUTTONS_MIDDLE_DEFAULT;
 
@@ -341,7 +341,7 @@
       'help bubble with no progress and title doesn\'t show progress',
       async () => {
         helpBubble.anchorId = 'title';
-        helpBubble.position = HelpBubblePosition.BELOW;
+        helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
         helpBubble.bodyText = HELP_BUBBLE_BODY;
         helpBubble.titleText = HELP_BUBBLE_TITLE;
         helpBubble.buttons = THREE_BUTTONS_MIDDLE_DEFAULT;
@@ -363,7 +363,7 @@
 
   test('help bubble with progress shows progress', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.progress = {current: 1, total: 3};
     helpBubble.buttons = THREE_BUTTONS_MIDDLE_DEFAULT;
@@ -387,7 +387,7 @@
 
   test('help bubble with progress and title shows progress', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.titleText = HELP_BUBBLE_TITLE;
     helpBubble.progress = {current: 1, total: 2};
@@ -416,7 +416,7 @@
 
   test('help bubble with full progress', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.progress = {current: 2, total: 2};
 
@@ -435,7 +435,7 @@
 
   test('help bubble with empty progress', async () => {
     helpBubble.anchorId = 'title';
-    helpBubble.position = HelpBubblePosition.BELOW;
+    helpBubble.position = HelpBubbleArrowPosition.TOP_CENTER;
     helpBubble.bodyText = HELP_BUBBLE_BODY;
     helpBubble.progress = {current: 0, total: 2};
 
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index e7ef248e..399a967 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -420,7 +420,6 @@
  ['OSSettingsMenu', 'os_settings_menu_test.js'],
  ['ParentalControlsPage', 'parental_controls_page_test.js'],
  ['PeoplePage', 'os_people_page_test.js'],
- ['PeoplePageChangePicture', 'people_page_change_picture_test.js'],
  ['PeoplePageQuickUnlock', 'quick_unlock_authenticate_browsertest_chromeos.js'],
  [
    'PersonalizationPage',
diff --git a/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js b/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js
deleted file mode 100644
index 467016c..0000000
--- a/chrome/test/data/webui/settings/chromeos/people_page_change_picture_test.js
+++ /dev/null
@@ -1,403 +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.
-
-import {ChangePictureBrowserProxyImpl, routes} from 'chrome://os-settings/chromeos/os_settings.js';
-import {CrPicture} from 'chrome://resources/ash/common/cr_picture/cr_picture_types.js';
-import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
-import {pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
-import {TestBrowserProxy} from '../../test_browser_proxy.js';
-
-/** @implements {ChangePictureBrowserProxy} */
-class TestChangePictureBrowserProxy extends TestBrowserProxy {
-  constructor() {
-    super([
-      'initialize',
-      'selectDefaultImage',
-      'selectOldImage',
-      'selectProfileImage',
-      'photoTaken',
-      'chooseFile',
-      'requestSelectedImage',
-    ]);
-  }
-
-  /** @override */
-  initialize() {
-    webUIListenerCallback(
-        'profile-image-changed', 'fake-profile-image-url',
-        false /* selected */);
-
-    const fakeCurrentDefaultImages = [
-      {
-        index: 2,
-        title: 'Title2',
-        url: 'chrome://foo/2.png',
-      },
-      {
-        index: 3,
-        title: 'Title3',
-        url: 'chrome://foo/3.png',
-      },
-    ];
-    webUIListenerCallback('default-images-changed', {
-      current_default_images: fakeCurrentDefaultImages,
-    });
-
-    this.methodCalled('initialize');
-  }
-
-  /** @override */
-  selectDefaultImage(imageUrl) {
-    webUIListenerCallback('selected-image-changed', imageUrl);
-    this.methodCalled('selectDefaultImage', imageUrl);
-  }
-
-  /** @override */
-  selectOldImage() {
-    webUIListenerCallback('old-image-changed', {
-      url: 'fake-old-image.jpg',
-      index: 1,
-    });
-    this.methodCalled('selectOldImage');
-  }
-
-  /** @override */
-  selectProfileImage() {
-    webUIListenerCallback(
-        'profile-image-changed', 'fake-profile-image-url', true /* selected */);
-    this.methodCalled('selectProfileImage');
-  }
-
-  /** @override */
-  photoTaken() {
-    this.methodCalled('photoTaken');
-  }
-
-  /** @override */
-  chooseFile() {
-    this.methodCalled('chooseFile');
-  }
-
-  /** @override */
-  requestSelectedImage() {
-    this.methodCalled('requestSelectedImage');
-  }
-}
-
-suite('ChangePictureTests', function() {
-  let changePicture = null;
-  let browserProxy = null;
-  let crPicturePane = null;
-  let crPictureList = null;
-
-  const LEFT_KEY_CODE = 37;
-  const RIGHT_KEY_CODE = 39;
-
-  /**
-   * @return {Array<HTMLElement>} Traverses the DOM tree to find the lowest
-   *     level active element and returns an array of the node path down the
-   *     tree, skipping shadow roots.
-   */
-  function getActiveElementPath() {
-    let node = document.activeElement;
-    const path = [];
-    while (node) {
-      path.push(node);
-      node = (node.shadowRoot || node).activeElement;
-    }
-    return path;
-  }
-
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({
-      profilePhoto: 'Fake Profile Photo description',
-    });
-  });
-
-  setup(async function() {
-    browserProxy = new TestChangePictureBrowserProxy();
-    ChangePictureBrowserProxyImpl.setInstanceForTesting(browserProxy);
-    PolymerTest.clearBody();
-    changePicture = document.createElement('settings-change-picture');
-    document.body.appendChild(changePicture);
-
-    crPicturePane = changePicture.shadowRoot.querySelector('cr-picture-pane');
-    assertTrue(!!crPicturePane);
-
-    crPictureList = changePicture.shadowRoot.querySelector('cr-picture-list');
-    assertTrue(!!crPictureList);
-
-    changePicture.currentRouteChanged(routes.CHANGE_PICTURE);
-
-    await browserProxy.whenCalled('initialize');
-    flush();
-  });
-
-  teardown(function() {
-    changePicture.remove();
-  });
-
-  test('TraverseCameraIconUsingArrows', function() {
-    // Force the camera to be present.
-    webUIListenerCallback('camera-presence-changed', true);
-    flush();
-    assertTrue(crPictureList.cameraPresent);
-
-    // Click camera icon.
-    const cameraImage = crPictureList.$.cameraImage;
-    cameraImage.click();
-    flush();
-
-    assertTrue(crPictureList.cameraSelected_);
-    const crCamera = crPicturePane.shadowRoot.querySelector('#camera');
-    assertTrue(!!crCamera);
-
-    // Mock camera's video stream beginning to play.
-    crCamera.$.cameraVideo.dispatchEvent(new Event('canplay'));
-    flush();
-
-    // "Take photo" button should be active.
-    let activeElementPath = getActiveElementPath();
-    assertTrue(activeElementPath.includes(crPicturePane));
-    assertFalse(activeElementPath.includes(crPictureList));
-
-    // Press 'Right' key on active element.
-    pressAndReleaseKeyOn(activeElementPath.pop(), RIGHT_KEY_CODE);
-    flush();
-
-    // A profile picture open should be active.
-    activeElementPath = getActiveElementPath();
-    assertFalse(crPictureList.cameraSelected_);
-    assertFalse(activeElementPath.includes(crPicturePane));
-    assertTrue(activeElementPath.includes(crPictureList));
-
-    // Press 'Left' key on active element.
-    pressAndReleaseKeyOn(activeElementPath.pop(), LEFT_KEY_CODE);
-    flush();
-
-    // Mock camera's video stream beginning to play.
-    crCamera.$.cameraVideo.dispatchEvent(new Event('canplay'));
-    flush();
-
-    // "Take photo" button should be active again.
-    activeElementPath = getActiveElementPath();
-    assertTrue(crPictureList.cameraSelected_);
-    assertTrue(activeElementPath.includes(crPicturePane));
-    assertFalse(activeElementPath.includes(crPictureList));
-  });
-
-  test('ChangePictureSelectCamera', async function() {
-    // Force the camera to be absent, even if it's actually present.
-    webUIListenerCallback('camera-presence-changed', false);
-    flush();
-
-    await new Promise(function(resolve) {
-      changePicture.async(resolve);
-    });
-    let camera = crPicturePane.shadowRoot.querySelector('#camera');
-    assertFalse(crPicturePane.cameraPresent);
-    assertFalse(crPicturePane.cameraActive_);
-    assertFalse(!!camera && camera.hidden);
-
-    webUIListenerCallback('camera-presence-changed', true);
-    flush();
-    await new Promise(function(resolve) {
-      changePicture.async(resolve);
-    });
-    camera = crPicturePane.shadowRoot.querySelector('#camera');
-    assertTrue(crPicturePane.cameraPresent);
-    assertFalse(crPicturePane.cameraActive_);
-    assertFalse(!!camera && camera.hidden);
-
-    const cameraImage = crPictureList.$.cameraImage;
-    cameraImage.click();
-    flush();
-    await new Promise(function(resolve) {
-      changePicture.async(resolve);
-    });
-    camera = crPicturePane.shadowRoot.querySelector('#camera');
-    assertTrue(crPicturePane.cameraActive_);
-    assertTrue(!!camera && !camera.hidden);
-    assertEquals(
-        CrPicture.SelectionTypes.CAMERA,
-        changePicture.selectedItem_.dataset.type);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!discard || discard.hidden);
-
-    // Ensure that the camera is deactivated if user navigates away.
-    changePicture.currentRouteChanged(routes.BASIC);
-    await new Promise(function(resolve) {
-      changePicture.async(resolve);
-    });
-    assertFalse(crPicturePane.cameraActive_);
-  });
-
-  test('ChangePictureProfileImage', async function() {
-    const profileImage = crPictureList.$.profileImage;
-    assertTrue(!!profileImage);
-
-    assertEquals(null, changePicture.selectedItem_);
-    profileImage.click();
-
-    await browserProxy.whenCalled('selectProfileImage');
-    flush();
-
-    assertEquals(
-        CrPicture.SelectionTypes.PROFILE,
-        changePicture.selectedItem_.dataset.type);
-    assertFalse(crPicturePane.cameraActive_);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!discard || discard.hidden);
-
-    // Ensure that the selection is restored after navigating away and
-    // then back to the subpage.
-    changePicture.currentRouteChanged(routes.BASIC);
-    changePicture.currentRouteChanged(routes.CHANGE_PICTURE);
-    assertEquals(null, changePicture.selectedItem_);
-  });
-
-  test('ChangePictureDeprecatedImage', async function() {
-    webUIListenerCallback(
-        'preview-deprecated-image', {url: 'fake-old-image.jpg'});
-    flush();
-
-    // Expect the deprecated image is presented in picture pane.
-    assertEquals(CrPicture.SelectionTypes.DEPRECATED, crPicturePane.imageType);
-    const image = crPicturePane.shadowRoot.querySelector('#image');
-    assertTrue(!!image);
-    assertFalse(image.hidden);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!!discard);
-    assertTrue(discard.hidden);
-  });
-
-  test('ChangePictureDeprecatedImageWithSourceInfo', async function() {
-    const fakeAuthor = 'FakeAuthor';
-    const fakeWebsite = 'http://foo1.com';
-    webUIListenerCallback('preview-deprecated-image', {
-      url: 'fake-old-image.jpg',
-      author: fakeAuthor,
-      website: fakeWebsite,
-    });
-    flush();
-
-    // Expect the deprecated image is presented in picture pane.
-    assertEquals(CrPicture.SelectionTypes.DEPRECATED, crPicturePane.imageType);
-    const image = crPicturePane.shadowRoot.querySelector('#image');
-    assertTrue(!!image);
-    assertFalse(image.hidden);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!!discard);
-    assertTrue(discard.hidden);
-    const sourceInfo = changePicture.shadowRoot.querySelector('#sourceInfo');
-    assertTrue(!!sourceInfo);
-    assertFalse(sourceInfo.hidden);
-    assertEquals(changePicture.authorInfo_, 'Photo by ' + fakeAuthor);
-    assertEquals(changePicture.websiteInfo_, fakeWebsite);
-  });
-
-  test('ChangePictureFileImage', async function() {
-    assertFalse(!!changePicture.selectedItem_);
-
-    // By default there is no old image and the element is hidden.
-    const oldImage = crPictureList.$.oldImage;
-    assertTrue(!!oldImage);
-    assertTrue(oldImage.hidden);
-
-    webUIListenerCallback('old-image-changed', 'file-image.jpg');
-    flush();
-
-    await new Promise(function(resolve) {
-      changePicture.async(resolve);
-    });
-    assertTrue(!!changePicture.selectedItem_);
-    // Expect the old image to be selected once an old image is sent via
-    // the native interface.
-    assertEquals(
-        CrPicture.SelectionTypes.OLD, changePicture.selectedItem_.dataset.type);
-    assertFalse(oldImage.hidden);
-    assertFalse(crPicturePane.cameraActive_);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!!discard);
-    assertFalse(discard.hidden);
-    // Ensure the file image does not show the source info.
-    const sourceInfo = changePicture.shadowRoot.querySelector('#sourceInfo');
-    assertTrue(!sourceInfo || sourceInfo.hidden);
-  });
-
-  test('ChangePictureSelectFirstDefaultImage', async function() {
-    const firstDefaultImage =
-        crPictureList.shadowRoot.querySelector('img[data-type="default"]');
-    assertTrue(!!firstDefaultImage);
-
-    firstDefaultImage.click();
-
-    let imageUrl = await browserProxy.whenCalled('selectDefaultImage');
-    assertEquals('chrome://foo/2.png', imageUrl);
-
-    flush();
-    assertEquals(
-        CrPicture.SelectionTypes.DEFAULT,
-        changePicture.selectedItem_.dataset.type);
-    assertEquals(firstDefaultImage, changePicture.selectedItem_);
-    assertFalse(crPicturePane.cameraActive_);
-    const discard = crPicturePane.shadowRoot.querySelector('#discard');
-    assertTrue(!discard || discard.hidden);
-
-    // Now verify that arrow keys actually select the new image.
-    browserProxy.resetResolver('selectDefaultImage');
-    pressAndReleaseKeyOn(changePicture.selectedItem_, RIGHT_KEY_CODE);
-    imageUrl = await browserProxy.whenCalled('selectDefaultImage');
-    assertEquals('chrome://foo/3.png', imageUrl);
-  });
-
-  test('ChangePictureRestoreImageAfterDiscard', async function() {
-    const firstDefaultImage =
-        crPictureList.shadowRoot.querySelector('img[data-type="default"]');
-    assertTrue(!!firstDefaultImage);
-
-    firstDefaultImage.click();
-
-    await browserProxy.whenCalled('selectDefaultImage');
-    flush();
-    assertEquals(firstDefaultImage, changePicture.selectedItem_);
-
-    webUIListenerCallback('old-image-changed', 'fake-old-image.jpg');
-
-    flush();
-    assertEquals(
-        CrPicture.SelectionTypes.OLD, changePicture.selectedItem_.dataset.type);
-
-    const discardButton =
-        crPicturePane.shadowRoot.querySelector('#discard cr-icon-button');
-    assertTrue(!!discardButton);
-    discardButton.click();
-
-    flush();
-    const profileImage = crPictureList.$.profileImage;
-    assertTrue(!!profileImage);
-    assertEquals(profileImage, changePicture.selectedItem_);
-  });
-
-  test('ChangePictureImagePendingStateCheck', async function() {
-    // oldImagePending_ should be false when no camera photo pending.
-    assertFalse(changePicture.oldImagePending_);
-    assertEquals(crPictureList.oldImageUrl_, '');
-    // Simulate photo taken event.
-    crPicturePane.fire('photo-taken', {photoDataUrl: 'camera-image.jpg'});
-    flush();
-    // oldImagePending_ should be true due to pending camera image.
-    assertTrue(changePicture.oldImagePending_);
-
-    webUIListenerCallback('old-image-changed', 'camera-image.jpg');
-    flush();
-    // oldImagePending_ should be false after the image has been received.
-    assertFalse(changePicture.oldImagePending_);
-    assertEquals(crPictureList.oldImageUrl_, 'camera-image.jpg');
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/personalization_page_test.js b/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
index 9f3074e..5b33db4 100644
--- a/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/personalization_page_test.js
@@ -52,34 +52,4 @@
     personalizationPage.remove();
     Router.getInstance().resetRouteForTesting();
   });
-
-  test('changePicture', function() {
-    const row =
-        personalizationPage.shadowRoot.getElementById('changePictureRow');
-    assertTrue(!!row);
-    row.click();
-    assertEquals(routes.CHANGE_PICTURE, Router.getInstance().getCurrentRoute());
-  });
-
-  test('Deep link to change account picture', async () => {
-    const params = new URLSearchParams();
-    params.append('settingId', '503');
-    Router.getInstance().navigateTo(routes.CHANGE_PICTURE, params);
-
-    flush();
-
-    await waitAfterNextRender(personalizationPage);
-
-    const changePicturePage =
-        personalizationPage.shadowRoot.querySelector('settings-change-picture');
-    assertTrue(!!changePicturePage);
-    const deepLinkElement =
-        changePicturePage.shadowRoot.querySelector('#pictureList')
-            .shadowRoot.querySelector('#selector')
-            .$$('[class="iron-selected"]');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Account picture elem should be focused for settingId=503.');
-  });
 });
diff --git a/chrome/test/data/webui/settings/fake_language_settings_private.ts b/chrome/test/data/webui/settings/fake_language_settings_private.ts
index 1080eb4c..b3ce8f4 100644
--- a/chrome/test/data/webui/settings/fake_language_settings_private.ts
+++ b/chrome/test/data/webui/settings/fake_language_settings_private.ts
@@ -457,6 +457,14 @@
       type: chrome.settingsPrivate.PrefType.LIST,
       value: ['en-US'],
     },
+    {
+      key: 'translate_site_blocklist_with_time',
+      type: chrome.settingsPrivate.PrefType.LIST,
+      value: {
+        'ru.wikipedia.org': '13305315102292953',
+        'de.wikipedia.org': '13305315083099649',
+      },
+    },
     // Note: The real implementation of this pref is actually a dictionary
     // of {always translate: target}, however only the keys are needed for
     // testing.
diff --git a/chrome/test/data/webui/tab_search/tab_search_media_tabs_test.ts b/chrome/test/data/webui/tab_search/tab_search_media_tabs_test.ts
index 558177a..7d1148e 100644
--- a/chrome/test/data/webui/tab_search/tab_search_media_tabs_test.ts
+++ b/chrome/test/data/webui/tab_search/tab_search_media_tabs_test.ts
@@ -151,37 +151,17 @@
 

   test('Show media tab in Audio & Video section', async () => {

     await setupTest(

-        createProfileData({

-          windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-        }),

-        {alsoShowMediaTabsinOpenTabsSection: false});

+        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));

     // One media tab and two non-media tabs.

     assertEquals(3, queryRows().length);

     // "Audio and Video" and "Open Tabs" section should both exist.

     assertEquals(2, queryListTitle().length);

   });

 

-  test(

-      'Show media tab in Audio & Video section and Open Tabs section',

-      async () => {

-        await setupTest(

-            createProfileData({

-              windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-            }),

-            {alsoShowMediaTabsinOpenTabsSection: true});

-        // Only the two media tabs should be duplicated.

-        assertEquals(4, queryRows().length);

-        // "Audio and Video" and "Open Tabs" section should both exist.

-        assertEquals(2, queryListTitle().length);

-      });

 

   test('Tab is no longer media tab', async () => {

     await setupTest(

-        createProfileData({

-          windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-        }),

-        {alsoShowMediaTabsinOpenTabsSection: false});

-

+        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));

     const updatedTab: Tab = createTab({

       alertStates: [],

       tabId: 1,

@@ -200,40 +180,11 @@
     assertEquals(1, queryListTitle().length);

   });

 

-  test(

-      'Tab is no longer media tab with media tabs also shown in Open Tabs section ',

-      async () => {

-        await setupTest(

-            createProfileData({

-              windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-            }),

-            {alsoShowMediaTabsinOpenTabsSection: true});

-

-        const updatedTab: Tab = createTab({

-          alertStates: [],

-          tabId: 1,

-          lastActiveTimeTicks: {internalValue: BigInt(1)},

-        });

-

-        const tabUpdateInfo = {

-          inActiveWindow: true,

-          tab: updatedTab,

-        };

-        testProxy.getCallbackRouterRemote().tabUpdated(tabUpdateInfo);

-        await flushTasks();

-        // Three non-media tabs.

-        assertEquals(3, queryRows().length);

-        // Only "Open Tabs" section should exist.

-        assertEquals(1, queryListTitle().length);

-      });

 

   test('Non-media tab becomes media tab', async () => {

     await setupTest(

-        createProfileData({

-          windows: SAMPLE_WINDOW_DATA,

-        }),

-        {alsoShowMediaTabsinOpenTabsSection: false});

-

+        createProfileData({windows: SAMPLE_WINDOW_DATA}),

+    );

     assertEquals(1, queryListTitle().length);

 

     const updatedTab: Tab = createTab({

@@ -254,43 +205,10 @@
     assertEquals(2, queryListTitle().length);

   });

 

-  test(

-      'Non-media tab becomes media tab with media tabs also shown in Open Tabs section ',

-      async () => {

-        await setupTest(

-            createProfileData({

-              windows: SAMPLE_WINDOW_DATA,

-            }),

-            {alsoShowMediaTabsinOpenTabsSection: true});

-

-        assertEquals(1, queryListTitle().length);

-

-        const updatedTab: Tab = createTab({

-          alertStates: [TabAlertState.kAudioPlaying],

-          tabId: 5,

-          lastActiveTimeTicks: {internalValue: BigInt(1)},

-        });

-

-        const tabUpdateInfo = {

-          inActiveWindow: true,

-          tab: updatedTab,

-        };

-

-        testProxy.getCallbackRouterRemote().tabUpdated(tabUpdateInfo);

-        await flushTasks();

-        // One media tab is duplicated, the rest are non-media tabs.

-        assertEquals(7, queryRows().length);

-        // "Audio and Video" and "Open Tabs" section should both exist.

-        assertEquals(2, queryListTitle().length);

-      });

 

   test('Search for media tab', async () => {

     await setupTest(

-        createProfileData({

-          windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-        }),

-        {alsoShowMediaTabsinOpenTabsSection: false});

-

+        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));

     const searchField = tabSearchApp.$.searchField;

     searchField.setValue('google');

     await flushTasks();

@@ -298,21 +216,4 @@
     assertEquals(1, queryListTitle().length);

     assertEquals(2, queryRows().length);

   });

-

-  test(

-      'Search for media tab with media tabs also shown in Open Tabs section',

-      async () => {

-        await setupTest(

-            createProfileData({

-              windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,

-            }),

-            {alsoShowMediaTabsinOpenTabsSection: true});

-

-        const searchField = tabSearchApp.$.searchField;

-        searchField.setValue('google');

-        await flushTasks();

-        // No media tabs section when there is a search query.

-        assertEquals(1, queryListTitle().length);

-        assertEquals(2, queryRows().length);

-      });

 });

diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 597ed7aa..f7dd358 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -713,13 +713,7 @@
 
 #if BUILDFLAG(CHROMIUM_BRANDING) || BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #if !defined(COMPONENT_BUILD)
-// Disabled on Windows due to high flake rate; see https://crbug.com/1341471.
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_SelfUpdateFromOldReal DISABLED_SelfUpdateFromOldReal
-#else
-#define MAYBE_SelfUpdateFromOldReal SelfUpdateFromOldReal
-#endif
-TEST_F(IntegrationTest, MAYBE_SelfUpdateFromOldReal) {
+TEST_F(IntegrationTest, SelfUpdateFromOldReal) {
   ScopedServer test_server(test_commands_);
 
   SetupRealUpdaterLowerVersion();
@@ -745,14 +739,7 @@
 #endif
 #endif
 
-// TODO(crbug.com/1336591) - enable test after investigating crbug.com/1336591
-// or open a new crbug for debugging this test if it is the culprit.
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_UpdateServiceStress DISABLED_UpdateServiceStress
-#else
-#define MAYBE_UpdateServiceStress UpdateServiceStress
-#endif
-TEST_F(IntegrationTest, MAYBE_UpdateServiceStress) {
+TEST_F(IntegrationTest, UpdateServiceStress) {
   Install();
   ExpectInstalled();
   StressUpdateService();
diff --git a/chrome/updater/update_service_impl.cc b/chrome/updater/update_service_impl.cc
index 164fa85e..f40867b 100644
--- a/chrome/updater/update_service_impl.cc
+++ b/chrome/updater/update_service_impl.cc
@@ -395,11 +395,32 @@
           priority, UpdateService::PolicySameVersionUpdate::kNotAllowed));
 }
 
+void UpdateServiceImpl::GetDMPolicies(base::OnceClosure callback) {
+  VLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::MakeRefCounted<DeviceManagementTask>(config_, main_task_runner_)
+      ->RunRegisterDevice(
+          base::BindOnce(&DeviceManagementTask::RunFetchPolicy,
+                         base::MakeRefCounted<DeviceManagementTask>(
+                             config_, main_task_runner_),
+                         std::move(callback)));
+}
+
 void UpdateServiceImpl::UpdateAll(StateChangeCallback state_update,
                                   Callback callback) {
   VLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  GetDMPolicies(base::BindOnce(&UpdateServiceImpl::UpdateAllInternal, this,
+                               state_update, std::move(callback)));
+}
+
+void UpdateServiceImpl::UpdateAllInternal(StateChangeCallback state_update,
+                                          Callback callback) {
+  VLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   const auto app_ids = persisted_data_->GetAppIds();
   DCHECK(base::Contains(app_ids, kUpdaterAppId));
 
@@ -436,6 +457,21 @@
   VLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  GetDMPolicies(base::BindOnce(
+      &UpdateServiceImpl::UpdateInternal, this, app_id, install_data_index,
+      priority, policy_same_version_update, state_update, std::move(callback)));
+}
+
+void UpdateServiceImpl::UpdateInternal(
+    const std::string& app_id,
+    const std::string& install_data_index,
+    Priority priority,
+    PolicySameVersionUpdate policy_same_version_update,
+    StateChangeCallback state_update,
+    Callback callback) {
+  VLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   int policy = kPolicyEnabled;
   if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
     HandleUpdateDisabledByPolicy(app_id, policy, false, state_update,
@@ -457,6 +493,19 @@
                                 Priority priority,
                                 StateChangeCallback state_update,
                                 Callback callback) {
+  VLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  GetDMPolicies(base::BindOnce(&UpdateServiceImpl::InstallInternal, this,
+                               registration, install_data_index, priority,
+                               state_update, std::move(callback)));
+}
+
+void UpdateServiceImpl::InstallInternal(const RegistrationRequest& registration,
+                                        const std::string& install_data_index,
+                                        Priority priority,
+                                        StateChangeCallback state_update,
+                                        Callback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   VLOG(1) << __func__;
   int policy = kPolicyEnabled;
@@ -510,6 +559,23 @@
   VLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  GetDMPolicies(base::BindOnce(&UpdateServiceImpl::RunInstallerInternal, this,
+                               app_id, installer_path, install_args,
+                               install_data, install_settings, state_update,
+                               std::move(callback)));
+}
+
+void UpdateServiceImpl::RunInstallerInternal(
+    const std::string& app_id,
+    const base::FilePath& installer_path,
+    const std::string& install_args,
+    const std::string& install_data,
+    const std::string& install_settings,
+    StateChangeCallback state_update,
+    Callback callback) {
+  VLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   int policy = kPolicyEnabled;
   if (IsUpdateDisabledByPolicy(app_id, Priority::kForeground, true, policy)) {
     HandleUpdateDisabledByPolicy(app_id, policy, true, state_update,
diff --git a/chrome/updater/update_service_impl.h b/chrome/updater/update_service_impl.h
index f6c159b..8ea194f 100644
--- a/chrome/updater/update_service_impl.h
+++ b/chrome/updater/update_service_impl.h
@@ -19,6 +19,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
+class FilePath;
 class SequencedTaskRunner;
 class Version;
 }  // namespace base
@@ -84,6 +85,32 @@
   // Installs applications in the wake task based on the ForceInstalls policy.
   void ForceInstall(StateChangeCallback state_update, Callback callback);
 
+  // `GetDMPolicies` refreshes the DM registration and policies, and is called
+  // before all installs and updates.
+  void GetDMPolicies(base::OnceClosure callback);
+
+  // After `GetDMPolicies` completes, it calls on these `Internal` method to do
+  // the actual install or update.
+  void UpdateAllInternal(StateChangeCallback state_update, Callback callback);
+  void UpdateInternal(const std::string& app_id,
+                      const std::string& install_data_index,
+                      Priority priority,
+                      PolicySameVersionUpdate policy_same_version_update,
+                      StateChangeCallback state_update,
+                      Callback callback);
+  void InstallInternal(const RegistrationRequest& registration,
+                       const std::string& install_data_index,
+                       Priority priority,
+                       StateChangeCallback state_update,
+                       Callback callback);
+  void RunInstallerInternal(const std::string& app_id,
+                            const base::FilePath& installer_path,
+                            const std::string& install_args,
+                            const std::string& install_data,
+                            const std::string& install_settings,
+                            StateChangeCallback state_update,
+                            Callback callback);
+
   bool IsUpdateDisabledByPolicy(const std::string& app_id,
                                 Priority priority,
                                 bool is_install,
diff --git a/chromeos/ash/components/network/OWNERS b/chromeos/ash/components/network/OWNERS
index 1cc59240..d1e3b95 100644
--- a/chromeos/ash/components/network/OWNERS
+++ b/chromeos/ash/components/network/OWNERS
@@ -1,5 +1,6 @@
 azeemarshad@chromium.org
+chadduffin@chromium.org
+jiajunz@google.com
 khorimoto@chromium.org
 stevenjb@chromium.org
 tbarzic@chromium.org
-jiajunz@google.com
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index 2b0204a..96837da 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -34,10 +34,6 @@
 // TODO(tbarzic): Add payment portal method values to shill/dbus-constants.
 constexpr char kPaymentPortalMethodPost[] = "POST";
 
-// TODO(b/169939319): Use shill constant once it lands.
-const char kPortalDetectionFailedStatusCodeProperty[] =
-    "PortalDetectionFailedStatusCode";
-
 // |dict| may be an empty value, in which case return an empty string.
 std::string GetStringFromDictionary(const base::Value& dict, const char* key) {
   const std::string* stringp =
@@ -646,7 +642,7 @@
 
 void NetworkState::UpdateCaptivePortalState(const base::Value& properties) {
   int status_code =
-      properties.FindIntKey(kPortalDetectionFailedStatusCodeProperty)
+      properties.FindIntKey(shill::kPortalDetectionFailedStatusCodeProperty)
           .value_or(0);
   if (connection_state_ == shill::kStateNoConnectivity) {
     shill_portal_state_ = PortalState::kNoInternet;
diff --git a/chromeos/ash/components/network/network_state_handler.cc b/chromeos/ash/components/network/network_state_handler.cc
index 57e4114..4225ace0 100644
--- a/chromeos/ash/components/network/network_state_handler.cc
+++ b/chromeos/ash/components/network/network_state_handler.cc
@@ -18,7 +18,7 @@
 #include "base/guid.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -1772,9 +1772,9 @@
         ++shared;
     }
   }
-  UMA_HISTOGRAM_COUNTS_100("Networks.Visible", visible);
-  UMA_HISTOGRAM_COUNTS_100("Networks.RememberedShared", shared);
-  UMA_HISTOGRAM_COUNTS_100("Networks.RememberedUnshared", unshared);
+  base::UmaHistogramCounts100("Networks.Visible", visible);
+  base::UmaHistogramCounts100("Networks.RememberedShared", shared);
+  base::UmaHistogramCounts100("Networks.RememberedUnshared", unshared);
 }
 
 void NetworkStateHandler::DefaultNetworkServiceChanged(
@@ -2073,28 +2073,92 @@
   notifying_network_observers_ = true;
   for (auto& observer : observers_)
     observer.DefaultNetworkChanged(default_network);
+  notifying_network_observers_ = false;
 
+  UpdatePortalStateAndNotify(default_network);
+}
+
+void NetworkStateHandler::UpdatePortalStateAndNotify(
+    const NetworkState* default_network) {
+  NetworkState::PortalState new_portal_state;
+  std::string new_default_network_path;
   if (default_network &&
       (default_network->shill_portal_state() != default_network_portal_state_ ||
        default_network->proxy_config() != default_network_proxy_config_)) {
-    default_network_portal_state_ = default_network->shill_portal_state();
+    new_portal_state = default_network->shill_portal_state();
+    new_default_network_path = default_network->path();
     default_network_proxy_config_ = default_network->proxy_config().Clone();
-    NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: "
-                   << default_network_portal_state_;
-    for (auto& observer : observers_) {
-      observer.PortalStateChanged(default_network,
-                                  default_network_portal_state_);
-    }
   } else if (!default_network && (default_network_portal_state_ !=
                                       NetworkState::PortalState::kUnknown ||
                                   !default_network_proxy_config_.is_none())) {
-    default_network_portal_state_ = NetworkState::PortalState::kUnknown;
+    new_portal_state = NetworkState::PortalState::kUnknown;
     default_network_proxy_config_ = base::Value();
-    NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: Unknown (no network)";
-    for (auto& observer : observers_)
-      observer.PortalStateChanged(nullptr, NetworkState::PortalState::kUnknown);
+  } else {
+    // No portal state changes.
+    return;
   }
-  notifying_network_observers_ = false;
+
+  // Update metrics.
+  if (new_default_network_path != default_network_path_) {
+    // When the default network changes, update time histograms with a 0 result
+    // to indicate a failure to transition to online.
+    if (time_in_portal_) {
+      SendPortalHistogramTimes(base::TimeDelta());
+      time_in_portal_.reset();
+    }
+  } else {
+    switch (new_portal_state) {
+      case NetworkState::PortalState::kUnknown:
+        // If we transition to an unknown state, update time histograms with a 0
+        // result to indicate a failure to transition to online.
+        if (time_in_portal_) {
+          SendPortalHistogramTimes(base::TimeDelta());
+          time_in_portal_.reset();
+        }
+        break;
+      case NetworkState::PortalState::kOnline:
+        if (time_in_portal_) {
+          SendPortalHistogramTimes(time_in_portal_->Elapsed());
+          time_in_portal_.reset();
+        }
+        break;
+      case NetworkState::PortalState::kPortalSuspected:
+        [[fallthrough]];
+      case NetworkState::PortalState::kPortal:
+        time_in_portal_ = base::ElapsedTimer();
+        break;
+      case NetworkState::PortalState::kProxyAuthRequired:
+        [[fallthrough]];
+      case NetworkState::PortalState::kNoInternet:
+        // We don't track these states, reset the timer.
+        time_in_portal_.reset();
+        break;
+    }
+  }
+
+  // Update the portal state after sending histograms.
+  default_network_portal_state_ = new_portal_state;
+
+  // Notify observers.
+  NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: "
+                 << GetLogName(default_network) << ": "
+                 << default_network_portal_state_;
+  for (auto& observer : observers_)
+    observer.PortalStateChanged(default_network, default_network_portal_state_);
+}
+
+void NetworkStateHandler::SendPortalHistogramTimes(base::TimeDelta elapsed) {
+  switch (default_network_portal_state_) {
+    case NetworkState::PortalState::kPortal:
+      base::UmaHistogramTimes("Network.RedirectFoundToOnlineTime", elapsed);
+      break;
+    case NetworkState::PortalState::kPortalSuspected:
+      base::UmaHistogramTimes("Network.PortalSuspectedToOnlineTime", elapsed);
+      break;
+    default:
+      // Previous state was not portalled, no times to report.
+      break;
+  }
 }
 
 bool NetworkStateHandler::ActiveNetworksChanged(
@@ -2239,6 +2303,9 @@
 
 void NetworkStateHandler::SetDefaultNetworkValues(const std::string& path,
                                                   bool metered) {
+  // If the default network changes, ensure that the portal state is updated.
+  if (!path.empty())
+    UpdatePortalStateAndNotify(GetNetworkState(path));
   default_network_path_ = path;
   default_network_is_metered_ = metered;
 }
diff --git a/chromeos/ash/components/network/network_state_handler.h b/chromeos/ash/components/network/network_state_handler.h
index d98b2cf..10c1f2a 100644
--- a/chromeos/ash/components/network/network_state_handler.h
+++ b/chromeos/ash/components/network/network_state_handler.h
@@ -15,12 +15,14 @@
 #include "base/gtest_prod_util.h"
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
+#include "base/timer/elapsed_timer.h"
 #include "chromeos/ash/components/network/managed_state.h"
 #include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_handler_callbacks.h"
 #include "chromeos/ash/components/network/network_state.h"
 #include "chromeos/ash/components/network/network_type_pattern.h"
 #include "chromeos/ash/components/network/shill_property_handler.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 class Location;
@@ -616,6 +618,15 @@
   // NotifyDefaultNetworkChanged.
   void OnNetworkConnectionStateChanged(NetworkState* network);
 
+  // Updates the cached portal state for the default network, sends portal
+  // timer metrics, and notifies observers of portal state changes.
+  void UpdatePortalStateAndNotify(const NetworkState* default_network);
+
+  // Send metrics for elapsed time from a redirect-found or portal-suspected
+  // to an online or non portal state. If the new state is not online then
+  // |elapsed| should be 0 to indicate a failure to transition to online.
+  void SendPortalHistogramTimes(base::TimeDelta elapsed);
+
   // Verifies the connection state of the default network. Returns false
   // if the connection state change should be ignored.
   bool VerifyDefaultNetworkConnectionStateChange(NetworkState* network);
@@ -751,6 +762,9 @@
   NetworkState::PortalState default_network_portal_state_ =
       NetworkState::PortalState::kUnknown;
 
+  // Tracks the time spent in a Portal or PortalSuspected state.
+  absl::optional<base::ElapsedTimer> time_in_portal_;
+
   // Tracks the default network proxy config for triggering PortalStateChanged.
   base::Value default_network_proxy_config_;
 
diff --git a/chromeos/ash/components/network/network_state_handler_unittest.cc b/chromeos/ash/components/network/network_state_handler_unittest.cc
index fb9de76f..c8fca8f 100644
--- a/chromeos/ash/components/network/network_state_handler_unittest.cc
+++ b/chromeos/ash/components/network/network_state_handler_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_clients.h"
@@ -40,6 +41,8 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
+using testing::ElementsAre;
+
 namespace ash {
 
 namespace {
@@ -1963,9 +1966,13 @@
 TEST_F(NetworkStateHandlerTest, SetNetworkChromePortalState) {
   RemoveEthernet();
 
+  base::HistogramTester histogram_tester;
   service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
                                     shill::kStateProperty,
                                     base::Value(shill::kStatePortalSuspected));
+  service_test_->SetServiceProperty(
+      kShillManagerClientStubDefaultWifi,
+      shill::kPortalDetectionFailedStatusCodeProperty, base::Value(300));
   base::RunLoop().RunUntilIdle();
 
   const NetworkState* network = network_state_handler_->GetNetworkState(
@@ -1989,10 +1996,17 @@
       kShillManagerClientStubDefaultWifi);
   EXPECT_EQ(NetworkState::PortalState::kPortalSuspected,
             network->GetPortalState());
+
+  EXPECT_THAT(histogram_tester.GetAllSamples("Network.CaptivePortalResult"),
+              ElementsAre(base::Bucket(
+                  NetworkState::PortalState::kPortalSuspected, 1)));
+  EXPECT_THAT(histogram_tester.GetAllSamples("Network.CaptivePortalStatusCode"),
+              ElementsAre(base::Bucket(300, 1)));
 }
 
 TEST_F(NetworkStateHandlerTest, PortalStateChanged) {
   RemoveEthernet();
+  test_observer_->reset_change_counts();
 
   // Set wifi1 to portal-suspected and ensure observer is triggered.
   service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
@@ -2030,6 +2044,62 @@
   EXPECT_EQ(test_observer_->portal_state_change_count(), 4u);
 }
 
+TEST_F(NetworkStateHandlerTest, PortalToOnline) {
+  RemoveEthernet();
+  test_observer_->reset_change_counts();
+  base::HistogramTester histogram_tester;
+
+  // Set wifi1 to redirect-found and ensure observer is triggered.
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kStateProperty,
+                                    base::Value(shill::kStateRedirectFound));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(test_observer_->default_network_portal_state(),
+            NetworkState::PortalState::kPortal);
+  EXPECT_EQ(test_observer_->portal_state_change_count(), 1u);
+
+  // Set wifi1 to online and ensure observer is triggered.
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(test_observer_->default_network_portal_state(),
+            NetworkState::PortalState::kOnline);
+  EXPECT_EQ(test_observer_->portal_state_change_count(), 2u);
+
+  // redirect-found -> online should update RedirectFoundToOnlineTime.
+  histogram_tester.ExpectTotalCount("Network.RedirectFoundToOnlineTime", 1);
+  histogram_tester.ExpectTotalCount("Network.PortalSuspectedToOnlineTime", 0);
+}
+
+TEST_F(NetworkStateHandlerTest, PortalSuspectedToOnline) {
+  RemoveEthernet();
+  test_observer_->reset_change_counts();
+  base::HistogramTester histogram_tester;
+
+  // Set wifi1 to portal-suspected and ensure observer is triggered.
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kStateProperty,
+                                    base::Value(shill::kStatePortalSuspected));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(test_observer_->default_network_portal_state(),
+            NetworkState::PortalState::kPortalSuspected);
+  EXPECT_EQ(test_observer_->portal_state_change_count(), 1u);
+
+  // Set wifi1 to online and ensure observer is triggered.
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(test_observer_->default_network_portal_state(),
+            NetworkState::PortalState::kOnline);
+  EXPECT_EQ(test_observer_->portal_state_change_count(), 2u);
+
+  // portal-suspected -> online should update PortalSuspectedToOnlineTime.
+  histogram_tester.ExpectTotalCount("Network.RedirectFoundToOnlineTime", 0);
+  histogram_tester.ExpectTotalCount("Network.PortalSuspectedToOnlineTime", 1);
+}
+
 TEST_F(NetworkStateHandlerTest, RequestUpdate) {
   // Request an update for kShillManagerClientStubDefaultWifi.
   EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.cc b/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.cc
index a2e85f74..a6f861d 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.h"
 
+#include "chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h"
+
 namespace ash::quick_start {
 
 FakeTargetDeviceConnectionBroker::Factory::Factory() = default;
@@ -11,7 +13,8 @@
 FakeTargetDeviceConnectionBroker::Factory::~Factory() = default;
 
 std::unique_ptr<TargetDeviceConnectionBroker>
-FakeTargetDeviceConnectionBroker::Factory::CreateInstance() {
+FakeTargetDeviceConnectionBroker::Factory::CreateInstance(
+    RandomSessionId session_id) {
   auto connection_broker = std::make_unique<FakeTargetDeviceConnectionBroker>();
   instances_.push_back(connection_broker.get());
   return std::move(connection_broker);
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.h b/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.h
index cae26106..0ce928f3 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.h
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/fake_target_device_connection_broker.h
@@ -13,6 +13,8 @@
 
 namespace ash::quick_start {
 
+class RandomSessionId;
+
 class FakeTargetDeviceConnectionBroker : public TargetDeviceConnectionBroker {
  public:
   class Factory : public TargetDeviceConnectionBrokerFactory {
@@ -29,7 +31,8 @@
     }
 
    private:
-    std::unique_ptr<TargetDeviceConnectionBroker> CreateInstance() override;
+    std::unique_ptr<TargetDeviceConnectionBroker> CreateInstance(
+        RandomSessionId session_id) override;
 
     std::vector<FakeTargetDeviceConnectionBroker*> instances_;
   };
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.cc b/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.cc
index 7c20277..0c60893 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.cc
@@ -15,6 +15,10 @@
   crypto::RandBytes(bytes_);
 }
 
+RandomSessionId::RandomSessionId(base::span<const uint8_t, kLength> bytes) {
+  std::copy(bytes.begin(), bytes.end(), bytes_.begin());
+}
+
 std::string RandomSessionId::ToString() const {
   return base::HexEncode(bytes_);
 }
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h b/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h
index fc3e3de..b06a08ab 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h
@@ -20,11 +20,12 @@
   static constexpr size_t kLength = 10;
 
   RandomSessionId();
+  explicit RandomSessionId(base::span<const uint8_t, kLength> bytes);
   RandomSessionId(RandomSessionId&) = default;
   RandomSessionId& operator=(RandomSessionId&) = default;
   ~RandomSessionId() = default;
 
-  base::span<const uint8_t, kLength> AsBytes() const { return bytes_; };
+  base::span<const uint8_t, kLength> AsBytes() const { return bytes_; }
 
   // Convert to hexadecimal.
   std::string ToString() const;
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.cc b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.cc
index 331d132e..18a6653c 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.h"
 
+#include "chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h"
 #include "chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h"
 
 namespace ash::quick_start {
@@ -11,11 +12,17 @@
 // static
 std::unique_ptr<TargetDeviceConnectionBroker>
 TargetDeviceConnectionBrokerFactory::Create() {
+  return Create(RandomSessionId());
+}
+
+// static
+std::unique_ptr<TargetDeviceConnectionBroker>
+TargetDeviceConnectionBrokerFactory::Create(RandomSessionId session_id) {
   if (test_factory_) {
-    return test_factory_->CreateInstance();
+    return test_factory_->CreateInstance(session_id);
   }
 
-  return std::make_unique<TargetDeviceConnectionBrokerImpl>();
+  return std::make_unique<TargetDeviceConnectionBrokerImpl>(session_id);
 }
 
 // static
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.h b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.h
index 94f45cf..e006f6d 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.h
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_factory.h
@@ -11,12 +11,18 @@
 
 namespace ash::quick_start {
 
+class RandomSessionId;
+
 // A factory class for creating instances of TargetDeviceConnectionBroker.
 // Calling code should use the static Create() method.
 class TargetDeviceConnectionBrokerFactory {
  public:
   static std::unique_ptr<TargetDeviceConnectionBroker> Create();
 
+  // A RandomSessionId may be provided in order to resume a connection.
+  static std::unique_ptr<TargetDeviceConnectionBroker> Create(
+      RandomSessionId session_id);
+
   static void SetFactoryForTesting(
       TargetDeviceConnectionBrokerFactory* test_factory);
 
@@ -28,7 +34,8 @@
   virtual ~TargetDeviceConnectionBrokerFactory();
 
  protected:
-  virtual std::unique_ptr<TargetDeviceConnectionBroker> CreateInstance() = 0;
+  virtual std::unique_ptr<TargetDeviceConnectionBroker> CreateInstance(
+      RandomSessionId session_id) = 0;
 
  private:
   static TargetDeviceConnectionBrokerFactory* test_factory_;
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
index 980696c..9b2aec1 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h"
 #include "chromeos/ash/components/oobe_quick_start/connectivity/random_session_id.h"
@@ -40,15 +41,29 @@
 
 constexpr char kEndpointInfoDefaultDisplayName[] = "Chromebook";
 
+// Derive three decimal digits from the RandomSessionId.
+std::string GetDisplayNameSessionIdDigits(const RandomSessionId& session_id) {
+  base::span<const uint8_t, RandomSessionId::kLength> session_id_bytes =
+      session_id.AsBytes();
+  uint32_t high = session_id_bytes[0];
+  uint32_t low = session_id_bytes[1];
+  uint32_t x = (high << 8) + low;
+  return base::NumberToString(x % 1000);
+}
+
 // The display name must:
 // - Be a variable-length string of utf-8 bytes
 // - Be at most 18 bytes
 // - If less than 18 bytes, must be null-terminated
 std::vector<uint8_t> GetEndpointInfoDisplayNameBytes(
-    RandomSessionId session_id) {
-  // TODO(b/234655072): Append session id to display name, vary name based on
-  // device type, e.g. Chromebook, Chromebox, Chromebase, Chromebit, etc.
+    const RandomSessionId& session_id) {
   std::string display_name = kEndpointInfoDefaultDisplayName;
+  std::string suffix = " (" + GetDisplayNameSessionIdDigits(session_id) + ")";
+
+  // TODO(b/234655072): Before appending suffix, vary |display_name| based on
+  // device type, e.g. Chromebook, Chromebox, Chromebase, etc.
+  display_name += suffix;
+
   std::vector<uint8_t> display_name_bytes(display_name.begin(),
                                           display_name.end());
   display_name_bytes.push_back(0);
@@ -79,7 +94,9 @@
     TargetDeviceConnectionBrokerImpl::BluetoothAdapterFactoryWrapper::
         bluetooth_adapter_factory_wrapper_for_testing_ = nullptr;
 
-TargetDeviceConnectionBrokerImpl::TargetDeviceConnectionBrokerImpl() {
+TargetDeviceConnectionBrokerImpl::TargetDeviceConnectionBrokerImpl(
+    RandomSessionId session_id)
+    : random_session_id_(session_id) {
   GetBluetoothAdapter();
 }
 
@@ -149,7 +166,8 @@
     return;
   }
 
-  VLOG(1) << "Starting advertising with session id " << random_session_id_;
+  VLOG(1) << "Starting advertising with session id " << random_session_id_
+          << " (" << GetDisplayNameSessionIdDigits(random_session_id_) << ")";
 
   fast_pair_advertiser_ =
       FastPairAdvertiser::Factory::Create(bluetooth_adapter_);
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
index bf869182..69bba0c3 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h
@@ -40,7 +40,7 @@
         bluetooth_adapter_factory_wrapper_for_testing_;
   };
 
-  TargetDeviceConnectionBrokerImpl();
+  explicit TargetDeviceConnectionBrokerImpl(RandomSessionId session_id);
   TargetDeviceConnectionBrokerImpl(TargetDeviceConnectionBrokerImpl&) = delete;
   TargetDeviceConnectionBrokerImpl& operator=(
       TargetDeviceConnectionBrokerImpl&) = delete;
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
index b1b3116..35d1f42 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/ash/components/oobe_quick_start/connectivity/target_device_connection_broker_impl.h"
 
+#include <array>
+
 #include "base/bind.h"
 #include "base/test/task_environment.h"
 #include "chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h"
@@ -20,6 +22,12 @@
 
 constexpr size_t kMaxEndpointInfoDisplayNameLength = 18;
 
+// 10 random bytes to use as the RandomSessionId. The corresponding display name
+// code is (0x135e % 1000) = 958.
+constexpr std::array<uint8_t, 10> kRandomSessionId = {
+    0x13, 0x5e, 0xfb, 0x0f, 0x3a, 0x20, 0x06, 0xbd, 0xbf, 0x95};
+constexpr char kExpectedEndpointInfoDisplayName[] = "Chromebook (958)";
+
 using testing::NiceMock;
 
 // Allows us to delay returning a Bluetooth adapter until after ReturnAdapter()
@@ -167,8 +175,10 @@
   }
 
   void CreateConnectionBroker() {
+    RandomSessionId session_id(kRandomSessionId);
     connection_broker_ =
-        ash::quick_start::TargetDeviceConnectionBrokerFactory::Create();
+        ash::quick_start::TargetDeviceConnectionBrokerFactory::Create(
+            session_id);
   }
 
   void FinishFetchingBluetoothAdapter() {
@@ -412,7 +422,7 @@
   }
   std::string display_name =
       std::string(display_name_bytes.begin(), display_name_bytes.end());
-  EXPECT_EQ("Chromebook", display_name);
+  EXPECT_EQ(kExpectedEndpointInfoDisplayName, display_name);
   i += j;
 
   ASSERT_GT(endpoint_info.size(), i);
diff --git a/chromeos/crosapi/mojom/tts.mojom b/chromeos/crosapi/mojom/tts.mojom
index d85d017b..515b2e8 100644
--- a/chromeos/crosapi/mojom/tts.mojom
+++ b/chromeos/crosapi/mojom/tts.mojom
@@ -5,6 +5,8 @@
 module crosapi.mojom;
 
 import "mojo/public/mojom/base/unguessable_token.mojom";
+import "mojo/public/mojom/base/values.mojom";
+import "url/mojom/url.mojom";
 
 // Events sent back from the TTS engine indicating the progress.
 [Stable, Extensible]
@@ -47,6 +49,68 @@
   string native_voice_identifier;
 };
 
+// Represents a Tts utterance.
+[Stable]
+struct TtsUtterance {
+  // Unique id of utterance that helps route a Tts event back to the client.
+  // This is needed when speaking an Ash utterance with a Lacros voice.
+  // |utterance_id| is created by TtsController (in Ash) and passed to TtsEngine
+  // extension API (in Lacros) onSpeak event, ttsEngine extension (in Lacros)
+  // will pass it back in sendTtsEvent, which will eventually pass it to
+  // TtsController::OnTtsEvent (in Ash), so that TtsController (in Ash) can
+  // track and process the async TtsEvent for the utterances.
+  int32 utterance_id;
+
+  // Text to speak.
+  string text;
+
+  // Language to use for synthesis.
+  string lang;
+
+  // Name of the voice to use for synthesis.
+  string voice_name;
+
+  // Speaking volume.
+  double volume;
+
+  // Speaking rate.
+  double rate;
+
+  // Speaking pitch.
+  double pitch;
+
+  // Extension id of the speech engine to use.
+  string engine_id;
+
+  // If false, enqueues this utterance if TTS is already in progress. Otherwise,
+  // interrupts any current speech and flushes the speech queue before speaking
+  // this new utterance.
+  bool should_clear_queue;
+
+  // TTS event types that the client is interested in listening to.
+  array<TtsEventType> desired_event_types;
+
+  // The TTS event types the voice must support.
+  array<TtsEventType> required_event_types;
+
+  // The source engine's ID of this utterance, so that it can route the events
+  // back to the correct tts.speak call.
+  uint32 src_id;
+
+  // The URL of the page from which the speech request is called.
+  url.mojom.Url src_url;
+
+  // Options passed from tts.speak argument, which will be passed to ttsEngine
+  // onSpeak event with sanitizing process.
+  mojo_base.mojom.DictionaryValue options;
+
+  // True if the utterance is associated with a WebContents.
+  bool was_created_with_web_contents;
+
+  // Unique id of the browser context of the utterance.
+  mojo_base.mojom.UnguessableToken browser_context_id;
+};
+
 // Interface for Tts, implemented in ash-chrome. Used by lacros-chrome to
 // communicate with ash TtsController to send the voice data and
 // speech requests to ash.
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 1e98dd1..da20873e 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-106-5195.19-1659952549-benchmark-106.0.5237.0-r1.orderfile.xz
+chromeos-chrome-orderfile-field-106-5226.0-1660558034-benchmark-106.0.5241.0-r1.orderfile.xz
diff --git a/chromeos/ui/base/window_properties.cc b/chromeos/ui/base/window_properties.cc
index 2217222..432b3c6b 100644
--- a/chromeos/ui/base/window_properties.cc
+++ b/chromeos/ui/base/window_properties.cc
@@ -45,6 +45,8 @@
 
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsShowingInOverviewKey, false)
 
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kShouldHaveHighlightBorderOverlay, false)
+
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kWindowManagerManagesOpacityKey, false)
 
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::u16string,
diff --git a/chromeos/ui/base/window_properties.h b/chromeos/ui/base/window_properties.h
index 2ad44ea..c8b4bf3 100644
--- a/chromeos/ui/base/window_properties.h
+++ b/chromeos/ui/base/window_properties.h
@@ -108,6 +108,10 @@
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
 extern const ui::ClassProperty<bool>* const kIsShowingInOverviewKey;
 
+// A property to indicate if a window should have a highlight border overlay.
+COMPONENT_EXPORT(CHROMEOS_UI_BASE)
+extern const ui::ClassProperty<bool>* const kShouldHaveHighlightBorderOverlay;
+
 // A property key to tell if the window's opacity should be managed by WM.
 COMPONENT_EXPORT(CHROMEOS_UI_BASE)
 extern const ui::ClassProperty<bool>* const kWindowManagerManagesOpacityKey;
diff --git a/chromeos/ui/frame/multitask_menu/multitask_button.cc b/chromeos/ui/frame/multitask_menu/multitask_button.cc
index 231eba1..1b7c0742 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_button.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_button.cc
@@ -5,6 +5,7 @@
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_constants.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/controls/focus_ring.h"
@@ -82,4 +83,7 @@
   views::Button::OnThemeChanged();
 }
 
+BEGIN_METADATA(MultitaskBaseButton, views::Button)
+END_METADATA
+
 }  // namespace chromeos
diff --git a/chromeos/ui/frame/multitask_menu/multitask_button.h b/chromeos/ui/frame/multitask_menu/multitask_button.h
index 148326c..fdbb78e 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_button.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_button.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_BUTTON_H_
 #define CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_BUTTON_H_
 
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/controls/button/button.h"
 
 namespace chromeos {
@@ -12,6 +13,8 @@
 // The base button for multitask menu to create Full Screen and Float buttons.
 class MultitaskBaseButton : public views::Button {
  public:
+  METADATA_HEADER(MultitaskBaseButton);
+
   // The types of single operated multitask button.
   enum class Type {
     kFull,   // The button that turn the window to full screen mode.
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu.cc b/chromeos/ui/frame/multitask_menu/multitask_menu.cc
index 5be13c966..c69634d0 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu.cc
@@ -9,6 +9,7 @@
 #include "base/check.h"
 #include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_view.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/layout/table_layout.h"
 
 namespace chromeos {
@@ -34,8 +35,6 @@
   set_close_on_deactivate(true);
   SetPreferredSize(gfx::Size(KMultitaskMenuWidth, kMultitaskMenuHeight));
   SetUseDefaultFillLayout(true);
-  // TODO(sammiequon/sophiewen): Check that `CalculatePreferredSize` gets the
-  // size based on the child button sizes.
 
   // Must be initialized after setting bounds.
   multitask_menu_view_ = AddChildView(std::make_unique<MultitaskMenuView>(
@@ -93,4 +92,8 @@
   if (bubble_widget_ && !bubble_widget_->IsClosed())
     bubble_widget_->CloseNow();
 }
+
+BEGIN_METADATA(MultitaskMenu, views::BubbleDialogDelegateView)
+END_METADATA
+
 }  // namespace chromeos
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu.h b/chromeos/ui/frame/multitask_menu/multitask_menu.h
index c59b6c47d..89860f2 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_view.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
 namespace views {
@@ -23,6 +24,8 @@
     : public views::BubbleDialogDelegateView,
       public views::WidgetObserver {
  public:
+  METADATA_HEADER(MultitaskMenu);
+
   MultitaskMenu(views::View* anchor, aura::Window* parent_window);
 
   MultitaskMenu(const MultitaskMenu&) = delete;
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
index ca4afe45..d70a5c5f 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
@@ -13,6 +13,7 @@
 #include "ui/aura/window.h"
 #include "ui/base/default_style.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/label.h"
@@ -128,4 +129,7 @@
   on_any_button_pressed_.Run();
 }
 
+BEGIN_METADATA(MultitaskMenuView, View)
+END_METADATA
+
 }  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
index 9db4eb5..6d5e989 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/split_button.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 
 namespace views {
 class View;
@@ -22,6 +23,8 @@
 class COMPONENT_EXPORT(CHROMEOS_UI_FRAME) MultitaskMenuView
     : public views::View {
  public:
+  METADATA_HEADER(MultitaskMenuView);
+
   MultitaskMenuView(aura::Window* window,
                     base::RepeatingClosure on_any_button_pressed);
 
diff --git a/chromeos/ui/frame/multitask_menu/split_button.cc b/chromeos/ui/frame/multitask_menu/split_button.cc
index f66fab9e..a9e7092 100644
--- a/chromeos/ui/frame/multitask_menu/split_button.cc
+++ b/chromeos/ui/frame/multitask_menu/split_button.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -61,6 +62,9 @@
   canvas->DrawRoundRect(pattern_bounds, kButtonCornerRadius, pattern_flags);
 }
 
+BEGIN_METADATA(SplitButton, views::Button)
+END_METADATA
+
 SplitButtonView::SplitButtonView(
     SplitButton::SplitButtonType type,
     views::Button::PressedCallback primary_callback,
@@ -135,4 +139,7 @@
   views::View::OnThemeChanged();
 }
 
+BEGIN_METADATA(SplitButtonView, View)
+END_METADATA
+
 }  // namespace chromeos
diff --git a/chromeos/ui/frame/multitask_menu/split_button.h b/chromeos/ui/frame/multitask_menu/split_button.h
index 6518295..b5979820 100644
--- a/chromeos/ui/frame/multitask_menu/split_button.h
+++ b/chromeos/ui/frame/multitask_menu/split_button.h
@@ -7,6 +7,7 @@
 
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_constants.h"
 
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/layout/box_layout_view.h"
 #include "ui/views/view.h"
@@ -16,6 +17,7 @@
 // A button used for SplitButtonView to trigger primary/secondary split.
 class SplitButton : public views::Button {
  public:
+  METADATA_HEADER(SplitButton);
   enum class SplitButtonType {
     kHalfButtons,
     kPartialButtons,
@@ -49,6 +51,8 @@
 // A button view with 2 divided buttons, primary and secondary.
 class SplitButtonView : public views::BoxLayoutView {
  public:
+  METADATA_HEADER(SplitButtonView);
+
   SplitButtonView(SplitButton::SplitButtonType type,
                   views::Button::PressedCallback primary_callback,
                   views::Button::PressedCallback secondary_callback);
diff --git a/components/autofill/core/browser/autofill_external_delegate.cc b/components/autofill/core/browser/autofill_external_delegate.cc
index d86f018..f6168116 100644
--- a/components/autofill/core/browser/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/autofill_external_delegate.cc
@@ -213,7 +213,7 @@
 void AutofillExternalDelegate::DidSelectSuggestion(
     const std::u16string& value,
     int frontend_id,
-    const std::string& backend_id) {
+    const Suggestion::BackendId& backend_id) {
   ClearPreviewedForm();
 
   // Only preview the data if it is a profile or a virtual card.
@@ -225,7 +225,7 @@
                                                  value);
   } else if (frontend_id == POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY) {
     manager_->FillOrPreviewVirtualCardInformation(
-        mojom::RendererFormDataAction::kPreview, backend_id, query_id_,
+        mojom::RendererFormDataAction::kPreview, backend_id.value(), query_id_,
         query_form_, query_field_);
   }
 }
@@ -278,10 +278,11 @@
     // POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY as a frontend_id. In this case,
     // the payload contains the backend id, which is a GUID that identifies the
     // actually chosen credit card.
-    DCHECK(absl::holds_alternative<std::string>(payload));
+    DCHECK(absl::holds_alternative<Suggestion::BackendId>(payload));
     manager_->FillOrPreviewVirtualCardInformation(
-        mojom::RendererFormDataAction::kFill, absl::get<std::string>(payload),
-        query_id_, query_form_, query_field_);
+        mojom::RendererFormDataAction::kFill,
+        absl::get<Suggestion::BackendId>(payload).value(), query_id_,
+        query_form_, query_field_);
   } else if (frontend_id == POPUP_ITEM_ID_SEE_PROMO_CODE_DETAILS) {
     DCHECK(absl::holds_alternative<GURL>(payload));
     manager_->OnSeePromoCodeOfferDetailsSelected(absl::get<GURL>(payload),
diff --git a/components/autofill/core/browser/autofill_external_delegate.h b/components/autofill/core/browser/autofill_external_delegate.h
index 40dce87..7999bcd 100644
--- a/components/autofill/core/browser/autofill_external_delegate.h
+++ b/components/autofill/core/browser/autofill_external_delegate.h
@@ -48,7 +48,7 @@
   void OnPopupSuppressed() override;
   void DidSelectSuggestion(const std::u16string& value,
                            int frontend_id,
-                           const std::string& backend_id) override;
+                           const Suggestion::BackendId& backend_id) override;
   void DidAcceptSuggestion(const std::u16string& value,
                            int frontend_id,
                            const Suggestion::Payload& payload,
diff --git a/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
index 45cdd7a..24133ca 100644
--- a/components/autofill/core/browser/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
@@ -609,7 +609,8 @@
   EXPECT_CALL(*browser_autofill_manager_, FillOrPreviewForm(_, _, _, _, _))
       .Times(0);
   EXPECT_CALL(*autofill_driver_, RendererShouldClearPreviewedForm()).Times(1);
-  external_delegate_->DidSelectSuggestion(std::u16string(), -1, std::string());
+  external_delegate_->DidSelectSuggestion(std::u16string(), -1,
+                                          Suggestion::BackendId());
 
   // Ensure it doesn't try to fill the form in with the negative id.
   EXPECT_CALL(autofill_client_,
@@ -649,7 +650,8 @@
   EXPECT_CALL(*autofill_driver_,
               RendererShouldPreviewFieldWithValue(field_id_, promo_code_value));
   external_delegate_->DidSelectSuggestion(
-      promo_code_value, POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY, "");
+      promo_code_value, POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY,
+      Suggestion::BackendId());
   EXPECT_CALL(autofill_client_,
               HideAutofillPopup(PopupHidingReason::kAcceptSuggestion));
   EXPECT_CALL(*autofill_driver_,
@@ -664,7 +666,8 @@
 TEST_F(AutofillExternalDelegateUnitTest,
        ExternalDelegateMerchantPromoCodeSuggestionsFooter) {
   const GURL gurl{"https://example.com/"};
-  absl::variant<std::string, GURL> payload(absl::in_place_type<GURL>, gurl);
+  absl::variant<Suggestion::BackendId, GURL> payload(absl::in_place_type<GURL>,
+                                                     gurl);
   EXPECT_CALL(autofill_client_, OpenPromoCodeOfferDetailsURL(gurl));
   external_delegate_->DidAcceptSuggestion(
       u"baz foo", POPUP_ITEM_ID_SEE_PROMO_CODE_DETAILS, payload, 0);
@@ -678,12 +681,13 @@
   IssueOnQuery(123);
   EXPECT_CALL(*autofill_driver_, RendererShouldClearPreviewedForm()).Times(1);
   external_delegate_->DidSelectSuggestion(
-      u"baz foo", POPUP_ITEM_ID_PASSWORD_ENTRY, std::string());
+      u"baz foo", POPUP_ITEM_ID_PASSWORD_ENTRY, Suggestion::BackendId());
   EXPECT_CALL(*autofill_driver_, RendererShouldClearPreviewedForm()).Times(1);
   EXPECT_CALL(
       *browser_autofill_manager_,
       FillOrPreviewForm(mojom::RendererFormDataAction::kPreview, _, _, _, _));
-  external_delegate_->DidSelectSuggestion(u"baz foo", 1, std::string());
+  external_delegate_->DidSelectSuggestion(u"baz foo", 1,
+                                          Suggestion::BackendId());
 
   // Ensure selecting an autocomplete entry will cause any previews to
   // get cleared.
@@ -691,7 +695,7 @@
   EXPECT_CALL(*autofill_driver_, RendererShouldPreviewFieldWithValue(
                                      field_id_, std::u16string(u"baz foo")));
   external_delegate_->DidSelectSuggestion(
-      u"baz foo", POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY, std::string());
+      u"baz foo", POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY, Suggestion::BackendId());
 
   // Ensure selecting a virtual card entry will cause any previews to
   // get cleared.
@@ -700,7 +704,8 @@
               FillOrPreviewVirtualCardInformation(
                   mojom::RendererFormDataAction::kPreview, _, _, _, _));
   external_delegate_->DidSelectSuggestion(
-      std::u16string(), POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY, std::string());
+      std::u16string(), POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY,
+      Suggestion::BackendId());
 }
 
 // Test that the popup is hidden once we are done editing the autofill field.
@@ -885,8 +890,9 @@
                                               POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY))
       .Times(1);
   base::HistogramTester histogram_tester;
-  external_delegate_->DidAcceptSuggestion(
-      dummy_string, POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY, std::string(), 0);
+  external_delegate_->DidAcceptSuggestion(dummy_string,
+                                          POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY,
+                                          Suggestion::BackendId(), 0);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SuggestionAcceptedIndex.Autocomplete", 0, 1);
 
@@ -899,7 +905,7 @@
       .Times(1);
   external_delegate_->DidAcceptSuggestion(
       dummy_string, POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY,
-      absl::variant<std::string, GURL>(), 0);
+      absl::variant<Suggestion::BackendId, GURL>(), 0);
 }
 
 TEST_F(AutofillExternalDelegateUnitTest, ShouldShowGooglePayIcon) {
@@ -997,7 +1003,8 @@
               FillOrPreviewVirtualCardInformation(
                   mojom::RendererFormDataAction::kPreview, _, _, _, _));
   external_delegate_->DidSelectSuggestion(
-      std::u16string(), POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY, std::string());
+      std::u16string(), POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY,
+      Suggestion::BackendId());
 }
 
 // Tests that the prompt to show account cards shows up when the corresponding
diff --git a/components/autofill/core/browser/autofill_suggestion_generator.cc b/components/autofill/core/browser/autofill_suggestion_generator.cc
index 20c60866..83fe4c8d 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator.cc
+++ b/components/autofill/core/browser/autofill_suggestion_generator.cc
@@ -20,7 +20,6 @@
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/browser/ui/suggestion_selection.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
@@ -77,7 +76,7 @@
   if (autofill_field.Type().group() == FieldTypeGroup::kPhoneHome) {
     for (auto& suggestion : suggestions) {
       const AutofillProfile* profile = personal_data_->GetProfileByGUID(
-          suggestion.GetPayload<std::string>());
+          suggestion.GetPayload<Suggestion::BackendId>().value());
       if (profile) {
         const std::u16string phone_home_city_and_number =
             profile->GetInfo(PHONE_HOME_CITY_AND_NUMBER, app_locale);
@@ -92,7 +91,8 @@
 
   for (auto& suggestion : suggestions) {
     suggestion.frontend_id =
-        MakeFrontendId(std::string(), suggestion.GetPayload<std::string>());
+        MakeFrontendId(Suggestion::BackendId(),
+                       suggestion.GetPayload<Suggestion::BackendId>());
   }
 
   return suggestions;
@@ -173,7 +173,8 @@
   for (Suggestion& suggestion : suggestions) {
     if (suggestion.frontend_id == 0) {
       suggestion.frontend_id =
-          MakeFrontendId(suggestion.GetPayload<std::string>(), std::string());
+          MakeFrontendId(suggestion.GetPayload<Suggestion::BackendId>(),
+                         Suggestion::BackendId());
     }
   }
 
@@ -187,7 +188,7 @@
   for (const IBAN* iban : ibans) {
     Suggestion& suggestion = suggestions.emplace_back(iban->value());
     suggestion.frontend_id = POPUP_ITEM_ID_IBAN_ENTRY;
-    suggestion.payload = iban->guid();
+    suggestion.payload = Suggestion::BackendId(iban->guid());
     suggestion.main_text.value = iban->GetIdentifierStringForAutofillDisplay();
     if (!iban->nickname().empty())
       suggestion.label = iban->nickname();
@@ -208,7 +209,8 @@
     Suggestion& suggestion = suggestions.back();
     suggestion.label = base::ASCIIToUTF16(
         promo_code_offer->GetDisplayStrings().value_prop_text);
-    suggestion.payload = base::NumberToString(promo_code_offer->GetOfferId());
+    suggestion.payload = Suggestion::BackendId(
+        base::NumberToString(promo_code_offer->GetOfferId()));
     suggestion.frontend_id = POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY;
 
     // Every offer for a given merchant leads to the same GURL, so we grab the
@@ -294,8 +296,8 @@
 // profile IDs into a single integer.  Credit card IDs are sent in the high
 // word and profile IDs are sent in the low word.
 int AutofillSuggestionGenerator::MakeFrontendId(
-    const std::string& cc_backend_id,
-    const std::string& profile_backend_id) const {
+    const Suggestion::BackendId& cc_backend_id,
+    const Suggestion::BackendId& profile_backend_id) const {
   InternalId cc_int_id = BackendIdToInternalId(cc_backend_id);
   InternalId profile_int_id = BackendIdToInternalId(profile_backend_id);
 
@@ -315,16 +317,17 @@
 // the high word and profile IDs are stored in the low word.
 void AutofillSuggestionGenerator::SplitFrontendId(
     int frontend_id,
-    std::string* cc_backend_id,
-    std::string* profile_backend_id) const {
+    Suggestion::BackendId* cc_backend_id,
+    Suggestion::BackendId* profile_backend_id) const {
   InternalId cc_int_id =
       InternalId((frontend_id >> std::numeric_limits<uint16_t>::digits) &
                  std::numeric_limits<uint16_t>::max());
   InternalId profile_int_id =
       InternalId(frontend_id & std::numeric_limits<uint16_t>::max());
 
-  *cc_backend_id = InternalIdToBackendId(cc_int_id);
-  *profile_backend_id = InternalIdToBackendId(profile_int_id);
+  *cc_backend_id = Suggestion::BackendId(InternalIdToBackendId(cc_int_id));
+  *profile_backend_id =
+      Suggestion::BackendId(InternalIdToBackendId(profile_int_id));
 }
 
 Suggestion AutofillSuggestionGenerator::CreateCreditCardSuggestion(
@@ -338,7 +341,7 @@
   suggestion.main_text = Suggestion::Text(credit_card.GetInfo(type, app_locale),
                                           Suggestion::Text::IsPrimary(true));
   suggestion.icon = credit_card.CardIconStringForAutofillSuggestion();
-  suggestion.payload = credit_card.guid();
+  suggestion.payload = Suggestion::BackendId(credit_card.guid());
   suggestion.match = prefix_matched_suggestion ? Suggestion::PREFIX_MATCH
                                                : Suggestion::SUBSTRING_MATCH;
 
@@ -410,7 +413,7 @@
       DCHECK(server_duplicate_card);
       card_art_url_for_virtual_card_option =
           server_duplicate_card->card_art_url();
-      suggestion.payload = server_duplicate_card->guid();
+      suggestion.payload = Suggestion::BackendId(server_duplicate_card->guid());
     }
 
     suggestion.frontend_id = POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY;
@@ -493,16 +496,16 @@
 }
 
 InternalId AutofillSuggestionGenerator::BackendIdToInternalId(
-    const std::string& backend_id) const {
-  if (!base::IsValidGUID(backend_id))
+    const Suggestion::BackendId& backend_id) const {
+  if (!base::IsValidGUID(backend_id.value()))
     return InternalId(0);
 
-  const auto found = backend_to_int_map_.find(backend_id);
+  const auto found = backend_to_int_map_.find(backend_id.value());
   if (found == backend_to_int_map_.end()) {
     // Unknown one, make a new entry.
     InternalId int_id = InternalId(backend_to_int_map_.size() + 1);
-    backend_to_int_map_[backend_id] = int_id;
-    int_to_backend_map_[int_id] = backend_id;
+    backend_to_int_map_[backend_id.value()] = int_id;
+    int_to_backend_map_[int_id] = backend_id.value();
     return int_id;
   }
   return InternalId(found->second);
diff --git a/components/autofill/core/browser/autofill_suggestion_generator.h b/components/autofill/core/browser/autofill_suggestion_generator.h
index 94a39c653..914f689 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator.h
+++ b/components/autofill/core/browser/autofill_suggestion_generator.h
@@ -12,6 +12,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/types/strong_alias.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 
 namespace base {
 class Time;
@@ -28,7 +29,6 @@
 class FormStructure;
 class IBAN;
 class PersonalDataManager;
-struct Suggestion;
 
 using InternalId = base::StrongAlias<class InternalIdTag, int>;
 
@@ -83,11 +83,11 @@
 
   // Methods for packing and unpacking credit card and profile IDs for sending
   // and receiving to and from the renderer process.
-  int MakeFrontendId(const std::string& cc_backend_id,
-                     const std::string& profile_backend_id) const;
+  int MakeFrontendId(const Suggestion::BackendId& cc_backend_id,
+                     const Suggestion::BackendId& profile_backend_id) const;
   void SplitFrontendId(int frontend_id,
-                       std::string* cc_backend_id,
-                       std::string* profile_backend_id) const;
+                       Suggestion::BackendId* cc_backend_id,
+                       Suggestion::BackendId* profile_backend_id) const;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(AutofillSuggestionGeneratorTest,
@@ -136,7 +136,8 @@
   // Maps suggestion backend ID to and from an integer identifying it. Two of
   // these intermediate integers are packed by MakeFrontendID to make the IDs
   // that this class generates for the UI and for IPC.
-  InternalId BackendIdToInternalId(const std::string& backend_id) const;
+  InternalId BackendIdToInternalId(
+      const Suggestion::BackendId& backend_id) const;
   std::string InternalIdToBackendId(InternalId int_id) const;
 
   // autofill_client_ and the generator are both one per tab, and have the same
diff --git a/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc b/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
index 4ca6cadb..4c7ffa1 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
@@ -291,8 +291,8 @@
 
   EXPECT_EQ(virtual_card_suggestion.frontend_id,
             POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY);
-  EXPECT_EQ(absl::get<std::string>(virtual_card_suggestion.payload),
-            "00000000-0000-0000-0000-000000000001");
+  EXPECT_EQ(virtual_card_suggestion.GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("00000000-0000-0000-0000-000000000001"));
 
   Suggestion real_card_suggestion =
       suggestion_generator()->CreateCreditCardSuggestion(
@@ -301,8 +301,8 @@
           "");
 
   EXPECT_EQ(real_card_suggestion.frontend_id, 0);
-  EXPECT_EQ(absl::get<std::string>(real_card_suggestion.payload),
-            "00000000-0000-0000-0000-000000000001");
+  EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("00000000-0000-0000-0000-000000000001"));
 }
 
 TEST_F(AutofillSuggestionGeneratorTest, CreateCreditCardSuggestion_LocalCard) {
@@ -330,8 +330,8 @@
 
   EXPECT_EQ(virtual_card_suggestion.frontend_id,
             POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY);
-  EXPECT_EQ(absl::get<std::string>(virtual_card_suggestion.payload),
-            "00000000-0000-0000-0000-000000000001");
+  EXPECT_EQ(virtual_card_suggestion.GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("00000000-0000-0000-0000-000000000001"));
 
   Suggestion real_card_suggestion =
       suggestion_generator()->CreateCreditCardSuggestion(
@@ -340,8 +340,8 @@
           "");
 
   EXPECT_EQ(real_card_suggestion.frontend_id, 0);
-  EXPECT_EQ(absl::get<std::string>(real_card_suggestion.payload),
-            "00000000-0000-0000-0000-000000000002");
+  EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("00000000-0000-0000-0000-000000000002"));
   EXPECT_TRUE(real_card_suggestion.custom_icon.IsEmpty());
 }
 
@@ -645,13 +645,15 @@
 
   EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
   EXPECT_EQ(promo_code_suggestions[0].label, u"test_value_prop_text_1");
-  EXPECT_EQ(promo_code_suggestions[0].GetPayload<std::string>(), "1");
+  EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("1"));
   EXPECT_EQ(promo_code_suggestions[0].frontend_id,
             POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY);
 
   EXPECT_EQ(promo_code_suggestions[1].main_text.value, u"test_promo_code_2");
   EXPECT_EQ(promo_code_suggestions[1].label, u"test_value_prop_text_2");
-  EXPECT_EQ(promo_code_suggestions[1].GetPayload<std::string>(), "2");
+  EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId("2"));
   EXPECT_EQ(promo_code_suggestions[1].frontend_id,
             POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY);
 
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 33d9fd9..5a8d48e 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1842,22 +1842,23 @@
 
 CreditCard* BrowserAutofillManager::GetCreditCard(int unique_id) {
   // Unpack the |unique_id| into component parts.
-  std::string credit_card_id;
-  std::string profile_id;
+  Suggestion::BackendId credit_card_id;
+  Suggestion::BackendId profile_id;
   suggestion_generator_->SplitFrontendId(unique_id, &credit_card_id,
                                          &profile_id);
-  return personal_data_->GetCreditCardByGUID(credit_card_id);
+  return personal_data_->GetCreditCardByGUID(credit_card_id.value());
 }
 
 AutofillProfile* BrowserAutofillManager::GetProfile(int unique_id) {
   // Unpack the |unique_id| into component parts.
-  std::string credit_card_id;
-  std::string profile_id;
+  Suggestion::BackendId credit_card_id;
+  Suggestion::BackendId profile_id;
   suggestion_generator_->SplitFrontendId(unique_id, &credit_card_id,
                                          &profile_id);
 
-  if (base::IsValidGUID(profile_id))
-    return personal_data_->GetProfileByGUID(profile_id);
+  std::string guid = profile_id.value();
+  if (base::IsValidGUID(guid))
+    return personal_data_->GetProfileByGUID(guid);
   return nullptr;
 }
 
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 2bc0c2d9..7493f1cd 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -602,7 +602,7 @@
   int MakeFrontendId(const std::string& cc_sid,
                      const std::string& profile_sid) const {
     return browser_autofill_manager_->suggestion_generator()->MakeFrontendId(
-        cc_sid, profile_sid);
+        Suggestion::BackendId(cc_sid), Suggestion::BackendId(profile_sid));
   }
 
   bool WillFillCreditCardNumber(const FormData& form,
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
index dc84f884..b3bce3c 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
@@ -54,6 +54,7 @@
 #include "components/autofill/core/browser/test_personal_data_manager.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
 #include "components/autofill/core/browser/ui/popup_types.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -793,8 +794,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -840,8 +841,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -899,8 +900,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   base::HistogramTester histogram_tester;
 
@@ -947,8 +948,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1027,8 +1028,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1111,8 +1112,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1201,8 +1202,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1294,8 +1295,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1375,8 +1376,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate form submission.
   base::HistogramTester histogram_tester;
@@ -1486,8 +1487,8 @@
   // Trigger phone number rationalization at filling time.
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
   EXPECT_EQ(
       1, user_action_tester.GetActionCount("Autofill_FilledProfileSuggestion"));
 
@@ -1553,8 +1554,8 @@
   // Trigger phone number rationalization at filling time.
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
   EXPECT_EQ(
       1, user_action_tester.GetActionCount("Autofill_FilledProfileSuggestion"));
 
@@ -1636,8 +1637,8 @@
     std::string guid(kTestGuid);  // local profile.
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
   }
 
   VerifyUkm(
@@ -2027,8 +2028,8 @@
   // Trigger phone number rationalization at filling time.
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
   EXPECT_EQ(
       1, user_action_tester.GetActionCount("Autofill_FilledProfileSuggestion"));
 
@@ -2110,8 +2111,8 @@
   // Trigger phone number rationalization at filling time.
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
   EXPECT_EQ(
       1, user_action_tester.GetActionCount("Autofill_FilledProfileSuggestion"));
 
@@ -3878,11 +3879,12 @@
     base::UserActionTester user_action_tester;
     std::string guid("10000000-0000-0000-0000-000000000001");  // local card
     external_delegate_->OnQuery(0, form, form.fields.front(), gfx::RectF());
+    Suggestion::BackendId backend_id = Suggestion::BackendId(guid);
     external_delegate_->DidAcceptSuggestion(
         u"Test",
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()),
-        guid, 0);
+            backend_id, Suggestion::BackendId()),
+        backend_id, 0);
     EXPECT_EQ(1,
               user_action_tester.GetActionCount("Autofill_SelectedSuggestion"));
   }
@@ -3903,7 +3905,7 @@
     std::string guid("10000000-0000-0000-0000-000000000001");  // local card
     external_delegate_->OnQuery(0, form, form.fields.front(), gfx::RectF());
     external_delegate_->DidAcceptSuggestion(
-        std::u16string(), POPUP_ITEM_ID_CLEAR_FORM, std::string(), 0);
+        std::u16string(), POPUP_ITEM_ID_CLEAR_FORM, Suggestion::BackendId(), 0);
     EXPECT_EQ(1, user_action_tester.GetActionCount("Autofill_ClearedForm"));
   }
 
@@ -3922,11 +3924,12 @@
     base::UserActionTester user_action_tester;
     std::string guid("10000000-0000-0000-0000-000000000001");  // local card
     external_delegate_->OnQuery(0, form, form.fields.front(), gfx::RectF());
+    Suggestion::BackendId backend_id = Suggestion::BackendId(guid);
     external_delegate_->DidAcceptSuggestion(
         u"Test",
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()),
-        guid, 0);
+            backend_id, Suggestion::BackendId()),
+        backend_id, 0);
     EXPECT_EQ(1,
               user_action_tester.GetActionCount("Autofill_SelectedSuggestion"));
   }
@@ -3938,7 +3941,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_FilledCreditCardSuggestion"));
   }
@@ -4097,11 +4100,12 @@
     base::UserActionTester user_action_tester;
     std::string guid(kTestGuid);  // local profile.
     external_delegate_->OnQuery(0, form, form.fields.front(), gfx::RectF());
+    Suggestion::BackendId backend_id = Suggestion::BackendId(guid);
     external_delegate_->DidAcceptSuggestion(
         u"Test",
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid),
-        guid, 0);
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), backend_id),
+        backend_id, 0);
     EXPECT_EQ(1,
               user_action_tester.GetActionCount("Autofill_SelectedSuggestion"));
   }
@@ -4112,8 +4116,8 @@
     std::string guid(kTestGuid);  // local profile.
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_FilledProfileSuggestion"));
   }
@@ -4794,7 +4798,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields[2],
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
         FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_SELECTED, 1);
@@ -4821,11 +4825,11 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields[2],
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields[2],
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
         FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_SELECTED, 2);
@@ -4940,7 +4944,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.CreditCard",
                                        FORM_EVENT_LOCAL_SUGGESTION_FILLED, 1);
     histogram_tester.ExpectBucketCount(credit_card_form_events_frame_histogram_,
@@ -4993,7 +4997,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnCreditCardFetchingSuccessful(u"6011000990139424");
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
@@ -5029,7 +5033,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.CreditCard",
                                        FORM_EVENT_SERVER_SUGGESTION_FILLED, 1);
     histogram_tester.ExpectBucketCount(credit_card_form_events_frame_histogram_,
@@ -5053,11 +5057,11 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.CreditCard",
                                        FORM_EVENT_LOCAL_SUGGESTION_FILLED, 2);
     histogram_tester.ExpectBucketCount(credit_card_form_events_frame_histogram_,
@@ -5096,8 +5100,8 @@
   base::HistogramTester histogram_tester;
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(local_guid,
-                                                                std::string()));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(local_guid), Suggestion::BackendId()));
 
   EXPECT_THAT(
       histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
@@ -5142,8 +5146,8 @@
   std::string local_guid(local_and_duplicate_server_card_guids.at(0));
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(local_guid,
-                                                                std::string()));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(local_guid), Suggestion::BackendId()));
 
   EXPECT_THAT(
       histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
@@ -5247,7 +5251,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     // Preflight call is made only if a masked server card is available and the
     // user is eligible for FIDO authentication (except iOS).
 #if BUILDFLAG(IS_IOS)
@@ -5275,7 +5279,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     // Preflight call is made only if a masked server card is available and the
     // user is eligible for FIDO authentication (except iOS).
 #if BUILDFLAG(IS_IOS)
@@ -5328,7 +5332,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
     histogram_tester.ExpectTotalCount(
@@ -5354,7 +5358,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kPermanentFailure,
                     std::string());
     histogram_tester.ExpectTotalCount(
@@ -5395,7 +5399,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPanWithNonHttpOkResponse();
     histogram_tester.ExpectTotalCount(
         "Autofill.UnmaskPrompt.GetRealPanDuration", 1);
@@ -5760,8 +5764,8 @@
   std::string guid("10000000-0000-0000-0000-000000000001");
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(guid,
-                                                                std::string()));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(guid), Suggestion::BackendId()));
 
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -5981,7 +5985,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
@@ -6074,8 +6078,9 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     SubmitForm(form);
+
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
         FORM_EVENT_SERVER_SUGGESTION_WILL_SUBMIT_ONCE, 1);
@@ -6119,7 +6124,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnCreditCardFetchingSuccessful(u"6011000990139424");
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
@@ -6448,7 +6453,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
@@ -6507,7 +6512,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
@@ -6535,7 +6540,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnCreditCardFetchingSuccessful(u"6011000990139424");
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
@@ -6743,7 +6748,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.CreditCard",
                                        FORM_EVENT_SUGGESTIONS_SHOWN, 1);
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.CreditCard",
@@ -6792,7 +6797,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
     SubmitForm(form);
@@ -6840,7 +6845,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
     SubmitForm(form);
@@ -6903,7 +6908,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
     SubmitForm(form);
@@ -6977,7 +6982,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
 
@@ -7038,7 +7043,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kPermanentFailure,
                     std::string());
 
@@ -7096,7 +7101,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
                     "6011000990139424");
 
@@ -7107,7 +7112,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.back(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     SubmitForm(form);
     EXPECT_THAT(
         histogram_tester.GetAllSamples(
@@ -7535,8 +7540,8 @@
     std::string guid(kTestGuid);  // local profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
                                        FORM_EVENT_LOCAL_SUGGESTION_FILLED, 1);
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
@@ -7571,12 +7576,12 @@
     std::string guid(kTestGuid);  // local profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
                                        FORM_EVENT_LOCAL_SUGGESTION_FILLED, 2);
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
@@ -7595,8 +7600,8 @@
     std::string guid(kTestGuid);  // server profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
                                        FORM_EVENT_SERVER_SUGGESTION_FILLED, 1);
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
@@ -7613,12 +7618,12 @@
     std::string guid(kTestGuid);  // server profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
                                        FORM_EVENT_SERVER_SUGGESTION_FILLED, 2);
     histogram_tester.ExpectBucketCount("Autofill.FormEvents.Address",
@@ -7732,8 +7737,8 @@
     std::string guid(kTestGuid);  // local profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.Address",
@@ -7874,8 +7879,8 @@
     std::string guid(kTestGuid);  // local profile
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     SubmitForm(form);
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.Address",
@@ -8839,7 +8844,7 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
         autofill_manager().suggestion_generator()->MakeFrontendId(
-            guid, std::string()));
+            Suggestion::BackendId(guid), Suggestion::BackendId()));
     ChangeTextField(form, form.fields.front());
     // Simulate a second keystroke; make sure we don't log the metric twice.
     ChangeTextField(form, form.fields.front());
@@ -8988,8 +8993,8 @@
     std::string guid(kTestGuid);
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  guid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(guid)));
     ChangeTextField(form, form.fields.front());
     // Simulate a second keystroke; make sure we don't log the metric twice.
     ChangeTextField(form, form.fields.front());
@@ -10082,8 +10087,8 @@
   // Simulate filling the form.
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Simulate checking whether to fill a dynamic form after the form was filled
   // initially.
@@ -11044,8 +11049,8 @@
     autofill_manager().FillOrPreviewForm(
         mojom::RendererFormDataAction::kFill, /*query_id=*/0, form,
         form.fields.front(),
-        autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                  kTestGuid));
+        autofill_manager().suggestion_generator()->MakeFrontendId(
+            Suggestion::BackendId(), Suggestion::BackendId(kTestGuid)));
   }
 
   // Simulate form submission.
@@ -11363,8 +11368,8 @@
       /*has_autofill_suggestions=*/true, form_, form_.fields[0]);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form_, form_.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                kTestGuid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(kTestGuid)));
 
   // Simulate user fixing the address.
   ChangeTextField(form_, form_.fields[1]);
@@ -11405,8 +11410,8 @@
       /*has_autofill_suggestions=*/true, form_, form_.fields[0]);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form_, form_.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                kTestGuid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(kTestGuid)));
 
   // Simulate user fixing the address.
   ChangeTextField(form_, form_.fields[1]);
@@ -11602,8 +11607,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
 
   // Case #1: Change submitted value to expected autofilled value for the field.
   // The histogram should emit true for this.
@@ -11649,8 +11654,8 @@
   std::string guid(kTestGuid);
   autofill_manager().FillOrPreviewForm(
       mojom::RendererFormDataAction::kFill, 0, form, form.fields.front(),
-      autofill_manager().suggestion_generator()->MakeFrontendId(std::string(),
-                                                                guid));
+      autofill_manager().suggestion_generator()->MakeFrontendId(
+          Suggestion::BackendId(), Suggestion::BackendId(guid)));
   // Simulate form submission.
   SubmitForm(form);
 
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
index b1858523..f98bfff 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
@@ -362,7 +362,8 @@
 bool CreditCardFormEventLogger::DoesCardHaveOffer(
     const CreditCard& credit_card) {
   for (auto& suggestion : suggestions_) {
-    if (suggestion.GetPayload<std::string>() == credit_card.guid()) {
+    if (suggestion.GetPayload<Suggestion::BackendId>().value() ==
+        credit_card.guid()) {
       return !suggestion.offer_label.empty();
     }
   }
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager.cc b/components/autofill/core/browser/payments/autofill_offer_manager.cc
index c94e756..1201488 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_manager.cc
@@ -53,8 +53,8 @@
 
   // Update |offer_label| for each suggestion.
   for (auto& suggestion : suggestions) {
-    std::string id = suggestion.GetPayload<std::string>();
-    if (eligible_offers_map.count(id)) {
+    if (eligible_offers_map.count(
+            suggestion.GetPayload<Suggestion::BackendId>().value())) {
       suggestion.offer_label =
           l10n_util::GetStringUTF16(IDS_AUTOFILL_OFFERS_CASHBACK);
     }
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
index 609a825..3e3adb1 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
 #include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
@@ -27,8 +28,10 @@
 namespace autofill {
 
 namespace {
-const char kTestGuid[] = "00000000-0000-0000-0000-000000000001";
-const char kTestGuid2[] = "00000000-0000-0000-0000-000000000002";
+const Suggestion::Suggestion::BackendId kTestGuid =
+    Suggestion::Suggestion::BackendId("00000000-0000-0000-0000-000000000001");
+const Suggestion::Suggestion::BackendId kTestGuid2 =
+    Suggestion::Suggestion::BackendId("00000000-0000-0000-0000-000000000002");
 const char kTestNumber[] = "4234567890123456";  // Visa
 const char kTestUrl[] = "http://www.example.com/";
 const char kTestUrlWithParam[] =
@@ -133,7 +136,7 @@
 };
 
 TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_EligibleCashback) {
-  CreditCard card = CreateCreditCard(kTestGuid);
+  CreditCard card = CreateCreditCard(kTestGuid.value());
   personal_data_manager_.AddAutofillOfferData(
       CreateCreditCardOfferForCard(card, "5%"));
 
@@ -147,7 +150,7 @@
 }
 
 TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_ExpiredOffer) {
-  CreditCard card = CreateCreditCard(kTestGuid);
+  CreditCard card = CreateCreditCard(kTestGuid.value());
   personal_data_manager_.AddAutofillOfferData(
       CreateCreditCardOfferForCard(card, "5%", /*expired=*/true));
 
@@ -160,7 +163,7 @@
 }
 
 TEST_F(AutofillOfferManagerTest, UpdateSuggestionsWithOffers_WrongUrl) {
-  CreditCard card = CreateCreditCard(kTestGuid);
+  CreditCard card = CreateCreditCard(kTestGuid.value());
   personal_data_manager_.AddAutofillOfferData(
       CreateCreditCardOfferForCard(card, "5%"));
 
@@ -174,9 +177,9 @@
 
 TEST_F(AutofillOfferManagerTest,
        UpdateSuggestionsWithOffer_SuggestionsSortedByOfferPresence) {
-  CreditCard cardWithoutOffer = CreateCreditCard(kTestGuid);
+  CreditCard cardWithoutOffer = CreateCreditCard(kTestGuid.value());
   CreditCard cardWithOffer =
-      CreateCreditCard(kTestGuid2, "4111111111111111", 100);
+      CreateCreditCard(kTestGuid2.value(), "4111111111111111", 100);
   personal_data_manager_.AddAutofillOfferData(
       CreateCreditCardOfferForCard(cardWithOffer, "5%"));
 
@@ -190,14 +193,15 @@
   // suggestion[0]
   EXPECT_TRUE(!suggestions[0].offer_label.empty());
   EXPECT_TRUE(suggestions[1].offer_label.empty());
-  EXPECT_EQ(absl::get<std::string>(suggestions[0].payload), kTestGuid2);
-  EXPECT_EQ(absl::get<std::string>(suggestions[1].payload), kTestGuid);
+  EXPECT_EQ(suggestions[0].GetPayload<Suggestion::BackendId>(), kTestGuid2);
+  EXPECT_EQ(suggestions[1].GetPayload<Suggestion::BackendId>(), kTestGuid);
 }
 
 TEST_F(AutofillOfferManagerTest,
        UpdateSuggestionsWithOffer_SuggestionsNotSortedIfAllCardsHaveOffers) {
-  CreditCard card1 = CreateCreditCard(kTestGuid, kTestNumber, 100);
-  CreditCard card2 = CreateCreditCard(kTestGuid2, "4111111111111111", 101);
+  CreditCard card1 = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
+  CreditCard card2 =
+      CreateCreditCard(kTestGuid2.value(), "4111111111111111", 101);
   personal_data_manager_.AddAutofillOfferData(
       CreateCreditCardOfferForCard(card1, "5%"));
   personal_data_manager_.AddAutofillOfferData(
@@ -209,13 +213,14 @@
   autofill_offer_manager_->UpdateSuggestionsWithOffers(GURL(kTestUrlWithParam),
                                                        suggestions);
 
-  EXPECT_EQ(absl::get<std::string>(suggestions[0].payload), kTestGuid);
-  EXPECT_EQ(absl::get<std::string>(suggestions[1].payload), kTestGuid2);
+  EXPECT_EQ(suggestions[0].GetPayload<Suggestion::BackendId>(), kTestGuid);
+  EXPECT_EQ(suggestions[1].GetPayload<Suggestion::BackendId>(), kTestGuid2);
 }
 
 TEST_F(AutofillOfferManagerTest, IsUrlEligible) {
-  CreditCard card1 = CreateCreditCard(kTestGuid, kTestNumber, 100);
-  CreditCard card2 = CreateCreditCard(kTestGuid2, "4111111111111111", 101);
+  CreditCard card1 = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
+  CreditCard card2 =
+      CreateCreditCard(kTestGuid2.value(), "4111111111111111", 101);
   personal_data_manager_.AddAutofillOfferData(CreateCreditCardOfferForCard(
       card1, "5%", /*expired=*/false,
       {GURL("http://www.google.com"), GURL("http://www.youtube.com")}));
@@ -232,7 +237,7 @@
 }
 
 TEST_F(AutofillOfferManagerTest, GetOfferForUrl_ReturnNothingWhenFindNoMatch) {
-  CreditCard card1 = CreateCreditCard(kTestGuid, kTestNumber, 100);
+  CreditCard card1 = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
   personal_data_manager_.AddAutofillOfferData(CreateCreditCardOfferForCard(
       card1, "5%", /*expired=*/false,
       {GURL("http://www.google.com"), GURL("http://www.youtube.com")}));
@@ -244,8 +249,9 @@
 
 TEST_F(AutofillOfferManagerTest,
        GetOfferForUrl_ReturnCorrectOfferWhenFindMatch) {
-  CreditCard card1 = CreateCreditCard(kTestGuid, kTestNumber, 100);
-  CreditCard card2 = CreateCreditCard(kTestGuid2, "4111111111111111", 101);
+  CreditCard card1 = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
+  CreditCard card2 =
+      CreateCreditCard(kTestGuid2.value(), "4111111111111111", 101);
 
   AutofillOfferData offer1 = CreateCreditCardOfferForCard(
       card1, "5%", /*expired=*/false,
@@ -266,7 +272,7 @@
 TEST_F(AutofillOfferManagerTest, GetOfferForUrl_ReturnOfferFromCouponDelegate) {
   const GURL example_url("http://www.example.com");
   // Add card-linked offer to PersonalDataManager.
-  CreditCard card = CreateCreditCard(kTestGuid, kTestNumber, 100);
+  CreditCard card = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
   AutofillOfferData offer1 = CreateCreditCardOfferForCard(
       card, "5%", /*expired=*/false,
       /*merchant_origins=*/
@@ -303,8 +309,9 @@
 
 TEST_F(AutofillOfferManagerTest,
        CreateCardLinkedOffersMap_ReturnsOnlyCardLinkedOffers) {
-  CreditCard card1 = CreateCreditCard(kTestGuid, kTestNumber, 100);
-  CreditCard card2 = CreateCreditCard(kTestGuid2, "4111111111111111", 101);
+  CreditCard card1 = CreateCreditCard(kTestGuid.value(), kTestNumber, 100);
+  CreditCard card2 =
+      CreateCreditCard(kTestGuid2.value(), "4111111111111111", 101);
 
   AutofillOfferData offer1 = CreateCreditCardOfferForCard(
       card1, "5%", /*expired=*/false,
diff --git a/components/autofill/core/browser/test_browser_autofill_manager.cc b/components/autofill/core/browser/test_browser_autofill_manager.cc
index 414bbcca..3517633 100644
--- a/components/autofill/core/browser/test_browser_autofill_manager.cc
+++ b/components/autofill/core/browser/test_browser_autofill_manager.cc
@@ -15,6 +15,7 @@
 #include "components/autofill/core/browser/test_autofill_driver.h"
 #include "components/autofill/core/browser/test_autofill_manager_waiter.h"
 #include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "form_structure_test_api.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -173,8 +174,8 @@
   std::string credit_card_guid =
       base::StringPrintf("00000000-0000-0000-0000-%012d", credit_card_id);
 
-  return suggestion_generator()->MakeFrontendId(credit_card_guid,
-                                                std::string());
+  return suggestion_generator()->MakeFrontendId(
+      Suggestion::BackendId(credit_card_guid), Suggestion::BackendId());
 }
 
 void TestBrowserAutofillManager::AddSeenForm(
diff --git a/components/autofill/core/browser/ui/autofill_popup_delegate.h b/components/autofill/core/browser/ui/autofill_popup_delegate.h
index d884117..409d19df 100644
--- a/components/autofill/core/browser/ui/autofill_popup_delegate.h
+++ b/components/autofill/core/browser/ui/autofill_popup_delegate.h
@@ -39,7 +39,7 @@
   // the guid of the backend data model.
   virtual void DidSelectSuggestion(const std::u16string& value,
                                    int frontend_id,
-                                   const std::string& backend_id) = 0;
+                                   const Suggestion::BackendId& backend_id) = 0;
 
   // Inform the delegate that a row in the popup has been chosen. |value| is the
   // suggestion's value, and is usually the main text to be shown. |frontend_id|
diff --git a/components/autofill/core/browser/ui/suggestion.h b/components/autofill/core/browser/ui/suggestion.h
index 1368f10a..a220a027 100644
--- a/components/autofill/core/browser/ui/suggestion.h
+++ b/components/autofill/core/browser/ui/suggestion.h
@@ -20,7 +20,8 @@
 
 struct Suggestion {
   using IsLoading = base::StrongAlias<class IsLoadingTag, bool>;
-  using Payload = absl::variant<std::string, GURL>;
+  using BackendId = base::StrongAlias<struct BackendIdTag, std::string>;
+  using Payload = absl::variant<BackendId, GURL>;
 
   enum MatchMode {
     PREFIX_MATCH,    // for prefix matched suggestions;
@@ -87,7 +88,7 @@
       case PopupItemId::POPUP_ITEM_ID_SEE_PROMO_CODE_DETAILS:
         return absl::holds_alternative<GURL>(payload);
       default:
-        return absl::holds_alternative<std::string>(payload);
+        return absl::holds_alternative<BackendId>(payload);
     }
   }
 #endif
diff --git a/components/autofill/core/browser/ui/suggestion_selection.cc b/components/autofill/core/browser/ui/suggestion_selection.cc
index 64a56eb..755e5510 100644
--- a/components/autofill/core/browser/ui/suggestion_selection.cc
+++ b/components/autofill/core/browser/ui/suggestion_selection.cc
@@ -126,7 +126,7 @@
       }
 
       suggestions.emplace_back(value);
-      suggestions.back().payload = profile->guid();
+      suggestions.back().payload = Suggestion::BackendId(profile->guid());
       suggestions.back().match = prefix_matched_suggestion
                                      ? Suggestion::PREFIX_MATCH
                                      : Suggestion::SUBSTRING_MATCH;
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index aeb0f6c..a90b5bf 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -29,6 +29,7 @@
 #include "components/autofill/core/browser/keyboard_accessory_metrics_logger.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
@@ -424,8 +425,8 @@
     if (_popupDelegate) {
       // TODO(966411): Replace 0 with the index of the selected suggestion.
       _popupDelegate->DidAcceptSuggestion(SysNSStringToUTF16(suggestion.value),
-                                          suggestion.identifier, std::string(),
-                                          0);
+                                          suggestion.identifier,
+                                          autofill::Suggestion::BackendId(), 0);
     }
     return;
   }
diff --git a/components/bookmarks/browser/base_bookmark_model_observer.cc b/components/bookmarks/browser/base_bookmark_model_observer.cc
index 657a3c9..f7bd690 100644
--- a/components/bookmarks/browser/base_bookmark_model_observer.cc
+++ b/components/bookmarks/browser/base_bookmark_model_observer.cc
@@ -25,7 +25,8 @@
 
 void BaseBookmarkModelObserver::BookmarkNodeAdded(BookmarkModel* model,
                                                   const BookmarkNode* parent,
-                                                  size_t index) {
+                                                  size_t index,
+                                                  bool added_by_user) {
   BookmarkModelChanged();
 }
 
diff --git a/components/bookmarks/browser/base_bookmark_model_observer.h b/components/bookmarks/browser/base_bookmark_model_observer.h
index abe84cf6..7ac72712 100644
--- a/components/bookmarks/browser/base_bookmark_model_observer.h
+++ b/components/bookmarks/browser/base_bookmark_model_observer.h
@@ -31,7 +31,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(BookmarkModel* model,
                          const BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(BookmarkModel* model,
                            const BookmarkNode* parent,
                            size_t old_index,
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index dd662f64..2d91c6d 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -671,7 +671,7 @@
     const BookmarkNode::MetaInfoMap* meta_info) {
   metrics::RecordBookmarkAdded();
   return AddURL(parent, index, title, url, meta_info, absl::nullopt,
-                absl::nullopt);
+                absl::nullopt, true);
 }
 
 const BookmarkNode* BookmarkModel::AddURL(
@@ -681,7 +681,8 @@
     const GURL& url,
     const BookmarkNode::MetaInfoMap* meta_info,
     absl::optional<base::Time> creation_time,
-    absl::optional<base::GUID> guid) {
+    absl::optional<base::GUID> guid,
+    bool added_by_user) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(loaded_);
   DCHECK(url.is_valid());
@@ -706,7 +707,7 @@
   if (meta_info)
     new_node->SetMetaInfoMap(*meta_info);
 
-  return AddNode(AsMutable(parent), index, std::move(new_node));
+  return AddNode(AsMutable(parent), index, std::move(new_node), added_by_user);
 }
 
 void BookmarkModel::SortChildren(const BookmarkNode* parent) {
@@ -824,7 +825,7 @@
 
   for (size_t i = 0; i < node->children().size(); ++i) {
     for (BookmarkModelObserver& observer : observers_)
-      observer.BookmarkNodeAdded(this, node, i);
+      observer.BookmarkNodeAdded(this, node, i, false);
     NotifyNodeAddedForAllDescendants(node->children()[i].get());
   }
 }
@@ -898,7 +899,8 @@
 
 BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
                                      size_t index,
-                                     std::unique_ptr<BookmarkNode> node) {
+                                     std::unique_ptr<BookmarkNode> node,
+                                     bool added_by_user) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   BookmarkNode* node_ptr = node.get();
@@ -910,7 +912,7 @@
   AddNodeToIndexRecursive(node_ptr);
 
   for (BookmarkModelObserver& observer : observers_)
-    observer.BookmarkNodeAdded(this, parent, index);
+    observer.BookmarkNodeAdded(this, parent, index, added_by_user);
 
   return node_ptr;
 }
diff --git a/components/bookmarks/browser/bookmark_model.h b/components/bookmarks/browser/bookmark_model.h
index 6347e4e..c74d4ee 100644
--- a/components/bookmarks/browser/bookmark_model.h
+++ b/components/bookmarks/browser/bookmark_model.h
@@ -235,7 +235,8 @@
   // (i.e. nullopt), then a random one will be generated. If a GUID is
   // provided, it must be valid. Used for bookmarks not being added from
   // direct user actions (e.g. created via sync, locally modified bookmark
-  // or pre-existing bookmark).
+  // or pre-existing bookmark). `added_by_user` is true when a new bookmark was
+  // added by the user and false when a node is added by sync or duplicated.
   const BookmarkNode* AddURL(
       const BookmarkNode* parent,
       size_t index,
@@ -243,7 +244,8 @@
       const GURL& url,
       const BookmarkNode::MetaInfoMap* meta_info = nullptr,
       absl::optional<base::Time> creation_time = absl::nullopt,
-      absl::optional<base::GUID> guid = absl::nullopt);
+      absl::optional<base::GUID> guid = absl::nullopt,
+      bool added_by_user = false);
 
   // Sorts the children of |parent|, notifying observers by way of the
   // BookmarkNodeChildrenReordered method.
@@ -356,11 +358,13 @@
   // Called when done loading. Updates internal state and notifies observers.
   void DoneLoading(std::unique_ptr<BookmarkLoadDetails> details);
 
-  // Adds the |node| at |parent| in the specified |index| and notifies its
-  // observers.
+  // Adds the `node` at `parent` in the specified `index` and notifies its
+  // observers. `added_by_user` is true when a new bookmark was added by the
+  // user and false when a node is added by sync or duplicated.
   BookmarkNode* AddNode(BookmarkNode* parent,
                         size_t index,
-                        std::unique_ptr<BookmarkNode> node);
+                        std::unique_ptr<BookmarkNode> node,
+                        bool added_by_user = false);
 
   // Adds |node| to |index_| and recursively invokes this for all children.
   void AddNodeToIndexRecursive(const BookmarkNode* node);
diff --git a/components/bookmarks/browser/bookmark_model_observer.h b/components/bookmarks/browser/bookmark_model_observer.h
index 0e5eb96..42cd99a 100644
--- a/components/bookmarks/browser/bookmark_model_observer.h
+++ b/components/bookmarks/browser/bookmark_model_observer.h
@@ -37,10 +37,13 @@
                                  const BookmarkNode* new_parent,
                                  size_t new_index) = 0;
 
-  // Invoked when a node has been added.
+  // Invoked when a node has been added. `added_by_user` is true when a new
+  // bookmark was added by the user and false when a node is added by sync
+  // or duplicated.
   virtual void BookmarkNodeAdded(BookmarkModel* model,
                                  const BookmarkNode* parent,
-                                 size_t index) = 0;
+                                 size_t index,
+                                 bool added_by_user) = 0;
 
   // Invoked prior to removing a node from the model. When a node is removed
   // it's descendants are implicitly removed from the model as
diff --git a/components/bookmarks/browser/bookmark_model_unittest.cc b/components/bookmarks/browser/bookmark_model_unittest.cc
index ef3d30e..83a491a 100644
--- a/components/bookmarks/browser/bookmark_model_unittest.cc
+++ b/components/bookmarks/browser/bookmark_model_unittest.cc
@@ -268,27 +268,32 @@
  public:
   struct ObserverDetails {
     ObserverDetails() {
-      Set(nullptr, nullptr, static_cast<size_t>(-1), static_cast<size_t>(-1));
+      Set(nullptr, nullptr, static_cast<size_t>(-1), static_cast<size_t>(-1),
+          false);
     }
 
     void Set(const BookmarkNode* node1,
              const BookmarkNode* node2,
              size_t index1,
-             size_t index2) {
+             size_t index2,
+             bool added_by_user) {
       node1_ = node1;
       node2_ = node2;
       index1_ = index1;
       index2_ = index2;
+      added_by_user_ = added_by_user;
     }
 
     void ExpectEquals(const BookmarkNode* node1,
                       const BookmarkNode* node2,
                       size_t index1,
-                      size_t index2) {
+                      size_t index2,
+                      bool added_by_user) {
       EXPECT_EQ(node1_, node1);
       EXPECT_EQ(node2_, node2);
       EXPECT_EQ(index1_, index1);
       EXPECT_EQ(index2_, index2);
+      EXPECT_EQ(added_by_user_, added_by_user);
     }
 
    private:
@@ -296,6 +301,7 @@
     raw_ptr<const BookmarkNode> node2_;
     size_t index1_;
     size_t index2_;
+    bool added_by_user_;
   };
 
   struct NodeRemovalDetail {
@@ -334,14 +340,16 @@
                          const BookmarkNode* new_parent,
                          size_t new_index) override {
     ++moved_count_;
-    observer_details_.Set(old_parent, new_parent, old_index, new_index);
+    observer_details_.Set(old_parent, new_parent, old_index, new_index, false);
   }
 
   void BookmarkNodeAdded(BookmarkModel* model,
                          const BookmarkNode* parent,
-                         size_t index) override {
+                         size_t index,
+                         bool added_by_user) override {
     ++added_count_;
-    observer_details_.Set(parent, nullptr, index, static_cast<size_t>(-1));
+    observer_details_.Set(parent, nullptr, index, static_cast<size_t>(-1),
+                          added_by_user);
   }
 
   void OnWillRemoveBookmarks(BookmarkModel* model,
@@ -359,14 +367,15 @@
                            const BookmarkNode* node,
                            const std::set<GURL>& removed_urls) override {
     ++removed_count_;
-    observer_details_.Set(parent, nullptr, old_index, static_cast<size_t>(-1));
+    observer_details_.Set(parent, nullptr, old_index, static_cast<size_t>(-1),
+                          false);
   }
 
   void BookmarkNodeChanged(BookmarkModel* model,
                            const BookmarkNode* node) override {
     ++changed_count_;
     observer_details_.Set(node, nullptr, static_cast<size_t>(-1),
-                          static_cast<size_t>(-1));
+                          static_cast<size_t>(-1), false);
   }
 
   void OnWillChangeBookmarkNode(BookmarkModel* model,
@@ -537,7 +546,30 @@
 
   const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
+
+  ASSERT_EQ(1u, root->children().size());
+  ASSERT_EQ(title, new_node->GetTitle());
+  ASSERT_TRUE(url == new_node->url());
+  ASSERT_TRUE(new_node->guid().is_valid());
+  ASSERT_EQ(BookmarkNode::URL, new_node->type());
+  ASSERT_EQ(new_node, model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+  EXPECT_TRUE(new_node->id() != root->id() &&
+              new_node->id() != model_->other_node()->id() &&
+              new_node->id() != model_->mobile_node()->id());
+}
+
+TEST_F(BookmarkModelTest, AddNewURL) {
+  const BookmarkNode* root = model_->bookmark_bar_node();
+  const std::u16string title(u"foo");
+  const GURL url("http://foo.com");
+
+  const BookmarkNode* new_node = model_->AddNewURL(root, 0, title, url);
+  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 true);
 
   ASSERT_EQ(1u, root->children().size());
   ASSERT_EQ(title, new_node->GetTitle());
@@ -559,7 +591,8 @@
 
   const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   ASSERT_EQ(1u, root->children().size());
   ASSERT_EQ(title, new_node->GetTitle());
@@ -599,7 +632,8 @@
   const BookmarkNode* new_node =
       model_->AddURL(root, 0, title, url, &meta_info, time);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   ASSERT_EQ(1u, root->children().size());
   ASSERT_EQ(title, new_node->GetTitle());
@@ -637,7 +671,8 @@
 
   const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   ASSERT_EQ(1u, root->children().size());
   ASSERT_EQ(title, new_node->GetTitle());
@@ -656,7 +691,8 @@
 
   const BookmarkNode* new_node = model_->AddFolder(root, 0, title);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   ASSERT_EQ(1u, root->children().size());
   ASSERT_EQ(title, new_node->GetTitle());
@@ -671,7 +707,8 @@
   ClearCounts();
   model_->AddFolder(root, 0, title);
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 }
 
 TEST_F(BookmarkModelTest, AddFolderWithCreationTime) {
@@ -724,7 +761,8 @@
   model_->Remove(root->children().front().get());
   ASSERT_EQ(0u, root->children().size());
   AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   // Make sure there is no mapping for the URL.
   ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == nullptr);
@@ -747,7 +785,8 @@
   model_->Remove(root->children().front().get());
   ASSERT_EQ(0u, root->children().size());
   AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
 
   // Make sure there is no mapping for the URL.
   ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == nullptr);
@@ -861,7 +900,7 @@
   model_->SetTitle(node, title);
   AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
   observer_details_.ExpectEquals(node, nullptr, static_cast<size_t>(-1),
-                                 static_cast<size_t>(-1));
+                                 static_cast<size_t>(-1), false);
   EXPECT_EQ(title, node->GetTitle());
 
   // Should update the index.
@@ -930,7 +969,7 @@
   model_->SetURL(node, url);
   AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
   observer_details_.ExpectEquals(node, nullptr, static_cast<size_t>(-1),
-                                 static_cast<size_t>(-1));
+                                 static_cast<size_t>(-1), false);
   EXPECT_EQ(url, node->url());
 }
 
@@ -960,7 +999,7 @@
   model_->Move(node, folder1, 0);
 
   AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(root, folder1, 1, 0);
+  observer_details_.ExpectEquals(root, folder1, 1, 0, false);
   EXPECT_TRUE(folder1 == node->parent());
   EXPECT_EQ(1u, root->children().size());
   EXPECT_EQ(folder1, root->children().front().get());
@@ -976,7 +1015,8 @@
   ClearCounts();
   model_->Remove(root->children().front().get());
   AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
-  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1));
+  observer_details_.ExpectEquals(root, nullptr, 0, static_cast<size_t>(-1),
+                                 false);
   EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == nullptr);
   EXPECT_EQ(0u, root->children().size());
 
@@ -1014,7 +1054,7 @@
 
   // Should update the hierarchy.
   AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(folder1, folder2, 0, 0);
+  observer_details_.ExpectEquals(folder1, folder2, 0, 0, false);
   EXPECT_EQ(root->children().size(), 2u);
   EXPECT_EQ(folder1->children().size(), 0u);
   EXPECT_EQ(folder2->children().size(), 1u);
@@ -1036,7 +1076,7 @@
 
   // Should update the hierarchy.
   AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(folder2, folder1, 0, 0);
+  observer_details_.ExpectEquals(folder2, folder1, 0, 0, false);
   EXPECT_EQ(root->children().size(), 2u);
   EXPECT_EQ(folder1->children().size(), 1u);
   EXPECT_EQ(folder2->children().size(), 0u);
@@ -1067,7 +1107,7 @@
 
   // Should update the hierarchy.
   AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
-  observer_details_.ExpectEquals(folder1, folder2, 0, 0);
+  observer_details_.ExpectEquals(folder1, folder2, 0, 0, false);
   EXPECT_EQ(root->children().size(), 2u);
   EXPECT_EQ(root->children()[0].get(), folder1);
   EXPECT_EQ(root->children()[1].get(), folder2);
@@ -1734,7 +1774,8 @@
 
   void BookmarkNodeAdded(BookmarkModel* model,
                          const BookmarkNode* parent,
-                         size_t index) override {}
+                         size_t index,
+                         bool added_by_user) override {}
 
   void BookmarkNodeRemoved(BookmarkModel* model,
                            const BookmarkNode* parent,
diff --git a/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc b/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
index 8f4d50a..444f1c9 100644
--- a/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
+++ b/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
@@ -251,7 +251,7 @@
   base::Value::List updated;
   updated.Append(CreateFolder("Container", CreateTestTree()));
 
-  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(), _, _)).Times(5);
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(), _, _, _)).Times(5);
   // The remaining nodes have been pushed to positions 1 and 2; they'll both be
   // removed when at position 1.
   const BookmarkNode* parent = managed_node();
@@ -308,10 +308,10 @@
   CreateModel();
   EXPECT_EQ(2u, managed_node()->children().size());
 
-  EXPECT_CALL(observer_,
-              BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 0));
-  EXPECT_CALL(observer_,
-              BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 1));
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(),
+                                           model_->bookmark_bar_node(), 0, _));
+  EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(),
+                                           model_->bookmark_bar_node(), 1, _));
   model_->AddURL(model_->bookmark_bar_node(), 0, u"Test",
                  GURL("http://google.com/"));
   model_->AddFolder(model_->bookmark_bar_node(), 1, u"Test Folder");
diff --git a/components/bookmarks/test/mock_bookmark_model_observer.h b/components/bookmarks/test/mock_bookmark_model_observer.h
index 7391c92..8d835848 100644
--- a/components/bookmarks/test/mock_bookmark_model_observer.h
+++ b/components/bookmarks/test/mock_bookmark_model_observer.h
@@ -25,8 +25,8 @@
                     const BookmarkNode*,
                     size_t));
 
-  MOCK_METHOD3(BookmarkNodeAdded,
-               void(BookmarkModel*, const BookmarkNode*, size_t));
+  MOCK_METHOD4(BookmarkNodeAdded,
+               void(BookmarkModel*, const BookmarkNode*, size_t, bool));
 
   MOCK_METHOD5(BookmarkNodeRemoved,
                void(BookmarkModel*,
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 8b99f79b..640dfb93 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -1097,7 +1097,7 @@
 
         // Resizing is necessary if we have non-zero translation on Window Y, which can change
         // throughout the lifecycle. Ensure sheet content's bottom is aligned with the base layout.
-        if (mWindow.getAttributes().y != 0
+        if (mBaseHeightProvider != null && mWindow.getAttributes().y != 0
                 && (mCurrentState == SheetState.PEEK || mCurrentState == SheetState.HALF
                         || mCurrentState == SheetState.FULL)) {
             BottomSheetContent content = mSheetContent;
diff --git a/components/desks_storage/core/desk_model.h b/components/desks_storage/core/desk_model.h
index e911ee7..c60257a 100644
--- a/components/desks_storage/core/desk_model.h
+++ b/components/desks_storage/core/desk_model.h
@@ -135,7 +135,7 @@
       base::OnceCallback<void(DeleteEntryStatus status)>;
   // Remove entry with `uuid` from entries. If the entry with `uuid` does not
   // exist, then the deletion is considered a success.
-  virtual void DeleteEntry(const std::string& uuid,
+  virtual void DeleteEntry(const base::GUID& uuid,
                            DeleteEntryCallback callback) = 0;
 
   // Delete all entries.
diff --git a/components/desks_storage/core/desk_model_wrapper.cc b/components/desks_storage/core/desk_model_wrapper.cc
index aa826d86..5c633674 100644
--- a/components/desks_storage/core/desk_model_wrapper.cc
+++ b/components/desks_storage/core/desk_model_wrapper.cc
@@ -81,13 +81,13 @@
   }
 }
 
-void DeskModelWrapper::DeleteEntry(const std::string& uuid_str,
+void DeskModelWrapper::DeleteEntry(const base::GUID& uuid,
                                    DeskModel::DeleteEntryCallback callback) {
   auto status = std::make_unique<DeskModel::DeleteEntryStatus>();
-  if (GetDeskTemplateModel()->HasUuid(uuid_str)) {
-    GetDeskTemplateModel()->DeleteEntry(uuid_str, std::move(callback));
+  if (GetDeskTemplateModel()->HasUuid(uuid.AsLowercaseString())) {
+    GetDeskTemplateModel()->DeleteEntry(uuid, std::move(callback));
   } else {
-    save_and_recall_desks_model_->DeleteEntry(uuid_str, std::move(callback));
+    save_and_recall_desks_model_->DeleteEntry(uuid, std::move(callback));
   }
 }
 
diff --git a/components/desks_storage/core/desk_model_wrapper.h b/components/desks_storage/core/desk_model_wrapper.h
index 0c969f5..ed7cd1a 100644
--- a/components/desks_storage/core/desk_model_wrapper.h
+++ b/components/desks_storage/core/desk_model_wrapper.h
@@ -39,7 +39,7 @@
                       GetEntryByUuidCallback callback) override;
   void AddOrUpdateEntry(std::unique_ptr<ash::DeskTemplate> new_entry,
                         AddOrUpdateEntryCallback callback) override;
-  void DeleteEntry(const std::string& uuid,
+  void DeleteEntry(const base::GUID& uuid,
                    DeleteEntryCallback callback) override;
   void DeleteAllEntries(DeleteEntryCallback callback) override;
   std::size_t GetEntryCount() const override;
diff --git a/components/desks_storage/core/desk_model_wrapper_unittests.cc b/components/desks_storage/core/desk_model_wrapper_unittests.cc
index ca86bb0aa..e1fa2498 100644
--- a/components/desks_storage/core/desk_model_wrapper_unittests.cc
+++ b/components/desks_storage/core/desk_model_wrapper_unittests.cc
@@ -622,7 +622,7 @@
                                    base::BindOnce(&VerifyEntryAddedCorrectly));
 
   model_wrapper_->DeleteEntry(
-      kTestUuid1,
+      base::GUID::ParseCaseInsensitive(kTestUuid1),
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
@@ -637,7 +637,7 @@
                                    base::BindOnce(&VerifyEntryAddedCorrectly));
 
   model_wrapper_->DeleteEntry(
-      kTestUuid3,
+      base::GUID::ParseCaseInsensitive(kTestUuid3),
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc
index f282150..9d4334c74 100644
--- a/components/desks_storage/core/desk_sync_bridge.cc
+++ b/components/desks_storage/core/desk_sync_bridge.cc
@@ -1155,7 +1155,7 @@
   std::move(callback).Run(AddOrUpdateEntryStatus::kOk);
 }
 
-void DeskSyncBridge::DeleteEntry(const std::string& uuid_str,
+void DeskSyncBridge::DeleteEntry(const base::GUID& uuid,
                                  DeleteEntryCallback callback) {
   if (!IsReady()) {
     // This sync bridge has not finished initializing.
@@ -1164,8 +1164,6 @@
     return;
   }
 
-  const base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
-
   if (GetUserEntryByUUID(uuid) == nullptr) {
     // Consider the deletion successful if the entry does not exist.
     std::move(callback).Run(DeleteEntryStatus::kOk);
diff --git a/components/desks_storage/core/desk_sync_bridge.h b/components/desks_storage/core/desk_sync_bridge.h
index 7efcbdd..88c838a 100644
--- a/components/desks_storage/core/desk_sync_bridge.h
+++ b/components/desks_storage/core/desk_sync_bridge.h
@@ -69,7 +69,7 @@
                       GetEntryByUuidCallback callback) override;
   void AddOrUpdateEntry(std::unique_ptr<ash::DeskTemplate> new_entry,
                         AddOrUpdateEntryCallback callback) override;
-  void DeleteEntry(const std::string& uuid,
+  void DeleteEntry(const base::GUID& uuid,
                    DeleteEntryCallback callback) override;
   void DeleteAllEntries(DeleteEntryCallback callback) override;
   std::size_t GetEntryCount() const override;
diff --git a/components/desks_storage/core/desk_sync_bridge_unittest.cc b/components/desks_storage/core/desk_sync_bridge_unittest.cc
index 055ae44f..b2c31ac9 100644
--- a/components/desks_storage/core/desk_sync_bridge_unittest.cc
+++ b/components/desks_storage/core/desk_sync_bridge_unittest.cc
@@ -1456,7 +1456,7 @@
   // Delete template 1.
   base::RunLoop loop;
   bridge()->DeleteEntry(
-      kTestUuid1.AsLowercaseString(),
+      kTestUuid1,
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
         loop.Quit();
diff --git a/components/desks_storage/core/local_desk_data_manager.cc b/components/desks_storage/core/local_desk_data_manager.cc
index fe45d60f..687c45f 100644
--- a/components/desks_storage/core/local_desk_data_manager.cc
+++ b/components/desks_storage/core/local_desk_data_manager.cc
@@ -122,8 +122,8 @@
 // file given the `file_path` to the desk template or save and recall desk
 // directory and the entry's `uuid`.
 base::FilePath GetFullyQualifiedPath(base::FilePath file_path,
-                                     const std::string& uuid) {
-  std::string filename(uuid);
+                                     const base::GUID& uuid) {
+  std::string filename = uuid.AsLowercaseString();
   filename.append(kFileExtension);
 
   return base::FilePath(file_path.Append(base::FilePath(filename)));
@@ -287,7 +287,7 @@
 }
 
 void LocalDeskDataManager::DeleteEntry(
-    const std::string& uuid_str,
+    const base::GUID& uuid,
     DeskModel::DeleteEntryCallback callback) {
   auto status = std::make_unique<DeskModel::DeleteEntryStatus>();
   if (cache_status_ != CacheStatus::kOk) {
@@ -296,7 +296,6 @@
     return;
   }
 
-  const base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
   if (!uuid.is_valid()) {
     // There does not exist an entry with invalid UUID.
     // Therefore the deletion request is vicariously successful.
@@ -314,7 +313,7 @@
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&LocalDeskDataManager::DeleteEntryTask,
-                     base::Unretained(this), uuid_str, status.get()),
+                     base::Unretained(this), uuid, status.get()),
       base::BindOnce(&LocalDeskDataManager::OnDeleteEntry,
                      weak_ptr_factory_.GetWeakPtr(), std::move(status),
                      std::move(entry), std::move(callback)));
@@ -509,7 +508,7 @@
     DeskModel::AddOrUpdateEntryStatus* out_status_ptr,
     base::Value entry_base_value) {
   const base::FilePath fully_qualified_path =
-      GetFullyQualifiedPath(local_saved_desk_path_, uuid.AsLowercaseString());
+      GetFullyQualifiedPath(local_saved_desk_path_, uuid);
   if (WriteTemplateFile(fully_qualified_path, std::move(entry_base_value))) {
     *out_status_ptr = DeskModel::AddOrUpdateEntryStatus::kOk;
   } else {
@@ -536,10 +535,10 @@
 }
 
 void LocalDeskDataManager::DeleteEntryTask(
-    const std::string& uuid_str,
+    const base::GUID& uuid,
     DeskModel::DeleteEntryStatus* out_status_ptr) {
   const base::FilePath fully_qualified_path =
-      GetFullyQualifiedPath(local_saved_desk_path_, uuid_str);
+      GetFullyQualifiedPath(local_saved_desk_path_, uuid);
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
   if (base::DeleteFile(fully_qualified_path)) {
diff --git a/components/desks_storage/core/local_desk_data_manager.h b/components/desks_storage/core/local_desk_data_manager.h
index 9230aeb..686ed0c9 100644
--- a/components/desks_storage/core/local_desk_data_manager.h
+++ b/components/desks_storage/core/local_desk_data_manager.h
@@ -60,7 +60,7 @@
                       GetEntryByUuidCallback callback) override;
   void AddOrUpdateEntry(std::unique_ptr<ash::DeskTemplate> new_entry,
                         AddOrUpdateEntryCallback callback) override;
-  void DeleteEntry(const std::string& uuid,
+  void DeleteEntry(const base::GUID& uuid,
                    DeleteEntryCallback callback) override;
   void DeleteAllEntries(DeleteEntryCallback callback) override;
   std::size_t GetEntryCount() const override;
@@ -117,9 +117,9 @@
       const base::GUID uuid,
       std::unique_ptr<ash::DeskTemplate> entry);
 
-  // Remove entry with `uuid_str`. If the entry with `uuid_str` does not
-  // exist, then the deletion is considered a success.
-  void DeleteEntryTask(const std::string& uuid_str,
+  // Remove entry with `uuid`. If the entry with `uuid` does not exist, then the
+  // deletion is considered a success.
+  void DeleteEntryTask(const base::GUID& uuid,
                        DeskModel::DeleteEntryStatus* status_ptr);
 
   // Delete all entries.
diff --git a/components/desks_storage/core/local_desk_data_manager_unittests.cc b/components/desks_storage/core/local_desk_data_manager_unittests.cc
index 7fe09f4..756817f 100644
--- a/components/desks_storage/core/local_desk_data_manager_unittests.cc
+++ b/components/desks_storage/core/local_desk_data_manager_unittests.cc
@@ -565,7 +565,7 @@
                                   base::BindOnce(&VerifyEntryAddedCorrectly));
 
   data_manager_->DeleteEntry(
-      kTestUuid1,
+      base::GUID::ParseCaseInsensitive(kTestUuid1),
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
@@ -680,7 +680,7 @@
   VerifyAllEntries(1ul, "Added one save and recall desk");
   EXPECT_EQ(data_manager_->GetSaveAndRecallDeskEntryCount(), 1ul);
   data_manager_->DeleteEntry(
-      kTestSaveAndRecallDeskUuid1,
+      base::GUID::ParseCaseInsensitive(kTestSaveAndRecallDeskUuid1),
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
@@ -838,7 +838,7 @@
   base::SetPosixFilePermissions(temp_dir_.GetPath(),
                                 base::FILE_PERMISSION_READ_BY_USER);
   data_manager_->DeleteEntry(
-      kTestUuid1,
+      base::GUID::ParseCaseInsensitive(kTestUuid1),
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kFailure);
       }));
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorPickerSimple.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorPickerSimple.java
index 0b03e8c2..36bb934 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorPickerSimple.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorPickerSimple.java
@@ -29,6 +29,8 @@
             R.string.color_picker_button_yellow, R.string.color_picker_button_black,
             R.string.color_picker_button_white};
 
+    private ColorSuggestionListAdapter mAdapter;
+
     public ColorPickerSimple(Context context) {
         super(context);
     }
@@ -60,10 +62,9 @@
             }
         }
 
-        ColorSuggestionListAdapter adapter =
-                new ColorSuggestionListAdapter(getContext(), suggestions);
-        adapter.setOnColorSuggestionClickListener(this);
-        setAdapter(adapter);
+        mAdapter = new ColorSuggestionListAdapter(getContext(), suggestions);
+        mAdapter.setOnColorSuggestionClickListener(this);
+        setAdapter(mAdapter);
         setAccessibilityDelegate(new View.AccessibilityDelegate() {
             @Override
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -77,5 +78,8 @@
     @Override
     public void onColorSuggestionClick(ColorSuggestion suggestion) {
         mOnColorChangedListener.onColorChanged(suggestion.mColor);
+
+        assert mAdapter != null;
+        mAdapter.setSelectedColor(suggestion.mColor);
     }
 }
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorSuggestionListAdapter.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorSuggestionListAdapter.java
index 0dc02d4..4e51b62 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorSuggestionListAdapter.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/delegate/ColorSuggestionListAdapter.java
@@ -25,6 +25,7 @@
     private Context mContext;
     private ColorSuggestion[] mSuggestions;
     private OnColorSuggestionClickListener mListener;
+    private int mSelectedColor;
 
     /**
      * The callback used to indicate the user has clicked on a suggestion.
@@ -53,6 +54,14 @@
     }
 
     /**
+     * Sets the currently selected color so the corresponding list item can be labeled.
+     * @param selectedColor The newly selected color.
+     */
+    public void setSelectedColor(int selectedColor) {
+        mSelectedColor = selectedColor;
+    }
+
+    /**
      * Sets up the color button to represent a color suggestion.
      *
      * @param button The button view to set up.
@@ -84,6 +93,7 @@
                 super.onInitializeAccessibilityNodeInfo(host, info);
                 info.setCollectionItemInfo(
                         AccessibilityNodeInfo.CollectionItemInfo.obtain(index, 1, 1, 1, false));
+                info.setSelected(suggestion.mColor == mSelectedColor);
             }
         });
     }
diff --git a/components/exo/pointer.cc b/components/exo/pointer.cc
index 75cd6bb..42989f0 100644
--- a/components/exo/pointer.cc
+++ b/components/exo/pointer.cc
@@ -265,7 +265,7 @@
 
   // Permission of Pointer lock is controlled by SecurityDelegate, created per
   // server instance. Default implementation allows this for ARC and Lacros
-  // windows which have their own security mechanism and are consiered trusted.
+  // windows which have their own security mechanism and are considered trusted.
   aura::Window* toplevel = constrained_surface->window()->GetToplevelWindow();
   auto* shell_surface_base = GetShellSurfaceBaseForWindow(toplevel);
   auto* security_delegate = shell_surface_base->GetSecurityDelegate();
@@ -398,6 +398,7 @@
   gfx::Point p = location_when_pointer_capture_enabled_
                      ? *location_when_pointer_capture_enabled_
                      : root->bounds().CenterPoint();
+  expected_next_mouse_location_ = p;
   root->MoveCursorTo(p);
 
   aura::Window* window = capture_window_;
@@ -528,12 +529,28 @@
 #endif
 
     if (!same_location) {
-      bool needs_frame = HandleRelativePointerMotion(
-          event->time_stamp(), location_in_root, ordinal_motion);
+      bool ignore_motion = false;
+      if (expected_next_mouse_location_) {
+        const gfx::Point& expected = *expected_next_mouse_location_;
+        // Since MoveCursorTo() takes integer coordinates, the resulting move
+        // could have a conversion error of up to 2 due to fractional scale
+        // factors.
+        if (std::abs(location_in_root.x() - expected.x()) <= 2 &&
+            std::abs(location_in_root.y() - expected.y()) <= 2) {
+          // This was a synthetic move event, so do not forward it and clear the
+          // expected location.
+          expected_next_mouse_location_.reset();
+          ignore_motion = true;
+        }
+      }
+      bool needs_frame =
+          !ignore_motion &&
+          HandleRelativePointerMotion(event->time_stamp(), location_in_root,
+                                      ordinal_motion);
       if (capture_window_) {
         if (ShouldMoveToCenter())
           MoveCursorToCenterOfActiveDisplay();
-      } else if (event->type() != ui::ET_MOUSE_EXITED) {
+      } else if (event->type() != ui::ET_MOUSE_EXITED && !ignore_motion) {
         delegate_->OnPointerMotion(event->time_stamp(), location_in_target);
         needs_frame = true;
       }
@@ -997,7 +1014,7 @@
     return;
   aura::Window* root = capture_window_->GetRootWindow();
   gfx::Point p = root->bounds().CenterPoint();
-  location_synthetic_move_ = p;
+  expected_next_mouse_location_ = p;
   root->MoveCursorTo(p);
 }
 
@@ -1008,19 +1025,6 @@
   if (!relative_pointer_delegate_)
     return false;
 
-  if (location_synthetic_move_) {
-    gfx::Point synthetic = *location_synthetic_move_;
-    // Since MoveCursorTo() takes integer coordinates, the resulting move could
-    // have a conversion error of up to 2 due to fractional scale factors.
-    if (std::abs(location_in_root.x() - synthetic.x()) <= 2 &&
-        std::abs(location_in_root.y() - synthetic.y()) <= 2) {
-      // This was a synthetic move event, so do not forward it and clear the
-      // synthetic move.
-      location_synthetic_move_.reset();
-      return false;
-    }
-  }
-
   gfx::Vector2dF delta = location_in_root - location_;
   relative_pointer_delegate_->OnPointerRelativeMotion(
       time_stamp, delta,
diff --git a/components/exo/pointer.h b/components/exo/pointer.h
index 187d5f9..8b0b457ee 100644
--- a/components/exo/pointer.h
+++ b/components/exo/pointer.h
@@ -233,7 +233,7 @@
 
   // If this is not nullptr, a synthetic move was sent and this points to the
   // location of a generated move that was sent which should not be forwarded.
-  absl::optional<gfx::Point> location_synthetic_move_;
+  absl::optional<gfx::Point> expected_next_mouse_location_;
 
   // The window with pointer capture. Pointer capture is enabled if and only if
   // this is not null.
diff --git a/components/exo/pointer_unittest.cc b/components/exo/pointer_unittest.cc
index 3a46589..8cd46a1 100644
--- a/components/exo/pointer_unittest.cc
+++ b/components/exo/pointer_unittest.cc
@@ -1815,7 +1815,7 @@
   pointer_.reset();
 }
 
-TEST_F(PointerConstraintTest, DefaultSecrityDeletegate) {
+TEST_F(PointerConstraintTest, DefaultSecurityDeletegate) {
   auto default_security_delegate =
       SecurityDelegate::GetDefaultSecurityDelegate();
   auto shell_surface = test::ShellSurfaceBuilder({10, 10})
@@ -1860,6 +1860,34 @@
   pointer_.reset();
 }
 
+TEST_F(PointerConstraintTest, NoPointerMotionEventWhenUnconstrainingPointer) {
+  testing::MockFunction<void(std::string check_point_name)> check;
+  {
+    testing::InSequence s;
+
+    EXPECT_CALL(check, Call("Unconstrain pointer"));
+    EXPECT_CALL(delegate_, OnPointerMotion(testing::_, testing::_)).Times(0);
+  }
+
+  generator_->MoveMouseTo(
+      surface_->window()->GetBoundsInScreen().CenterPoint() +
+      gfx::Vector2d(4, 4));
+
+  EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
+
+  generator_->MoveMouseTo(
+      surface_->window()->GetBoundsInScreen().CenterPoint() +
+      gfx::Vector2d(-4, -4));
+
+  check.Call("Unconstrain pointer");
+
+  pointer_->UnconstrainPointerByUserAction();
+
+  // Ensure the posted task for synthesized mouse move event is run.
+  base::RunLoop().RunUntilIdle();
+
+  pointer_.reset();
+}
 #endif
 
 TEST_F(PointerTest, PointerStylus) {
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 8483d959..9672f91 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -152,7 +152,7 @@
 
 // Enables prefetching of the zero prefix suggestions for eligible users on NTP.
 const base::Feature kZeroSuggestPrefetching{"ZeroSuggestPrefetching",
-                                            enabled_by_default_desktop_only};
+                                            enabled_by_default_desktop_android};
 
 // Enables prefetching of the zero prefix suggestions for eligible users on SRP.
 const base::Feature kZeroSuggestPrefetchingOnSRP{
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index db0186b4..d1f5ec3 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -390,7 +390,7 @@
 void PasswordAutofillManager::DidSelectSuggestion(
     const std::u16string& value,
     int frontend_id,
-    const std::string& backend_id) {
+    const autofill::Suggestion::BackendId& backend_id) {
   ClearPreviewedForm();
   if (frontend_id == autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY ||
       frontend_id == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_EMPTY ||
@@ -479,9 +479,10 @@
         PasswordDropdownSelectedOption::kWebAuthn,
         password_client_->IsIncognito());
     password_client_->GetWebAuthnCredentialsDelegate()
-        ->SelectWebAuthnCredential(absl::holds_alternative<std::string>(payload)
-                                       ? absl::get<std::string>(payload)
-                                       : std::string());
+        ->SelectWebAuthnCredential(
+            absl::holds_alternative<autofill::Suggestion::BackendId>(payload)
+                ? absl::get<autofill::Suggestion::BackendId>(payload).value()
+                : std::string());
   } else if (frontend_id ==
              autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE) {
     metrics_util::LogPasswordDropdownItemSelected(
diff --git a/components/password_manager/core/browser/password_autofill_manager.h b/components/password_manager/core/browser/password_autofill_manager.h
index 349c6b46..0b4e764 100644
--- a/components/password_manager/core/browser/password_autofill_manager.h
+++ b/components/password_manager/core/browser/password_autofill_manager.h
@@ -54,9 +54,10 @@
   void OnPopupShown() override;
   void OnPopupHidden() override;
   void OnPopupSuppressed() override;
-  void DidSelectSuggestion(const std::u16string& value,
-                           int frontend_id,
-                           const std::string& backend_id) override;
+  void DidSelectSuggestion(
+      const std::u16string& value,
+      int frontend_id,
+      const autofill::Suggestion::BackendId& backend_id) override;
   void DidAcceptSuggestion(const std::u16string& value,
                            int frontend_id,
                            const autofill::Suggestion::Payload& payload,
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 17b3081..adb7190 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -23,6 +23,7 @@
 #include "components/autofill/core/browser/test_autofill_client.h"
 #include "components/autofill/core/browser/test_autofill_driver.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/browser/ui/suggestion_test_helpers.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -454,7 +455,7 @@
         is_suggestion_on_password_field
             ? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY
             : autofill::POPUP_ITEM_ID_USERNAME_ENTRY,
-        std::string(), 1);
+        autofill::Suggestion::BackendId(), 1);
     histograms.ExpectUniqueSample(
         kDropdownSelectedHistogram,
         metrics_util::PasswordDropdownSelectedOption::kPassword, 1);
@@ -524,7 +525,7 @@
         is_suggestion_on_password_field
             ? autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_PASSWORD_ENTRY
             : autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_USERNAME_ENTRY,
-        std::string(),
+        autofill::Suggestion::BackendId(),
         /*position=*/1);
   }
 }
@@ -624,7 +625,7 @@
                                              /*has_re_signin=*/false)));
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
   ASSERT_GE(suggestions.size(), 2u);
   EXPECT_TRUE(suggestions.back().is_loading);
 }
@@ -659,7 +660,7 @@
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_,
       autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
   ASSERT_GE(suggestions.size(), 2u);
   EXPECT_TRUE(suggestions.back().is_loading);
 }
@@ -678,8 +679,8 @@
   EXPECT_CALL(autofill_client, HideAutofillPopup);
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_,
-      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN, std::string(),
-      1);
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN,
+      autofill::Suggestion::BackendId(), 1);
 }
 
 // Test that the popup is updated once "opt in and fill" is clicked and the
@@ -726,7 +727,7 @@
 
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
   ASSERT_GE(suggestions.size(), 2u);
   EXPECT_FALSE(suggestions.back().is_loading);
 }
@@ -778,7 +779,7 @@
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_,
       autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
   ASSERT_GE(suggestions.size(), 2u);
   EXPECT_FALSE(suggestions.back().is_loading);
 }
@@ -813,7 +814,7 @@
 
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
 }
 
 // Test that the popup is hidden and password generation is triggered once
@@ -853,7 +854,7 @@
   password_autofill_manager_->DidAcceptSuggestion(
       test_username_,
       autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
 }
 
 // Test that the popup shows an empty state if opted-into an empty store.
@@ -1254,7 +1255,7 @@
   EXPECT_CALL(*client.mock_driver(),
               PreviewSuggestion(std::u16string(), test_password_));
   password_autofill_manager_->DidSelectSuggestion(
-      no_username_string, 0 /*not used*/, std::string() /*not used*/);
+      no_username_string, 0 /*not used*/, Suggestion::BackendId() /*not used*/);
   testing::Mock::VerifyAndClearExpectations(client.mock_driver());
 
   // Check that fill of the empty username works.
@@ -1264,8 +1265,8 @@
       autofill_client,
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
   password_autofill_manager_->DidAcceptSuggestion(
-      no_username_string, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(),
-      1);
+      no_username_string, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+      autofill::Suggestion::BackendId(), 1);
   testing::Mock::VerifyAndClearExpectations(client.mock_driver());
 }
 
@@ -1313,7 +1314,7 @@
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
   password_autofill_manager_->DidAcceptSuggestion(
       std::u16string(), autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY,
-      std::string(), 0);
+      autofill::Suggestion::BackendId(), 0);
   histograms.ExpectUniqueSample(
       kDropdownSelectedHistogram,
       metrics_util::PasswordDropdownSelectedOption::kShowAll, 1);
@@ -1425,7 +1426,7 @@
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
   password_autofill_manager_->DidAcceptSuggestion(
       std::u16string(), autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY,
-      std::string(), 1);
+      autofill::Suggestion::BackendId(), 1);
   histograms.ExpectUniqueSample(
       kDropdownSelectedHistogram,
       metrics_util::PasswordDropdownSelectedOption::kGenerate, 1);
@@ -1576,8 +1577,8 @@
     // Accept the suggestion to start the filing process which tries to
     // reauthenticate the user if possible.
     password_autofill_manager_->DidAcceptSuggestion(
-        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(),
-        1);
+        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+        autofill::Suggestion::BackendId(), 1);
   }
 }
 
@@ -1633,8 +1634,8 @@
     // Accept the suggestion to start the filing process which tries to
     // reauthenticate the user if possible.
     password_autofill_manager_->DidAcceptSuggestion(
-        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(),
-        1);
+        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+        autofill::Suggestion::BackendId(), 1);
   }
 }
 
@@ -1690,8 +1691,8 @@
     // Accept the suggestion to start the filing process which tries to
     // reauthenticate the user if possible.
     password_autofill_manager_->DidAcceptSuggestion(
-        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(),
-        1);
+        test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+        autofill::Suggestion::BackendId(), 1);
   }
 }
 
@@ -1733,7 +1734,8 @@
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(), 1);
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+      autofill::Suggestion::BackendId(), 1);
 
   EXPECT_CALL(*authenticator_.get(),
               Cancel(BiometricAuthRequester::kAutofillSuggestion));
@@ -1778,7 +1780,8 @@
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(), 1);
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+      autofill::Suggestion::BackendId(), 1);
 
   EXPECT_CALL(*authenticator_.get(),
               Cancel(BiometricAuthRequester::kAutofillSuggestion));
@@ -1824,7 +1827,8 @@
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, std::string(), 1);
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+      autofill::Suggestion::BackendId(), 1);
 
   EXPECT_CALL(*authenticator_.get(),
               Cancel(BiometricAuthRequester::kAutofillSuggestion));
@@ -1843,7 +1847,7 @@
   const std::u16string kAuthenticator = u"Use device sign-in";
   autofill::Suggestion webauthn_credential(kName);
   webauthn_credential.frontend_id = autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL;
-  webauthn_credential.payload = kId;
+  webauthn_credential.payload = Suggestion::BackendId(kId);
   webauthn_credential.label = kAuthenticator;
   ON_CALL(webauthn_credentials_delegate, IsWebAuthnAutofillEnabled)
       .WillByDefault(Return(true));
@@ -1871,7 +1875,8 @@
                   autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE,
 #endif  // !BUILDFLAG(IS_ANDROID)
                   autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)));
-  EXPECT_EQ(absl::get<std::string>(open_args.suggestions[0].payload), kId);
+  EXPECT_EQ(open_args.suggestions[0].GetPayload<Suggestion::BackendId>(),
+            Suggestion::BackendId(kId));
   EXPECT_EQ(open_args.suggestions[0].frontend_id,
             autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL);
   EXPECT_EQ(open_args.suggestions[0].main_text.value, kName);
@@ -1883,7 +1888,7 @@
               PreviewSuggestion(kName, /*password=*/std::u16string(u"")));
   password_autofill_manager_->DidSelectSuggestion(
       kName, autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL,
-      std::string() /*not used*/);
+      Suggestion::BackendId() /*not used*/);
   testing::Mock::VerifyAndClearExpectations(client.mock_driver());
 
   // Check that selecting the credential reports back to the client.
@@ -1892,7 +1897,8 @@
       autofill_client,
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
   password_autofill_manager_->DidAcceptSuggestion(
-      kName, autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL, kId, /*position=*/1);
+      kName, autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL,
+      autofill::Suggestion::BackendId(kId), /*position=*/1);
 }
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -1940,7 +1946,7 @@
   password_autofill_manager_->DidAcceptSuggestion(
       kSignInWithAnotherDeviceText,
       autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE,
-      /*payload=*/std::string(), /*position=*/1);
+      /*payload=*/autofill::Suggestion::BackendId(), /*position=*/1);
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.cc b/components/pdf/renderer/pdf_view_web_plugin_client.cc
index 0beee3a..937e0de 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.cc
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.cc
@@ -115,8 +115,9 @@
 }
 
 void PdfViewWebPluginClient::ReportFindInPageSelection(int identifier,
-                                                       int index) {
-  plugin_container_->ReportFindInPageSelection(identifier, index);
+                                                       int index,
+                                                       bool final_update) {
+  plugin_container_->ReportFindInPageSelection(identifier, index, final_update);
 }
 
 void PdfViewWebPluginClient::ReportFindInPageTickmarks(
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.h b/components/pdf/renderer/pdf_view_web_plugin_client.h
index 1a1bf3c6..9d2cc65 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.h
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.h
@@ -49,7 +49,9 @@
   void ReportFindInPageMatchCount(int identifier,
                                   int total,
                                   bool final_update) override;
-  void ReportFindInPageSelection(int identifier, int index) override;
+  void ReportFindInPageSelection(int identifier,
+                                 int index,
+                                 bool final_update) override;
   void ReportFindInPageTickmarks(
       const std::vector<gfx::Rect>& tickmarks) override;
   float DeviceScaleFactor() override;
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.cc b/components/privacy_sandbox/privacy_sandbox_settings.cc
index 453c304..8458b1a 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings.cc
@@ -328,6 +328,13 @@
                                            top_frame_origin);
 }
 
+bool PrivacySandboxSettings::IsPrivateAggregationAllowed(
+    const url::Origin& top_frame_origin,
+    const url::Origin& reporting_origin) const {
+  return IsPrivacySandboxEnabledForContext(reporting_origin.GetURL(),
+                                           top_frame_origin);
+}
+
 bool PrivacySandboxSettings::IsPrivacySandboxEnabled() const {
   // If the delegate is restricting access the Privacy Sandbox is disabled.
   if (delegate_->IsPrivacySandboxRestricted())
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.h b/components/privacy_sandbox/privacy_sandbox_settings.h
index f75fd48e..44509d1c 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings.h
@@ -155,6 +155,12 @@
   bool IsSharedStorageAllowed(const url::Origin& top_frame_origin,
                               const url::Origin& accessing_origin) const;
 
+  // Determines whether the Private Aggregation API is allowable in a particular
+  // context. `top_frame_origin` is the associated top-frame origin of the
+  // calling context.
+  bool IsPrivateAggregationAllowed(const url::Origin& top_frame_origin,
+                                   const url::Origin& reporting_origin) const;
+
   // Returns whether the profile has the Privacy Sandbox enabled. This consults
   // the main preference, as well as the delegate to check whether the sandbox
   // is restricted. It does not consider any cookie settings. A return value of
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
index 18ec624..bd044f7 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
@@ -131,6 +131,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -180,6 +184,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -223,6 +231,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -265,6 +277,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -319,6 +335,13 @@
       url::Origin::Create(GURL("https://unrelated-d.com")),
       url::Origin::Create(GURL("https://unrelated-e.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://unrelated-a.com")),
+      url::Origin::Create(GURL("https://unrelated-b.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -387,6 +410,10 @@
       url::Origin::Create(GURL("https://yet-another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_TRUE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://another-test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -427,6 +454,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -465,6 +496,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -501,6 +536,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
@@ -542,6 +581,10 @@
       url::Origin::Create(GURL("https://another-test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
 
+  EXPECT_FALSE(privacy_sandbox_settings()->IsPrivateAggregationAllowed(
+      url::Origin::Create(GURL("https://test.com")),
+      url::Origin::Create(GURL("https://embedded.com"))));
+
   EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeAllowed(
       url::Origin::Create(GURL("https://test.com")),
       url::Origin::Create(GURL("https://embedded.com"))));
diff --git a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
index 7618054..949fb64 100644
--- a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
+++ b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
@@ -19,6 +19,24 @@
 
 namespace {
 
+bool AreWindowShadowsDisabled() {
+  // When:
+  // 1) Shadows are being generated by the window server
+  // 2) The window with the shadow has a layer (all of Chrome's do)
+  // 3) Software compositing is in use (it is in most test configs, which
+  //    run in VMs)
+  // 4) There are many windows in use at once (they are when running
+  //    test in parallel)
+  // The window server seems to crash with distressing frequency. To hopefully
+  // mitigate that, disable window shadows when running on a bot.
+  // For context on this see:
+  //   https://crbug.com/899286
+  //   https://crbug.com/828031
+  //   https://crbug.com/515627, especially #63 and #67
+  static bool is_headless = getenv("CHROME_HEADLESS") != nullptr;
+  return is_headless;
+}
+
 // AppKit quirk: -[NSWindow orderWindow] does not handle reordering for children
 // windows. Their order is fixed to the attachment order (the last attached
 // window is on the top). Therefore, work around it by re-parenting in our
@@ -218,6 +236,10 @@
 
 // Public methods.
 
+- (void)setHasShadow:(BOOL)flag {
+  [super setHasShadow:flag && !AreWindowShadowsDisabled()];
+}
+
 - (void)setCommandDispatcherDelegate:(id<CommandDispatcherDelegate>)delegate {
   [_commandDispatcher setDelegate:delegate];
 }
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 9a7641b..0a2a8ff 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -58,24 +58,6 @@
 namespace {
 constexpr auto kUIPaintTimeout = base::Seconds(5);
 
-bool AreWindowShadowsDisabled() {
-  // When:
-  // 1) Shadows are being generated by the window server
-  // 2) The window with the shadow has a layer (all of Chrome's do)
-  // 3) Software compositing is in use (it is in most test configs, which
-  //    run in VMs)
-  // 4) There are many windows in use at once (they are when running
-  //    test in parallel)
-  // The window server seems to crash with distressing frequency. To hopefully
-  // mitigate that, disable window shadows when running on a bot.
-  // For context on this see:
-  //   https://crbug.com/899286
-  //   https://crbug.com/828031
-  //   https://crbug.com/515627, especially #63 and #67
-  static bool is_headless = getenv("CHROME_HEADLESS") != nullptr;
-  return is_headless;
-}
-
 // Returns the display that the specified window is on.
 display::Display GetDisplayForWindow(NSWindow* window) {
   return display::Screen::GetScreen()->GetDisplayNearestWindow(window);
@@ -477,8 +459,8 @@
                               NSWindowCollectionBehaviorParticipatesInCycle];
   }
 
-  [window_ setHasShadow:params->has_window_server_shadow &&
-                        !AreWindowShadowsDisabled()];
+  [window_ setHasShadow:params->has_window_server_shadow];
+
   // Don't allow dragging sheets.
   if (params->modal_type == ui::MODAL_TYPE_WINDOW)
     [window_ setMovable:NO];
diff --git a/components/safe_search_api/safe_search/safe_search_url_checker_client.cc b/components/safe_search_api/safe_search/safe_search_url_checker_client.cc
index c53542b..69ecb34 100644
--- a/components/safe_search_api/safe_search/safe_search_url_checker_client.cc
+++ b/components/safe_search_api/safe_search/safe_search_url_checker_client.cc
@@ -42,30 +42,29 @@
 // returns true on success. Otherwise, returns false and doesn't set |is_porn|.
 bool ParseResponse(const std::string& response, bool* is_porn) {
   absl::optional<base::Value> optional_value = base::JSONReader::Read(response);
-  const base::DictionaryValue* dict = nullptr;
-  if (!optional_value || !optional_value.value().GetAsDictionary(&dict)) {
+  if (!optional_value || !optional_value.value().is_dict()) {
     DLOG(WARNING) << "ParseResponse failed to parse global dictionary";
     return false;
   }
-  const base::ListValue* classifications_list = nullptr;
-  if (!dict->GetList("classifications", &classifications_list)) {
+  const base::Value::Dict& dict = optional_value.value().GetDict();
+  const base::Value::List* classifications_list =
+      dict.FindList("classifications");
+  if (!classifications_list) {
     DLOG(WARNING) << "ParseResponse failed to parse classifications list";
     return false;
   }
-  if (classifications_list->GetListDeprecated().size() != 1) {
+  if (classifications_list->size() != 1u) {
     DLOG(WARNING) << "ParseResponse expected exactly one result";
     return false;
   }
-  const base::Value& classification_value =
-      classifications_list->GetListDeprecated()[0];
+  const base::Value& classification_value = (*classifications_list)[0];
   if (!classification_value.is_dict()) {
     DLOG(WARNING) << "ParseResponse failed to parse classification dict";
     return false;
   }
-  const base::DictionaryValue& classification_dict =
-      base::Value::AsDictionaryValue(classification_value);
+  const base::Value::Dict& classification_dict = classification_value.GetDict();
   absl::optional<bool> is_porn_opt =
-      classification_dict.FindBoolKey("pornography");
+      classification_dict.FindBool("pornography");
   if (is_porn_opt.has_value())
     *is_porn = is_porn_opt.value();
   return true;
diff --git a/components/segmentation_platform/internal/metadata/metadata_utils.cc b/components/segmentation_platform/internal/metadata/metadata_utils.cc
index 4128d5bf..06fd7f5 100644
--- a/components/segmentation_platform/internal/metadata/metadata_utils.cc
+++ b/components/segmentation_platform/internal/metadata/metadata_utils.cc
@@ -362,9 +362,19 @@
                            model_metadata.min_signal_collection_length()));
   }
   if (model_metadata.has_result_time_to_live()) {
-    result.append(base::StringPrintf("result_time_to_live:%" PRId64,
+    result.append(base::StringPrintf("result_time_to_live:%" PRId64 ", ",
                                      model_metadata.result_time_to_live()));
   }
+  if (model_metadata.has_output_collection_delay_sec()) {
+    result.append(
+        base::StringPrintf("output_collection_delay_sec:%" PRId64 ", ",
+                           model_metadata.output_collection_delay_sec()));
+  }
+  if (model_metadata.has_upload_tensors()) {
+    result.append(
+        base::StringPrintf("upload_tensors: %s",
+                           model_metadata.upload_tensors() ? "true" : "false"));
+  }
 
   if (base::EndsWith(result, ", "))
     result.resize(result.size() - 2);
diff --git a/components/segmentation_platform/public/proto/model_metadata.proto b/components/segmentation_platform/public/proto/model_metadata.proto
index 0f94f4c..1dcb45a0 100644
--- a/components/segmentation_platform/public/proto/model_metadata.proto
+++ b/components/segmentation_platform/public/proto/model_metadata.proto
@@ -227,7 +227,7 @@
 
 // Metadata about a segmentation model for a given segment. Contains information
 // on how to use the model such as collecting signals, interpreting results etc.
-// Next tag: 12
+// Next tag: 14
 message SegmentationModelMetadata {
   // The version information needed to validate segmentation models.
   optional VersionInfo version_info = 9;
@@ -285,4 +285,13 @@
   // The default key to use during the mapping process if no key has been
   // provided.
   optional string default_discrete_mapping = 8;
+
+  // The delay, in seconds, to collect output tensors after input tensors are
+  // collected. For example, output labels can be collected one week after input
+  // tensors are collected. If not specified, output tensors are collected in
+  // the same time period as input tensors.
+  optional int64 output_collection_delay_sec = 12;
+
+  // Whether the client should upload the input and output tensors through UKM.
+  optional bool upload_tensors = 13;
 }
diff --git a/components/sync/base/features.h b/components/sync/base/features.h
index e273d48..0806302 100644
--- a/components/sync/base/features.h
+++ b/components/sync/base/features.h
@@ -24,7 +24,7 @@
     "CacheBaseEntitySpecificsInMetadata", base::FEATURE_DISABLED_BY_DEFAULT};
 
 inline constexpr base::Feature kEnableSyncImmediatelyInFRE{
-    "EnableSyncImmediatelyInFRE", base::FEATURE_DISABLED_BY_DEFAULT};
+    "EnableSyncImmediatelyInFRE", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Causes Sync to ignore updates encrypted with keys that have been missing for
 // too long from this client; Sync will proceed normally as if those updates
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl.cc b/components/sync_bookmarks/bookmark_model_observer_impl.cc
index 1035d50a..6c0e4e8 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl.cc
+++ b/components/sync_bookmarks/bookmark_model_observer_impl.cc
@@ -148,7 +148,8 @@
 void BookmarkModelObserverImpl::BookmarkNodeAdded(
     bookmarks::BookmarkModel* model,
     const bookmarks::BookmarkNode* parent,
-    size_t index) {
+    size_t index,
+    bool added_by_user) {
   const bookmarks::BookmarkNode* node = parent->children()[index].get();
   if (!model->client()->CanSyncNode(node)) {
     return;
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl.h b/components/sync_bookmarks/bookmark_model_observer_impl.h
index 52d7bf0..9af25de0 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl.h
+++ b/components/sync_bookmarks/bookmark_model_observer_impl.h
@@ -53,7 +53,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model,
                              const bookmarks::BookmarkNode* parent,
                              size_t old_index,
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc b/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
index 190a5440..ad0330f 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
+++ b/components/sync_bookmarks/bookmark_model_observer_impl_unittest.cc
@@ -725,11 +725,11 @@
 
   // Now simulate calling the observer as if the nodes are added in that order.
   // 4,0,2,3,1.
-  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 4);
-  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 0);
-  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 2);
-  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 3);
-  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 1);
+  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 4, false);
+  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 0, false);
+  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 2, false);
+  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 3, false);
+  observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 1, false);
 
   ASSERT_THAT(bookmark_tracker->TrackedEntitiesCountForTest(), 6U);
 
diff --git a/components/translate/core/browser/translate_pref_names.cc b/components/translate/core/browser/translate_pref_names.cc
index ae72e6c..74228fb7 100644
--- a/components/translate/core/browser/translate_pref_names.cc
+++ b/components/translate/core/browser/translate_pref_names.cc
@@ -22,5 +22,9 @@
 // Languages that the user marked as "do not translate".
 const char kBlockedLanguages[] = "translate_blocked_languages";
 
+// Sites that never prompt to translate.
+const char kPrefNeverPromptSitesWithTime[] =
+    "translate_site_blocklist_with_time";
+
 }  // namespace prefs
 }  // namespace translate
diff --git a/components/translate/core/browser/translate_pref_names.h b/components/translate/core/browser/translate_pref_names.h
index 65ac5232..ee02655 100644
--- a/components/translate/core/browser/translate_pref_names.h
+++ b/components/translate/core/browser/translate_pref_names.h
@@ -12,6 +12,7 @@
 extern const char kPrefAlwaysTranslateList[];
 extern const char kPrefTranslateRecentTarget[];
 extern const char kBlockedLanguages[];
+extern const char kPrefNeverPromptSitesWithTime[];
 
 }  // namespace prefs
 }  // namespace translate
diff --git a/components/translate/core/browser/translate_prefs.cc b/components/translate/core/browser/translate_prefs.cc
index 13446c50..f37ee92 100644
--- a/components/translate/core/browser/translate_prefs.cc
+++ b/components/translate/core/browser/translate_prefs.cc
@@ -144,8 +144,6 @@
     "translate_force_trigger_on_english_count_for_backoff_1";
 const char TranslatePrefs::kPrefNeverPromptSitesDeprecated[] =
     "translate_site_blacklist";
-const char TranslatePrefs::kPrefNeverPromptSitesWithTime[] =
-    "translate_site_blacklist_with_time";
 const char TranslatePrefs::kPrefTranslateDeniedCount[] =
     "translate_denied_count_for_language";
 const char TranslatePrefs::kPrefTranslateIgnoredCount[] =
@@ -611,20 +609,20 @@
 }
 
 bool TranslatePrefs::IsSiteOnNeverPromptList(base::StringPiece site) const {
-  return prefs_->GetValueDict(kPrefNeverPromptSitesWithTime).Find(site);
+  return prefs_->GetValueDict(prefs::kPrefNeverPromptSitesWithTime).Find(site);
 }
 
 void TranslatePrefs::AddSiteToNeverPromptList(base::StringPiece site) {
   DCHECK(!site.empty());
   AddValueToNeverPromptList(kPrefNeverPromptSitesDeprecated, site);
-  DictionaryPrefUpdate update(prefs_, kPrefNeverPromptSitesWithTime);
+  DictionaryPrefUpdate update(prefs_, prefs::kPrefNeverPromptSitesWithTime);
   update->GetDict().Set(site, base::TimeToValue(base::Time::Now()));
 }
 
 void TranslatePrefs::RemoveSiteFromNeverPromptList(base::StringPiece site) {
   DCHECK(!site.empty());
   RemoveValueFromNeverPromptList(kPrefNeverPromptSitesDeprecated, site);
-  DictionaryPrefUpdate update(prefs_, kPrefNeverPromptSitesWithTime);
+  DictionaryPrefUpdate update(prefs_, prefs::kPrefNeverPromptSitesWithTime);
   update->GetDict().Remove(site);
 }
 
@@ -632,13 +630,13 @@
     base::Time begin,
     base::Time end) const {
   std::vector<std::string> result;
-  const auto& dict = prefs_->GetValueDict(kPrefNeverPromptSitesWithTime);
+  const auto& dict = prefs_->GetValueDict(prefs::kPrefNeverPromptSitesWithTime);
   for (const auto entry : dict) {
     absl::optional<base::Time> time = base::ValueToTime(entry.second);
     if (!time) {
       // Badly formatted preferences may be synced from the server, see
       // https://crbug.com/1295549
-      LOG(ERROR) << "Preference " << kPrefNeverPromptSitesWithTime
+      LOG(ERROR) << "Preference " << prefs::kPrefNeverPromptSitesWithTime
                  << " has invalid format. Ignoring.";
       continue;
     }
@@ -723,7 +721,7 @@
 
 void TranslatePrefs::ClearNeverPromptSiteList() {
   prefs_->ClearPref(kPrefNeverPromptSitesDeprecated);
-  prefs_->ClearPref(kPrefNeverPromptSitesWithTime);
+  prefs_->ClearPref(prefs::kPrefNeverPromptSitesWithTime);
 }
 
 bool TranslatePrefs::HasLanguagePairsToAlwaysTranslate() const {
@@ -939,7 +937,7 @@
   registry->RegisterListPref(kPrefNeverPromptSitesDeprecated,
                              user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   registry->RegisterDictionaryPref(
-      kPrefNeverPromptSitesWithTime,
+      prefs::kPrefNeverPromptSitesWithTime,
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   registry->RegisterDictionaryPref(
       prefs::kPrefAlwaysTranslateList,
@@ -990,8 +988,8 @@
   // Migration copies any sites on the deprecated never prompt pref to
   // the new version and clears all references to the old one. This will
   // make subsequent calls to migrate no-ops.
-  DictionaryPrefUpdate never_prompt_list_update(prefs_,
-                                                kPrefNeverPromptSitesWithTime);
+  DictionaryPrefUpdate never_prompt_list_update(
+      prefs_, prefs::kPrefNeverPromptSitesWithTime);
   base::Value::Dict& never_prompt_list = never_prompt_list_update->GetDict();
   ListPrefUpdate deprecated_prompt_list_update(prefs_,
                                                kPrefNeverPromptSitesDeprecated);
diff --git a/components/translate/core/browser/translate_prefs.h b/components/translate/core/browser/translate_prefs.h
index c19a471..3120555e 100644
--- a/components/translate/core/browser/translate_prefs.h
+++ b/components/translate/core/browser/translate_prefs.h
@@ -111,7 +111,6 @@
   // TODO(crbug.com/524927): Remove kPrefNeverPromptSites after
   // 3 milestones (M74).
   static const char kPrefNeverPromptSitesDeprecated[];
-  static const char kPrefNeverPromptSitesWithTime[];
   static const char kPrefTranslateDeniedCount[];
   static const char kPrefTranslateIgnoredCount[];
   static const char kPrefTranslateAcceptedCount[];
diff --git a/components/translate/core/browser/translate_prefs_unittest.cc b/components/translate/core/browser/translate_prefs_unittest.cc
index b044fb44..51d4796 100644
--- a/components/translate/core/browser/translate_prefs_unittest.cc
+++ b/components/translate/core/browser/translate_prefs_unittest.cc
@@ -943,7 +943,7 @@
             2u);
   // Also put one of those sites on the new pref but migrated incorrectly.
   DictionaryPrefUpdate never_prompt_list_update(
-      &prefs_, TranslatePrefs::kPrefNeverPromptSitesWithTime);
+      &prefs_, prefs::kPrefNeverPromptSitesWithTime);
   base::Value::Dict& never_prompt_list = never_prompt_list_update->GetDict();
   never_prompt_list.Set("migratedWrong.com", 0);
 
@@ -961,7 +961,7 @@
 TEST_F(TranslatePrefsTest, InvalidNeverPromptSites) {
   // Add sites with invalid times.
   DictionaryPrefUpdate never_prompt_list_update(
-      &prefs_, TranslatePrefs::kPrefNeverPromptSitesWithTime);
+      &prefs_, prefs::kPrefNeverPromptSitesWithTime);
   base::Value::Dict& never_prompt_list = never_prompt_list_update->GetDict();
   never_prompt_list.Set("not-a-string.com", 0);
   never_prompt_list.Set("not-a-valid-time.com", "foo");
diff --git a/components/translate/translate_internals/translate_internals_handler.cc b/components/translate/translate_internals/translate_internals_handler.cc
index b5bca335..2d88f76 100644
--- a/components/translate/translate_internals/translate_internals_handler.cc
+++ b/components/translate/translate_internals/translate_internals_handler.cc
@@ -241,7 +241,7 @@
       prefs::kPrefAlwaysTranslateList,
       prefs::kPrefTranslateRecentTarget,
       translate::TranslatePrefs::kPrefNeverPromptSitesDeprecated,
-      translate::TranslatePrefs::kPrefNeverPromptSitesWithTime,
+      prefs::kPrefNeverPromptSitesWithTime,
       translate::TranslatePrefs::kPrefTranslateDeniedCount,
       translate::TranslatePrefs::kPrefTranslateIgnoredCount,
       translate::TranslatePrefs::kPrefTranslateAcceptedCount,
diff --git a/components/undo/bookmark_undo_service.cc b/components/undo/bookmark_undo_service.cc
index 6fa3a3b..a4ccc71 100644
--- a/components/undo/bookmark_undo_service.cc
+++ b/components/undo/bookmark_undo_service.cc
@@ -378,7 +378,8 @@
 
 void BookmarkUndoService::BookmarkNodeAdded(BookmarkModel* model,
                                             const BookmarkNode* parent,
-                                            size_t index) {
+                                            size_t index,
+                                            bool added_by_user) {
   std::unique_ptr<UndoOperation> op(
       new BookmarkAddOperation(model, parent, index));
   undo_manager()->AddUndoOperation(std::move(op));
diff --git a/components/undo/bookmark_undo_service.h b/components/undo/bookmark_undo_service.h
index bdb9687..a3d6d7c 100644
--- a/components/undo/bookmark_undo_service.h
+++ b/components/undo/bookmark_undo_service.h
@@ -54,7 +54,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void OnWillChangeBookmarkNode(bookmarks::BookmarkModel* model,
                                 const bookmarks::BookmarkNode* node) override;
   void OnWillReorderBookmarkNode(bookmarks::BookmarkModel* model,
diff --git a/components/user_education/webui/help_bubble_handler.cc b/components/user_education/webui/help_bubble_handler.cc
index 0b03dae2..0510828 100644
--- a/components/user_education/webui/help_bubble_handler.cc
+++ b/components/user_education/webui/help_bubble_handler.cc
@@ -29,24 +29,41 @@
 
 // Converts help bubble arrow to WebUI bubble position. This is not a complete
 // mapping as many HelpBubbleArrow options are not (yet) supported in WebUI.
-help_bubble::mojom::HelpBubblePosition HelpBubbleArrowToPosition(
+help_bubble::mojom::HelpBubbleArrowPosition HelpBubbleArrowToPosition(
     HelpBubbleArrow arrow) {
   switch (arrow) {
     case HelpBubbleArrow::kBottomLeft:
+      return help_bubble::mojom::HelpBubbleArrowPosition::BOTTOM_LEFT;
     case HelpBubbleArrow::kBottomCenter:
+      return help_bubble::mojom::HelpBubbleArrowPosition::BOTTOM_CENTER;
     case HelpBubbleArrow::kBottomRight:
-      return help_bubble::mojom::HelpBubblePosition::ABOVE;
+      return help_bubble::mojom::HelpBubbleArrowPosition::BOTTOM_RIGHT;
+
+    case HelpBubbleArrow::kTopLeft:
+      return help_bubble::mojom::HelpBubbleArrowPosition::TOP_LEFT;
+    case HelpBubbleArrow::kTopCenter:
+      return help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
+    case HelpBubbleArrow::kTopRight:
+      return help_bubble::mojom::HelpBubbleArrowPosition::TOP_RIGHT;
+
     case HelpBubbleArrow::kLeftTop:
+      return help_bubble::mojom::HelpBubbleArrowPosition::LEFT_TOP;
     case HelpBubbleArrow::kLeftCenter:
+      return help_bubble::mojom::HelpBubbleArrowPosition::LEFT_CENTER;
     case HelpBubbleArrow::kLeftBottom:
-      return help_bubble::mojom::HelpBubblePosition::RIGHT;
+      return help_bubble::mojom::HelpBubbleArrowPosition::LEFT_BOTTOM;
+
     case HelpBubbleArrow::kRightTop:
+      return help_bubble::mojom::HelpBubbleArrowPosition::RIGHT_TOP;
     case HelpBubbleArrow::kRightCenter:
+      return help_bubble::mojom::HelpBubbleArrowPosition::RIGHT_CENTER;
     case HelpBubbleArrow::kRightBottom:
-      return help_bubble::mojom::HelpBubblePosition::LEFT;
+      return help_bubble::mojom::HelpBubbleArrowPosition::RIGHT_BOTTOM;
+
     default:
-      return help_bubble::mojom::HelpBubblePosition::BELOW;
+      NOTIMPLEMENTED();
   }
+  return help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
 }
 
 }  // namespace
diff --git a/components/user_education/webui/help_bubble_handler_unittest.cc b/components/user_education/webui/help_bubble_handler_unittest.cc
index f3c6a98..0eb0ea6 100644
--- a/components/user_education/webui/help_bubble_handler_unittest.cc
+++ b/components/user_education/webui/help_bubble_handler_unittest.cc
@@ -224,7 +224,7 @@
   expected->body_text = base::UTF16ToUTF8(params.body_text);
   expected->close_button_alt_text =
       base::UTF16ToUTF8(params.close_button_alt_text);
-  expected->position = help_bubble::mojom::HelpBubblePosition::BELOW;
+  expected->position = help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
 
   EXPECT_CALL(test_handler_->mock(),
               ShowHelpBubble(MatchesHelpBubbleParams(expected.get())));
@@ -269,7 +269,7 @@
   expected->body_text = base::UTF16ToUTF8(params.body_text);
   expected->close_button_alt_text =
       base::UTF16ToUTF8(params.close_button_alt_text);
-  expected->position = help_bubble::mojom::HelpBubblePosition::BELOW;
+  expected->position = help_bubble::mojom::HelpBubbleArrowPosition::TOP_CENTER;
 
   auto expected_button = help_bubble::mojom::HelpBubbleButtonParams::New();
   expected_button->text = "button1";
diff --git a/components/viz/common/DEPS b/components/viz/common/DEPS
index adbe40b2..1211a19 100644
--- a/components/viz/common/DEPS
+++ b/components/viz/common/DEPS
@@ -6,6 +6,7 @@
   "-mojo/public/cpp/bindings",
   # Exception is struct_traits.h which is used for defining friends only.
   "+cc/base",
+  "+cc/paint/filter_operations.h",
   "+mojo/public/cpp/bindings/struct_traits.h",
   "+third_party/perfetto/protos/perfetto/trace/track_event",
   "+third_party/skia",
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc
index 6da7dc0..c933c89 100644
--- a/components/viz/common/viz_utils.cc
+++ b/components/viz/common/viz_utils.cc
@@ -163,4 +163,18 @@
   return gfx::ToEnclosingRect(ClippedQuadRectangleF(quad));
 }
 
+gfx::Rect GetExpandedRectWithPixelMovingForegroundFilter(
+    const DrawQuad& rpdq,
+    const cc::FilterOperations& filters) {
+  const SharedQuadState* shared_quad_state = rpdq.shared_quad_state;
+  float max_pixel_movement = filters.MaximumPixelMovement();
+  gfx::RectF rect(rpdq.rect);
+  rect.Inset(-max_pixel_movement);
+  gfx::Rect expanded_rect = gfx::ToEnclosingRect(rect);
+
+  // expanded_rect in the target space
+  return cc::MathUtil::MapEnclosingClippedRect(
+      shared_quad_state->quad_to_target_transform, expanded_rect);
+}
+
 }  // namespace viz
diff --git a/components/viz/common/viz_utils.h b/components/viz/common/viz_utils.h
index d07ab405..c1821c5 100644
--- a/components/viz/common/viz_utils.h
+++ b/components/viz/common/viz_utils.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
 
 #include "base/timer/elapsed_timer.h"
+#include "cc/paint/filter_operations.h"
 #include "components/viz/common/quads/draw_quad.h"
 #include "components/viz/common/viz_common_export.h"
 
@@ -52,6 +53,12 @@
 // Returns the smallest rectangle in target space that contains the quad.
 VIZ_COMMON_EXPORT gfx::Rect ClippedQuadRectangle(const DrawQuad* quad);
 VIZ_COMMON_EXPORT gfx::RectF ClippedQuadRectangleF(const DrawQuad* quad);
+
+// The expanded area that will be changed by a render pass draw quad with a
+// pixel-moving foreground filter.
+VIZ_COMMON_EXPORT gfx::Rect GetExpandedRectWithPixelMovingForegroundFilter(
+    const DrawQuad& rpdq,
+    const cc::FilterOperations& filters);
 }  // namespace viz
 
 #endif  // COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index f86e099..647174a 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -27,6 +27,7 @@
 #include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
 #include "components/viz/common/quads/draw_quad.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/service/display/bsp_tree.h"
 #include "components/viz/service/display/bsp_walk_action.h"
 #include "components/viz/service/display/output_surface.h"
@@ -249,8 +250,11 @@
   // RenderPass owns filters, backdrop_filters, etc., and will outlive this
   // function call. So it is safe to store pointers in these maps.
   for (const auto& pass : *render_passes_in_draw_order) {
-    if (!pass->filters.IsEmpty())
+    if (!pass->filters.IsEmpty()) {
       render_pass_filters_[pass->id] = &pass->filters;
+      if (pass->filters.HasFilterThatMovesPixels())
+        has_pixel_moving_foreground_filters_ = true;
+    }
     if (!pass->backdrop_filters.IsEmpty()) {
       render_pass_backdrop_filters_[pass->id] = &pass->backdrop_filters;
       render_pass_backdrop_filter_bounds_[pass->id] =
@@ -406,6 +410,7 @@
   render_pass_backdrop_filter_bounds_.clear();
   render_pass_bypass_quads_.clear();
   backdrop_filter_output_rects_.clear();
+  has_pixel_moving_foreground_filters_ = false;
 
   current_frame_valid_ = false;
 }
@@ -792,27 +797,43 @@
       root_damage_rect.Union(frame_buffer_damage);
 
       // If the root damage rect intersects any child render pass that has a
-      // pixel-moving backdrop-filter, expand the damage to include the entire
+      // pixel-moving backdrop filter, expand the damage to include the entire
       // child pass. See crbug.com/986206 for context.
-      if (!backdrop_filter_output_rects_.empty() &&
+      if ((!backdrop_filter_output_rects_.empty() ||
+           has_pixel_moving_foreground_filters_) &&
           !root_damage_rect.IsEmpty()) {
-        for (auto* quad : render_pass->quad_list) {
+        for (auto* quad : root_render_pass->quad_list) {
           // Sanity check: we should not have a Compositor
           // CompositorRenderPassDrawQuad here.
           DCHECK_NE(quad->material, DrawQuad::Material::kCompositorRenderPass);
           if (quad->material == DrawQuad::Material::kAggregatedRenderPass) {
-            auto iter = backdrop_filter_output_rects_.find(
-                AggregatedRenderPassDrawQuad::MaterialCast(quad)
-                    ->render_pass_id);
-            if (iter != backdrop_filter_output_rects_.end()) {
-              gfx::Rect this_output_rect = iter->second;
-              if (root_damage_rect.Intersects(this_output_rect))
-                root_damage_rect.Union(this_output_rect);
+            const auto* rpdq = AggregatedRenderPassDrawQuad::MaterialCast(quad);
+
+            // For render pass with pixel moving backdrop filters.
+            if (!backdrop_filter_output_rects_.empty()) {
+              auto iter =
+                  backdrop_filter_output_rects_.find(rpdq->render_pass_id);
+              if (iter != backdrop_filter_output_rects_.end()) {
+                gfx::Rect this_output_rect = iter->second;
+                if (root_damage_rect.Intersects(this_output_rect))
+                  root_damage_rect.Union(this_output_rect);
+              }
+            }
+
+            // For render pass with pixel moving foreground filters.
+            const cc::FilterOperations* foreground_filters =
+                FiltersForPass(rpdq->render_pass_id);
+            if (foreground_filters &&
+                foreground_filters->HasFilterThatMovesPixels()) {
+              gfx::Rect expanded_rect =
+                  GetExpandedRectWithPixelMovingForegroundFilter(
+                      *rpdq, *foreground_filters);
+              if (root_damage_rect.Intersects(expanded_rect))
+                root_damage_rect.Union(expanded_rect);
             }
           }
         }
       }
-
       // Total damage after all adjustments.
       base::CheckedNumeric<int64_t> total_damage_area =
           root_damage_rect.size().GetCheckedArea();
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index 88512df..c97bb8b 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -331,6 +331,10 @@
   base::flat_map<AggregatedRenderPassId, gfx::Rect>
       backdrop_filter_output_rects_;
 
+  // Whether a render pass with foreground filters that move pixels is found in
+  // this frame.
+  bool has_pixel_moving_foreground_filters_ = false;
+
   bool visible_ = false;
   bool disable_color_checks_for_testing_ = false;
 
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc
index 22e0c7a9..896a3636 100644
--- a/components/viz/service/display/display_unittest.cc
+++ b/components/viz/service/display/display_unittest.cc
@@ -59,6 +59,7 @@
 #include "components/viz/test/fake_skia_output_surface.h"
 #include "components/viz/test/mock_compositor_frame_sink_client.h"
 #include "components/viz/test/test_gles2_interface.h"
+#include "components/viz/test/test_surface_id_allocator.h"
 #include "components/viz/test/viz_test_suite.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -4332,6 +4333,121 @@
   }
 }
 
+TEST_F(DisplayTest, PixelMovingForegroundFilterTest) {
+  RendererSettings settings;
+  settings.partial_swap_enabled = true;
+  id_allocator_.GenerateId();
+  const LocalSurfaceId local_surface_id(
+      id_allocator_.GetCurrentLocalSurfaceId());
+
+  // Set up first display.
+  SetUpSoftwareDisplay(settings);
+  StubDisplayClient client;
+  display_->Initialize(&client, manager_.surface_manager());
+  display_->SetLocalSurfaceId(local_surface_id, 1.f);
+
+  // Create frame sink for a sub surface.
+  TestSurfaceIdAllocator sub_surface_id1(kAnotherFrameSinkId);
+  auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
+      nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);
+
+  // Create frame sink for another sub surface.
+  TestSurfaceIdAllocator sub_surface_id2(kAnotherFrameSinkId2);
+  auto sub_support2 = std::make_unique<CompositorFrameSinkSupport>(
+      nullptr, &manager_, kAnotherFrameSinkId2, /*is_root=*/false);
+
+  // Main surface M, damage D, sub-surface B with foreground filter.
+  //   +-----------+
+  //   | +----+   M|
+  //   | |B +-|-+  |
+  //   | +--|-+ |  |
+  //   |    |  D|  |
+  //   |    +---+  |
+  //   +-----------+
+  const gfx::Size display_size(100, 100);
+  const gfx::Rect damage_rect(20, 20, 40, 40);
+  display_->Resize(display_size);
+  const gfx::Rect sub_surface_rect(5, 5, 25, 25);
+  const gfx::Rect no_damage;
+
+  CompositorRenderPassId::Generator render_pass_id_generator;
+  for (size_t frame_num = 1; frame_num <= 2; ++frame_num) {
+    bool first_frame = frame_num == 1;
+    ResetDamageForTest();
+    {
+      // Sub-surface with pixel-moving foreground filter - drop shadow filter
+      CompositorRenderPassList pass_list;
+      auto bd_pass = CompositorRenderPass::Create();
+      cc::FilterOperations foreground_filters;
+      foreground_filters.Append(cc::FilterOperation::CreateDropShadowFilter(
+          gfx::Point(5, 10), 2.f, SkColors::kTransparent));
+      bd_pass->SetAll(
+          render_pass_id_generator.GenerateNextId(), sub_surface_rect,
+          no_damage, gfx::Transform(), foreground_filters,
+          cc::FilterOperations(), gfx::RRectF(gfx::RectF(sub_surface_rect), 0),
+          SubtreeCaptureId(), sub_surface_rect.size(),
+          SharedElementResourceId(), false, false, false, false, false);
+      pass_list.push_back(std::move(bd_pass));
+
+      CompositorFrame frame = CompositorFrameBuilder()
+                                  .SetRenderPassList(std::move(pass_list))
+                                  .Build();
+      sub_support1->SubmitCompositorFrame(sub_surface_id1.local_surface_id(),
+                                          std::move(frame));
+    }
+
+    {
+      // Sub-surface with damage.
+      CompositorRenderPassList pass_list;
+      auto other_pass = CompositorRenderPass::Create();
+      other_pass->output_rect = gfx::Rect(display_size);
+      other_pass->damage_rect = damage_rect;
+      other_pass->id = render_pass_id_generator.GenerateNextId();
+      pass_list.push_back(std::move(other_pass));
+
+      CompositorFrame frame = CompositorFrameBuilder()
+                                  .SetRenderPassList(std::move(pass_list))
+                                  .Build();
+      sub_support2->SubmitCompositorFrame(sub_surface_id2.local_surface_id(),
+                                          std::move(frame));
+    }
+
+    {
+      auto frame = CompositorFrameBuilder()
+                       .AddRenderPass(
+                           RenderPassBuilder(display_size)
+                               .AddSurfaceQuad(
+                                   sub_surface_rect,
+                                   SurfaceRange(absl::nullopt, sub_surface_id1),
+                                   {.allow_merge = false})
+                               .AddSurfaceQuad(
+                                   gfx::Rect(display_size),
+                                   SurfaceRange(absl::nullopt, sub_surface_id2),
+                                   {.allow_merge = false})
+                               .SetDamageRect(damage_rect))
+                       .Build();
+      support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
+
+      scheduler_->reset_swapped_for_test();
+      display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
+      EXPECT_TRUE(scheduler_->swapped());
+      EXPECT_EQ(frame_num, output_surface_->num_sent_frames());
+      EXPECT_EQ(display_size, software_output_device_->viewport_pixel_size());
+
+      auto expected_damage =
+          first_frame ? gfx::Rect(display_size) : damage_rect;
+      EXPECT_EQ(expected_damage, software_output_device_->damage_rect());
+      // The scissor rect is expanded by direct_renderer to include the
+      // overlapping pixel-moving foreground filter surface.
+      auto expected_scissor_rect =
+          first_frame ? gfx::Rect(display_size) : gfx::Rect(0, 0, 60, 60);
+      EXPECT_EQ(
+          expected_scissor_rect,
+          display_->renderer_for_testing()->GetLastRootScissorRectForTesting());
+    }
+  }
+}
+
 class SkiaDelegatedInkRendererTest : public DisplayTest {
  public:
   void SetUp() override { EnablePrediction(); }
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 764edcb..c62b4f7e 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -128,20 +128,6 @@
   return true;
 }
 
-gfx::Rect GetExpandedRectWithPixelMovingForegroundFilter(
-    const CompositorRenderPassDrawQuad* rpdq,
-    const CompositorRenderPass& child_render_pass) {
-  const SharedQuadState* shared_quad_state = rpdq->shared_quad_state;
-  float max_pixel_movement = child_render_pass.filters.MaximumPixelMovement();
-  gfx::RectF rect(rpdq->rect);
-  rect.Inset(-max_pixel_movement);
-  gfx::Rect expanded_rect = gfx::ToEnclosingRect(rect);
-
-  // expanded_rect in the target space
-  return cc::MathUtil::MapEnclosingClippedRect(
-      shared_quad_state->quad_to_target_transform, expanded_rect);
-}
-
 // Create a clip rect for an aggregated quad from the original clip rect and
 // the clip rect from the surface it's on.
 absl::optional<gfx::Rect> CalculateClipRect(
@@ -347,8 +333,8 @@
     // The size of pixel-moving foreground filter is allowed to expand.
     // No intersecting shared_quad_state->clip_rect for the expanded rect.
     damage_rect_in_target_space =
-        GetExpandedRectWithPixelMovingForegroundFilter(render_pass_quad,
-                                                       child_render_pass);
+        GetExpandedRectWithPixelMovingForegroundFilter(
+            *render_pass_quad, child_render_pass.filters);
   } else if (child_render_pass.backdrop_filters.HasFilterThatMovesPixels()) {
     const auto* shared_quad_state = render_pass_quad->shared_quad_state;
     damage_rect_in_target_space = cc::MathUtil::MapEnclosingClippedRect(
@@ -1686,8 +1672,8 @@
       // has pixel-moving foreground filter.
       if (child_render_pass.filters.HasFilterThatMovesPixels()) {
         gfx::Rect expanded_rect_in_target_space =
-            GetExpandedRectWithPixelMovingForegroundFilter(render_pass_quad,
-                                                           child_render_pass);
+            GetExpandedRectWithPixelMovingForegroundFilter(
+                *render_pass_quad, child_render_pass.filters);
 
         if (expanded_rect_in_target_space.Intersects(damage_rect) ||
             expanded_rect_in_target_space.Intersects(damage_from_parent) ||
diff --git a/content/BUILD.gn b/content/BUILD.gn
index dc8fafb..a098bc6 100644
--- a/content/BUILD.gn
+++ b/content/BUILD.gn
@@ -114,8 +114,10 @@
     "dev_ui_content_resources.pak",
   ]
   deps = [
+    "//content/browser/aggregation_service:mojo_bindings_webui_js",
     "//content/browser/attribution_reporting:mojo_bindings_webui_js",
     "//content/browser/preloading/prerender:mojo_bindings_webui_js",
+    "//content/browser/resources/aggregation_service:build_ts",
     "//content/browser/resources/attribution_reporting:build_ts",
     "//content/browser/resources/gpu:html_wrapper_files",
     "//content/browser/resources/process:build_ts",
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 1a71bab..993ec8ac 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -391,6 +391,8 @@
     "aggregation_service/aggregation_service_impl.h",
     "aggregation_service/aggregation_service_internals_handler_impl.cc",
     "aggregation_service/aggregation_service_internals_handler_impl.h",
+    "aggregation_service/aggregation_service_internals_ui.cc",
+    "aggregation_service/aggregation_service_internals_ui.h",
     "aggregation_service/aggregation_service_key_fetcher.cc",
     "aggregation_service/aggregation_service_key_fetcher.h",
     "aggregation_service/aggregation_service_network_fetcher_impl.cc",
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 2e692f4b..b1c4ad4 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -1675,7 +1675,7 @@
 }
 
 ui::AXNode* BrowserAccessibilityManager::GetNodeFromTree(
-    const ui::AXTreeID tree_id,
+    const ui::AXTreeID& tree_id,
     const ui::AXNodeID node_id) const {
   auto* manager = BrowserAccessibilityManager::FromID(tree_id);
   CHECK(manager);
diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h
index 4fea19ac..73f2bf1 100644
--- a/content/browser/accessibility/browser_accessibility_manager.h
+++ b/content/browser/accessibility/browser_accessibility_manager.h
@@ -483,7 +483,7 @@
       const std::vector<ui::AXTreeObserver::Change>& changes) override;
 
   // AXTreeManager overrides.
-  ui::AXNode* GetNodeFromTree(ui::AXTreeID tree_id,
+  ui::AXNode* GetNodeFromTree(const ui::AXTreeID& tree_id,
                               ui::AXNodeID node_id) const override;
   ui::AXNode* GetNodeFromTree(ui::AXNodeID node_id) const override;
   ui::AXPlatformNode* GetPlatformNodeFromTree(
diff --git a/content/browser/aggregation_service/aggregation_service_impl.cc b/content/browser/aggregation_service/aggregation_service_impl.cc
index ae68d2f..19d5f779 100644
--- a/content/browser/aggregation_service/aggregation_service_impl.cc
+++ b/content/browser/aggregation_service/aggregation_service_impl.cc
@@ -188,6 +188,9 @@
     return;
   }
 
+  // TODO(crbug.com/1354220): Consider checking with the browser client if
+  // reporting is allowed before sending. We don't currently have the top-frame
+  // origin to perform this check.
   base::Value value(report->GetAsJson());
   SendReport(reporting_url, value,
              /*callback=*/
diff --git a/content/browser/aggregation_service/aggregation_service_internals_browsertest.cc b/content/browser/aggregation_service/aggregation_service_internals_browsertest.cc
new file mode 100644
index 0000000..2764050
--- /dev/null
+++ b/content/browser/aggregation_service/aggregation_service_internals_browsertest.cc
@@ -0,0 +1,390 @@
+// 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/browser/aggregation_service/aggregation_service_internals_ui.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/time/time.h"
+#include "content/browser/aggregation_service/aggregation_service.h"
+#include "content/browser/aggregation_service/aggregation_service_observer.h"
+#include "content/browser/aggregation_service/aggregation_service_storage.h"
+#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+using GetPendingReportsCallback = base::OnceCallback<void(
+    std::vector<AggregationServiceStorage::RequestAndId>)>;
+
+constexpr char kPrivateAggregationInternalsUrl[] =
+    "chrome://private-aggregation-internals/";
+
+const std::u16string kCompleteTitle = u"Complete";
+
+class AggregationServiceInternalsWebUiBrowserTest : public ContentBrowserTest {
+ public:
+  AggregationServiceInternalsWebUiBrowserTest() = default;
+
+  void SetUpOnMainThread() override {
+    ContentBrowserTest::SetUpOnMainThread();
+
+    auto aggregation_service = std::make_unique<MockAggregationService>();
+    aggregation_service_ = aggregation_service.get();
+
+    ON_CALL(*aggregation_service_, GetPendingReportRequestsForWebUI)
+        .WillByDefault([](GetPendingReportsCallback callback) {
+          std::move(callback).Run(
+              AggregatableReportRequestsAndIdsBuilder().Build());
+        });
+
+    static_cast<StoragePartitionImpl*>(shell()
+                                           ->web_contents()
+                                           ->GetBrowserContext()
+                                           ->GetDefaultStoragePartition())
+        ->OverrideAggregationServiceForTesting(std::move(aggregation_service));
+  }
+
+  // Executing javascript in the WebUI requires using an isolated world in which
+  // to execute the script because WebUI has a default CSP policy denying
+  // "eval()", which is what EvalJs uses under the hood.
+  bool ExecJsInWebUI(const std::string& script) {
+    return ExecJs(shell()->web_contents()->GetPrimaryMainFrame(), script,
+                  EXECUTE_SCRIPT_DEFAULT_OPTIONS, /*world_id=*/1);
+  }
+
+  // Registers a mutation observer that sets the window title to `title` when
+  // the report table is empty.
+  void SetTitleOnReportsTableEmpty(const std::u16string& title) {
+    static constexpr char kObserveEmptyReportsTableScript[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 1 &&
+            table.children[0].children[0].textContent === 'No sent or pending reports.') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+    EXPECT_TRUE(
+        ExecJsInWebUI(JsReplace(kObserveEmptyReportsTableScript, title)));
+  }
+
+  void ClickRefreshButton() {
+    EXPECT_TRUE(ExecJsInWebUI("document.getElementById('refresh').click();"));
+  }
+
+ protected:
+  raw_ptr<MockAggregationService> aggregation_service_;
+};
+
+IN_PROC_BROWSER_TEST_F(AggregationServiceInternalsWebUiBrowserTest,
+                       NavigationUrl_ResolvedToWebUI) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kPrivateAggregationInternalsUrl)));
+
+  EXPECT_EQ(true, EvalJs(shell()->web_contents()->GetPrimaryMainFrame(),
+                         "document.body.innerHTML.includes('Private "
+                         "Aggregation API Internals');",
+                         EXECUTE_SCRIPT_DEFAULT_OPTIONS, /*world_id=*/1));
+}
+
+IN_PROC_BROWSER_TEST_F(AggregationServiceInternalsWebUiBrowserTest,
+                       WebUIShownWithNoReports_NoReportsDisplayed) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kPrivateAggregationInternalsUrl)));
+
+  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+  SetTitleOnReportsTableEmpty(kCompleteTitle);
+  ClickRefreshButton();
+  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(AggregationServiceInternalsWebUiBrowserTest,
+                       WebUIShownWithReports_ReportsDisplayed) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kPrivateAggregationInternalsUrl)));
+
+  base::Time now = base::Time::Now();
+
+  ON_CALL(*aggregation_service_, GetPendingReportRequestsForWebUI)
+      .WillByDefault([now](GetPendingReportsCallback callback) {
+        std::move(callback).Run(
+            AggregatableReportRequestsAndIdsBuilder()
+                .AddRequestWithID(
+                    aggregation_service::CreateExampleRequestWithReportTime(
+                        now),
+                    AggregationServiceStorage::RequestId(20))
+                .Build());
+      });
+
+  aggregation_service::TestHpkeKey hpke_key =
+      aggregation_service::GenerateKey("id123");
+
+  AggregatableReportRequest request_1 =
+      aggregation_service::CreateExampleRequest();
+  absl::optional<AggregatableReport> report_1 =
+      AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
+          request_1, {hpke_key.public_key});
+
+  aggregation_service_->NotifyReportHandled(
+      AggregationServiceStorage::RequestAndId{
+          .request = std::move(request_1),
+          .id = AggregationServiceStorage::RequestId(1)},
+      std::move(report_1), /*report_handled_time=*/now + base::Hours(1),
+      AggregationServiceObserver::ReportStatus::kSent);
+
+  AggregatableReportRequest request_2 =
+      aggregation_service::CreateExampleRequest();
+  aggregation_service_->NotifyReportHandled(
+      AggregationServiceStorage::RequestAndId{
+          .request = std::move(request_2),
+          .id = AggregationServiceStorage::RequestId(2)},
+      /*report=*/absl::nullopt,
+      /*report_handled_time=*/now + base::Hours(2),
+      AggregationServiceObserver::ReportStatus::kFailedToAssemble);
+
+  AggregatableReportRequest request_3 =
+      aggregation_service::CreateExampleRequest();
+  absl::optional<AggregatableReport> report_3 =
+      AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
+          request_3, {hpke_key.public_key});
+
+  aggregation_service_->NotifyReportHandled(
+      AggregationServiceStorage::RequestAndId{
+          .request = std::move(request_3),
+          .id = AggregationServiceStorage::RequestId(3)},
+      std::move(report_3),
+      /*report_handled_time=*/now + base::Hours(3),
+      AggregationServiceObserver::ReportStatus::kFailedToSend);
+
+  {
+    static constexpr char wait_script[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const cell = (a, b) => table.children[a]?.children[b]?.textContent;
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 4 &&
+            cell(0, 1) === 'Pending' &&
+            cell(0, 2) === 'https://reporting.example/example-path' &&
+            cell(0, 3) === (new Date($2)).toLocaleString() &&
+            cell(0, 4) === 'example-api' &&
+            cell(0, 5) === '' &&
+            cell(0, 6) === '[ {  "bucket": {   "high": "0",   "low": "123"  },  "value": 456 }]' &&
+            cell(1, 1) === 'Sent' &&
+            cell(2, 1) === 'Failed to assemble' &&
+            cell(3, 1) === 'Failed to send') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+    EXPECT_TRUE(ExecJsInWebUI(
+        JsReplace(wait_script, kCompleteTitle, (now).ToJsTime())));
+
+    TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+    ClickRefreshButton();
+    EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+  }
+
+  {
+    static constexpr char wait_script[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const cell = (a, b) => table.children[a]?.children[b]?.textContent;
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 4 &&
+            cell(0, 1) === 'Failed to assemble' &&
+            cell(1, 1) === 'Failed to send' &&
+            cell(2, 1) === 'Pending' &&
+            cell(3, 1) === 'Sent') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+
+    const std::u16string kCompleteTitle2 = u"Complete2";
+    EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle2)));
+
+    TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle2);
+    // Sort by status ascending.
+    EXPECT_TRUE(
+        ExecJsInWebUI("document.querySelector('#reportTable')"
+                      ".shadowRoot.querySelectorAll('th')[1].click();"));
+    EXPECT_EQ(kCompleteTitle2, title_watcher.WaitAndGetTitle());
+  }
+
+  {
+    static constexpr char wait_script[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const cell = (a, b) => table.children[a]?.children[b]?.textContent;
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 4 &&
+            cell(0, 1) === 'Sent' &&
+            cell(1, 1) === 'Pending' &&
+            cell(2, 1) === 'Failed to send' &&
+            cell(3, 1) === 'Failed to assemble') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+
+    const std::u16string kCompleteTitle3 = u"Complete3";
+    EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle3)));
+
+    TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle3);
+    // Sort by status descending.
+    EXPECT_TRUE(
+        ExecJsInWebUI("document.querySelector('#reportTable')"
+                      ".shadowRoot.querySelectorAll('th')[1].click();"));
+    EXPECT_EQ(kCompleteTitle3, title_watcher.WaitAndGetTitle());
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(AggregationServiceInternalsWebUiBrowserTest,
+                       WebUISendReports_ReportsRemoved) {
+  EXPECT_CALL(*aggregation_service_, GetPendingReportRequestsForWebUI)
+      .WillOnce([](GetPendingReportsCallback callback) {
+        std::move(callback).Run(
+            AggregatableReportRequestsAndIdsBuilder().Build());
+      })  // on page loaded
+      .WillOnce([](GetPendingReportsCallback callback) {
+        std::move(callback).Run(
+            AggregatableReportRequestsAndIdsBuilder()
+                .AddRequestWithID(aggregation_service::CreateExampleRequest(),
+                                  AggregationServiceStorage::RequestId(5))
+                .Build());
+      })  // on page refresh
+      .WillOnce([](GetPendingReportsCallback callback) {
+        std::move(callback).Run(
+            AggregatableReportRequestsAndIdsBuilder().Build());
+      });  // on page updated
+
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kPrivateAggregationInternalsUrl)));
+
+  EXPECT_CALL(*aggregation_service_,
+              SendReportsForWebUI(
+                  testing::ElementsAre(AggregationServiceStorage::RequestId(5)),
+                  testing::_))
+      .WillOnce(base::test::RunOnceCallback<1>());
+
+  static constexpr char wait_script[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 1 &&
+            table.children[0].children[1].textContent === 'Pending') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+  EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
+
+  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+  ClickRefreshButton();
+  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+
+  // Click the send reports button and expect that the report table is emptied.
+  const std::u16string kSentTitle = u"Sent";
+  TitleWatcher sent_title_watcher(shell()->web_contents(), kSentTitle);
+  SetTitleOnReportsTableEmpty(kSentTitle);
+
+  EXPECT_TRUE(ExecJsInWebUI(
+      R"(document.querySelector('#reportTable')
+         .shadowRoot.querySelector('input[type="checkbox"]').click();)"));
+  EXPECT_TRUE(
+      ExecJsInWebUI("document.getElementById('send-reports').click();"));
+
+  // The real aggregation service would do this itself, but the test aggregation
+  // service requires manual triggering.
+  aggregation_service_->NotifyRequestStorageModified();
+
+  EXPECT_EQ(kSentTitle, sent_title_watcher.WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(AggregationServiceInternalsWebUiBrowserTest,
+                       WebUIClearStorage_ReportsRemoved) {
+  EXPECT_TRUE(NavigateToURL(shell(), GURL(kPrivateAggregationInternalsUrl)));
+
+  ON_CALL(*aggregation_service_, GetPendingReportRequestsForWebUI)
+      .WillByDefault([](GetPendingReportsCallback callback) {
+        std::move(callback).Run(
+            AggregatableReportRequestsAndIdsBuilder()
+                .AddRequestWithID(aggregation_service::CreateExampleRequest(),
+                                  AggregationServiceStorage::RequestId(5))
+                .Build());
+      });
+
+  aggregation_service::TestHpkeKey hpke_key =
+      aggregation_service::GenerateKey("id123");
+  AggregatableReportRequest request =
+      aggregation_service::CreateExampleRequest();
+  absl::optional<AggregatableReport> report =
+      AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
+          request, {hpke_key.public_key});
+
+  aggregation_service_->NotifyReportHandled(
+      AggregationServiceStorage::RequestAndId{
+          .request = std::move(request),
+          .id = AggregationServiceStorage::RequestId(10)},
+      std::move(report),
+      /*report_handled_time=*/base::Time::Now() + base::Hours(1),
+      AggregationServiceObserver::ReportStatus::kSent);
+
+  EXPECT_CALL(*aggregation_service_, ClearData)
+      .WillOnce(base::test::RunOnceCallback<3>());
+
+  // Verify both rows get rendered.
+  static constexpr char wait_script[] = R"(
+      const table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
+      const obs = new MutationObserver((_, obs) => {
+        if (table.children.length === 2 &&
+            table.children[0].children[1].textContent === 'Pending' &&
+            table.children[1].children[1].textContent === 'Sent') {
+          obs.disconnect();
+          document.title = $1;
+        }
+      });
+      obs.observe(table, {'childList': true});)";
+  EXPECT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
+
+  // Wait for the table to be rendered.
+  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
+  ClickRefreshButton();
+  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
+
+  // Click the send reports button and expect that the report table is emptied.
+  const std::u16string kDeleteTitle = u"Delete";
+  TitleWatcher delete_title_watcher(shell()->web_contents(), kDeleteTitle);
+  SetTitleOnReportsTableEmpty(kDeleteTitle);
+
+  EXPECT_TRUE(ExecJsInWebUI("document.getElementById('clear-data').click();"));
+
+  EXPECT_EQ(kDeleteTitle, delete_title_watcher.WaitAndGetTitle());
+}
+
+}  // namespace
+
+}  // namespace content
diff --git a/content/browser/aggregation_service/aggregation_service_internals_ui.cc b/content/browser/aggregation_service/aggregation_service_internals_ui.cc
new file mode 100644
index 0000000..9ac842c0
--- /dev/null
+++ b/content/browser/aggregation_service/aggregation_service_internals_ui.cc
@@ -0,0 +1,66 @@
+// 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/browser/aggregation_service/aggregation_service_internals_ui.h"
+
+#include <memory>
+#include <utility>
+
+#include "content/browser/aggregation_service/aggregation_service_internals_handler_impl.h"
+#include "content/grit/dev_ui_content_resources.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/bindings_policy.h"
+#include "services/network/public/mojom/content_security_policy.mojom.h"
+
+namespace content {
+
+AggregationServiceInternalsUI::AggregationServiceInternalsUI(WebUI* web_ui)
+    : WebUIController(web_ui) {
+  // Initialize the UI with no bindings. Mojo bindings will be separately
+  // granted to frames within this WebContents.
+  web_ui->SetBindings(BINDINGS_POLICY_NONE);
+  WebUIDataSource* source = WebUIDataSource::CreateAndAdd(
+      web_ui->GetWebContents()->GetBrowserContext(),
+      kChromeUIPrivateAggregationInternalsHost);
+
+  source->AddResourcePath("aggregation_service_internals.mojom-webui.js",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_MOJOM_JS);
+  source->AddResourcePath("aggregation_service_internals.js",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_JS);
+  source->AddResourcePath("aggregation_service_internals_table.js",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_JS);
+  source->AddResourcePath("aggregation_service_internals_table.html.js",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_HTML_JS);
+  source->AddResourcePath("table_model.js",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_MODEL_JS);
+  source->AddResourcePath("aggregation_service_internals.css",
+                          IDR_AGGREGATION_SERVICE_INTERNALS_CSS);
+  source->SetDefaultResource(IDR_AGGREGATION_SERVICE_INTERNALS_HTML);
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::TrustedTypes,
+      "trusted-types static-types;");
+}
+
+WEB_UI_CONTROLLER_TYPE_IMPL(AggregationServiceInternalsUI)
+
+AggregationServiceInternalsUI::~AggregationServiceInternalsUI() = default;
+
+void AggregationServiceInternalsUI::WebUIRenderFrameCreated(
+    RenderFrameHost* rfh) {
+  // Enable the JavaScript Mojo bindings in the renderer process, so the JS
+  // code can call the Mojo APIs exposed by this WebUI.
+  rfh->EnableMojoJsBindings(nullptr);
+}
+
+void AggregationServiceInternalsUI::BindInterface(
+    mojo::PendingReceiver<aggregation_service_internals::mojom::Handler>
+        receiver) {
+  ui_handler_ = std::make_unique<AggregationServiceInternalsHandlerImpl>(
+      web_ui(), std::move(receiver));
+}
+
+}  // namespace content
diff --git a/content/browser/aggregation_service/aggregation_service_internals_ui.h b/content/browser/aggregation_service/aggregation_service_internals_ui.h
new file mode 100644
index 0000000..539be5ab
--- /dev/null
+++ b/content/browser/aggregation_service/aggregation_service_internals_ui.h
@@ -0,0 +1,58 @@
+// 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_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_INTERNALS_UI_H_
+#define CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_INTERNALS_UI_H_
+
+#include <memory>
+
+#include "content/browser/aggregation_service/aggregation_service_internals.mojom-forward.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/browser/webui_config.h"
+#include "content/public/common/url_constants.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace content {
+
+class AggregationServiceInternalsHandlerImpl;
+class AggregationServiceInternalsUI;
+class WebUI;
+
+// WebUIConfig for chrome://aggregation-service-internals page
+class AggregationServiceInternalsUIConfig
+    : public DefaultWebUIConfig<AggregationServiceInternalsUI> {
+ public:
+  AggregationServiceInternalsUIConfig()
+      : DefaultWebUIConfig(kChromeUIScheme,
+                           kChromeUIPrivateAggregationInternalsHost) {}
+};
+
+// WebUI which handles serving the chrome://aggregation-service-internals page.
+class AggregationServiceInternalsUI : public WebUIController {
+ public:
+  explicit AggregationServiceInternalsUI(WebUI* web_ui);
+  AggregationServiceInternalsUI(const AggregationServiceInternalsUI&) = delete;
+  AggregationServiceInternalsUI(AggregationServiceInternalsUI&&) = delete;
+  AggregationServiceInternalsUI& operator=(
+      const AggregationServiceInternalsUI&) = delete;
+  AggregationServiceInternalsUI& operator=(AggregationServiceInternalsUI&&) =
+      delete;
+  ~AggregationServiceInternalsUI() override;
+
+  // WebUIController overrides:
+  void WebUIRenderFrameCreated(RenderFrameHost* render_frame_host) override;
+
+  void BindInterface(
+      mojo::PendingReceiver<aggregation_service_internals::mojom::Handler>
+          receiver);
+
+ private:
+  std::unique_ptr<AggregationServiceInternalsHandlerImpl> ui_handler_;
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_INTERNALS_UI_H_
diff --git a/content/browser/aggregation_service/aggregation_service_test_utils.cc b/content/browser/aggregation_service/aggregation_service_test_utils.cc
index fe141c0..0a975be 100644
--- a/content/browser/aggregation_service/aggregation_service_test_utils.cc
+++ b/content/browser/aggregation_service/aggregation_service_test_utils.cc
@@ -213,6 +213,13 @@
 
 AggregatableReportRequest CreateExampleRequest(
     mojom::AggregationServiceMode aggregation_mode) {
+  return CreateExampleRequestWithReportTime(base::Time::Now(),
+                                            aggregation_mode);
+}
+
+AggregatableReportRequest CreateExampleRequestWithReportTime(
+    base::Time report_time,
+    mojom::AggregationServiceMode aggregation_mode) {
   return AggregatableReportRequest::Create(
              AggregationServicePayloadContents(
                  AggregationServicePayloadContents::Operation::kHistogram,
@@ -221,7 +228,7 @@
                      /*value=*/456)},
                  aggregation_mode),
              AggregatableReportSharedInfo(
-                 /*scheduled_report_time=*/base::Time::Now(),
+                 /*scheduled_report_time=*/report_time,
                  /*report_id=*/
                  base::GUID::GenerateRandomV4(),
                  url::Origin::Create(GURL("https://reporting.example")),
diff --git a/content/browser/aggregation_service/aggregation_service_test_utils.h b/content/browser/aggregation_service/aggregation_service_test_utils.h
index 304b7aab..9c6e3d7 100644
--- a/content/browser/aggregation_service/aggregation_service_test_utils.h
+++ b/content/browser/aggregation_service/aggregation_service_test_utils.h
@@ -69,6 +69,11 @@
     mojom::AggregationServiceMode aggregation_mode =
         mojom::AggregationServiceMode::kDefault);
 
+AggregatableReportRequest CreateExampleRequestWithReportTime(
+    base::Time report_time,
+    mojom::AggregationServiceMode aggregation_mode =
+        mojom::AggregationServiceMode::kDefault);
+
 AggregatableReportRequest CloneReportRequest(
     const AggregatableReportRequest& request);
 AggregatableReport CloneAggregatableReport(const AggregatableReport& report);
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index abcc0ca..7ff2810 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -308,6 +308,7 @@
   MSDH_REQUEST_ALL_SCREENS_NOT_ALLOWED_FOR_ORIGIN = 281,
   RFHI_CREATE_FENCED_FRAME_BAD_FRAME_TOKEN = 282,
   RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN = 283,
+  FF_FROZEN_SANDBOX_FLAGS_CHANGED = 284,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 15debe2..8566ac7 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -13,6 +13,8 @@
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
 #include "cc/base/switches.h"
+#include "content/browser/aggregation_service/aggregation_service_internals.mojom.h"
+#include "content/browser/aggregation_service/aggregation_service_internals_ui.h"
 #include "content/browser/attribution_reporting/attribution_internals.mojom.h"
 #include "content/browser/attribution_reporting/attribution_internals_ui.h"
 #include "content/browser/background_fetch/background_fetch_service_impl.h"
@@ -1044,6 +1046,9 @@
   map->Add<device::mojom::VRService>(
       base::BindRepeating(&EmptyBinderForFrame<device::mojom::VRService>));
 #endif
+  RegisterWebUIControllerInterfaceBinder<
+      aggregation_service_internals::mojom::Handler,
+      AggregationServiceInternalsUI>(map);
   RegisterWebUIControllerInterfaceBinder<attribution_internals::mojom::Handler,
                                          AttributionInternalsUI>(map);
   RegisterWebUIControllerInterfaceBinder<mojom::PrerenderInternalsHandler,
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.cc b/content/browser/first_party_sets/database/first_party_sets_database.cc
index fff7688..6a93f5b 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database.cc
@@ -17,6 +17,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "content/browser/first_party_sets/first_party_set_parser.h"
 #include "net/base/schemeful_site.h"
+#include "net/cookies/first_party_set_entry.h"
 #include "sql/database.h"
 #include "sql/error_delegate_util.h"
 #include "sql/meta_table.h"
@@ -148,7 +149,8 @@
 bool FirstPartySetsDatabase::InsertPolicyModifications(
     const std::string& browser_context_id,
     const base::flat_map<net::SchemefulSite,
-                         absl::optional<net::SchemefulSite>>& modificatons) {
+                         absl::optional<net::FirstPartySetEntry>>&
+        modificatons) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!LazyInit())
@@ -175,7 +177,7 @@
     statement.BindString(0, browser_context_id);
     statement.BindString(1, site.Serialize());
     if (owner.has_value()) {
-      statement.BindString(2, owner.value().Serialize());
+      statement.BindString(2, owner.value().primary().Serialize());
     } else {
       statement.BindNull(2);
     }
@@ -263,7 +265,7 @@
   return results;
 }
 
-base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>>
+base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
 FirstPartySetsDatabase::FetchPolicyModifications(
     const std::string& browser_context_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -271,7 +273,7 @@
   if (!LazyInit())
     return {};
 
-  base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>>
+  base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
       results;
   static constexpr char kSelectSql[] =
       // clang-format off
@@ -294,8 +296,19 @@
 
     // TODO(crbug/1314039): Invalid sites should be rare case but possible.
     // Consider deleting them from DB.
-    if (site.has_value())
-      results.emplace(std::move(site.value()), maybe_site_owner);
+    if (site.has_value()) {
+      results.emplace(
+          std::move(site.value()),
+          maybe_site_owner.has_value()
+              ? absl::make_optional(net::FirstPartySetEntry(
+                    maybe_site_owner.value(),
+                    // TODO(https://crbug.com/1219656): May change to use the
+                    // real site_type and site_index in the future, depending on
+                    // the design details. Use kAssociated as default site type
+                    // and null site index for now.
+                    net::SiteType::kAssociated, absl::nullopt))
+              : absl::nullopt);
+    }
   }
   return results;
 }
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.h b/content/browser/first_party_sets/database/first_party_sets_database.h
index ba7d8131..9c1adf3 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.h
+++ b/content/browser/first_party_sets/database/first_party_sets_database.h
@@ -19,6 +19,7 @@
 
 namespace net {
 class SchemefulSite;
+class FirstPartySetEntry;
 }  // namespace net
 
 namespace sql {
@@ -80,7 +81,8 @@
   [[nodiscard]] bool InsertPolicyModifications(
       const std::string& browser_context_id,
       const base::flat_map<net::SchemefulSite,
-                           absl::optional<net::SchemefulSite>>& modificatons);
+                           absl::optional<net::FirstPartySetEntry>>&
+          modificatons);
 
   // Gets the list of sites to clear for the `browser_context_id`.
   [[nodiscard]] std::vector<net::SchemefulSite> FetchSitesToClear(
@@ -95,7 +97,7 @@
   // Gets the previously-stored policy modifications for the
   // `browser_context_id`.
   [[nodiscard]] base::flat_map<net::SchemefulSite,
-                               absl::optional<net::SchemefulSite>>
+                               absl::optional<net::FirstPartySetEntry>>
   FetchPolicyModifications(const std::string& browser_context_id);
 
  private:
diff --git a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
index efe00c9..44406dd 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "net/base/schemeful_site.h"
+#include "net/cookies/first_party_set_entry.h"
 #include "sql/database.h"
 #include "sql/statement.h"
 #include "sql/test/test_helpers.h"
@@ -393,10 +394,12 @@
   const std::string site_member1 = "https://member1.test";
   const std::string site_member2 = "https://member2.test";
 
-  base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>> input =
-      {{net::SchemefulSite(GURL(site_member1)),
-        net::SchemefulSite(GURL(site_owner))},
-       {net::SchemefulSite(GURL(site_member2)), absl::nullopt}};
+  base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
+      input = {
+          {net::SchemefulSite(GURL(site_member1)),
+           net::FirstPartySetEntry(net::SchemefulSite(GURL(site_owner)),
+                                   net::SiteType::kAssociated, absl::nullopt)},
+          {net::SchemefulSite(GURL(site_member2)), absl::nullopt}};
 
   OpenDatabase();
   // Trigger the lazy-initialization.
@@ -456,10 +459,12 @@
   const std::string site_member1 = "https://member3.test";
   const std::string site_member2 = "https://member4.test";
 
-  base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>> input =
-      {{net::SchemefulSite(GURL(site_member1)),
-        net::SchemefulSite(GURL(site_owner))},
-       {net::SchemefulSite(GURL(site_member2)), absl::nullopt}};
+  base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
+      input = {
+          {net::SchemefulSite(GURL(site_member1)),
+           net::FirstPartySetEntry(net::SchemefulSite(GURL(site_owner)),
+                                   net::SiteType::kAssociated, absl::nullopt)},
+          {net::SchemefulSite(GURL(site_member2)), absl::nullopt}};
 
   OpenDatabase();
   // Trigger the lazy-initialization.
@@ -632,11 +637,14 @@
     EXPECT_EQ(kTableCount, sql::test::CountSQLTables(&db));
     EXPECT_EQ(2u, CountPolicyModificationsEntries(&db));
   }
-  base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>> res = {
-      {net::SchemefulSite(GURL("https://member1.test")),
-       {net::SchemefulSite(GURL("https://example.test"))}},
-      {net::SchemefulSite(GURL("https://member2.test")), absl::nullopt},
-  };
+  base::flat_map<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>
+      res = {
+          {net::SchemefulSite(GURL("https://member1.test")),
+           net::FirstPartySetEntry(
+               {net::SchemefulSite(GURL("https://example.test"))},
+               net::SiteType::kAssociated, absl::nullopt)},
+          {net::SchemefulSite(GURL("https://member2.test")), absl::nullopt},
+      };
   OpenDatabase();
   EXPECT_THAT(db()->FetchPolicyModifications("b2"), res);
 }
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 60015c4..674de83 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -467,7 +467,8 @@
   for (auto& [origin, requests] : private_aggregation_requests) {
     mojo::Remote<mojom::PrivateAggregationHost> remote;
     if (!private_aggregation_manager_->BindNewReceiver(
-            origin, PrivateAggregationBudgetKey::Api::kFledge,
+            origin, main_frame_origin_,
+            PrivateAggregationBudgetKey::Api::kFledge,
             remote.BindNewPipeAndPassReceiver())) {
       continue;
     }
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 541289d..b24c1be7 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -5638,13 +5638,14 @@
                                    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())));
+  auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(
+      browser_context()->GetDefaultStoragePartition());
+  storage_partition_impl->OverridePrivateAggregationManagerForTesting(
+      std::make_unique<TestPrivateAggregationManagerImpl>(
+          std::make_unique<MockPrivateAggregationBudgeter>(),
+          std::make_unique<PrivateAggregationHost>(
+              /*on_report_request_received=*/mock_callback.Get(),
+              /*browser_context=*/storage_partition_impl->browser_context())));
 
   network_responder_->RegisterScriptResponse(kBiddingUrlPath, kBiddingScript);
   network_responder_->RegisterScriptResponse(kDecisionUrlPath, kDecisionScript);
diff --git a/content/browser/private_aggregation/private_aggregation_host.cc b/content/browser/private_aggregation/private_aggregation_host.cc
index f3d8d83..2f5a1a57 100644
--- a/content/browser/private_aggregation/private_aggregation_host.cc
+++ b/content/browser/private_aggregation/private_aggregation_host.cc
@@ -19,6 +19,8 @@
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 #include "content/common/aggregatable_report.mojom.h"
 #include "content/common/private_aggregation_host.mojom.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -42,19 +44,25 @@
 
 struct PrivateAggregationHost::ReceiverContext {
   url::Origin worklet_origin;
+  url::Origin top_frame_origin;
   PrivateAggregationBudgetKey::Api api_for_budgeting;
 };
 
 PrivateAggregationHost::PrivateAggregationHost(
     base::RepeatingCallback<void(AggregatableReportRequest,
                                  PrivateAggregationBudgetKey)>
-        on_report_request_received)
-    : on_report_request_received_(std::move(on_report_request_received)) {}
+        on_report_request_received,
+    BrowserContext* browser_context)
+    : on_report_request_received_(std::move(on_report_request_received)),
+      browser_context_(*browser_context) {
+  DCHECK(!on_report_request_received_.is_null());
+}
 
 PrivateAggregationHost::~PrivateAggregationHost() = default;
 
 bool PrivateAggregationHost::BindNewReceiver(
     url::Origin worklet_origin,
+    url::Origin top_frame_origin,
     PrivateAggregationBudgetKey::Api api_for_budgeting,
     mojo::PendingReceiver<mojom::PrivateAggregationHost> pending_receiver) {
   if (!network::IsOriginPotentiallyTrustworthy(worklet_origin)) {
@@ -62,9 +70,11 @@
     // its requests are processed.
     return false;
   }
-  receiver_set_.Add(this, std::move(pending_receiver),
-                    ReceiverContext{.worklet_origin = std::move(worklet_origin),
-                                    .api_for_budgeting = api_for_budgeting});
+  receiver_set_.Add(
+      this, std::move(pending_receiver),
+      ReceiverContext{.worklet_origin = std::move(worklet_origin),
+                      .top_frame_origin = std::move(top_frame_origin),
+                      .api_for_budgeting = api_for_budgeting});
   return true;
 }
 
@@ -82,6 +92,12 @@
       receiver_set_.current_context().worklet_origin;
   DCHECK(network::IsOriginPotentiallyTrustworthy(reporting_origin));
 
+  if (!GetContentClient()->browser()->IsPrivateAggregationAllowed(
+          &*browser_context_, receiver_set_.current_context().top_frame_origin,
+          reporting_origin)) {
+    return;
+  }
+
   // Null pointers should fail mojo validation.
   DCHECK(base::ranges::none_of(
       contribution_ptrs,
diff --git a/content/browser/private_aggregation/private_aggregation_host.h b/content/browser/private_aggregation/private_aggregation_host.h
index 4157534e..019f273 100644
--- a/content/browser/private_aggregation/private_aggregation_host.h
+++ b/content/browser/private_aggregation/private_aggregation_host.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/memory/raw_ref.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
 #include "content/common/content_export.h"
 #include "content/common/private_aggregation_host.mojom.h"
@@ -21,6 +22,7 @@
 namespace content {
 
 class AggregatableReportRequest;
+class BrowserContext;
 
 // UI thread class responsible for implementing the mojo interface used by
 // worklets and renderers to request reports be sent and maintaining the
@@ -39,10 +41,12 @@
   // Aligns with `blink::kMaxAttributionAggregationKeysPerSourceOrTrigger`.
   static constexpr int kMaxNumberOfContributions = 50;
 
-  explicit PrivateAggregationHost(
+  // `on_report_request_received` and `browser_context` must be non-null.
+  PrivateAggregationHost(
       base::RepeatingCallback<void(AggregatableReportRequest,
                                    PrivateAggregationBudgetKey)>
-          on_report_request_received);
+          on_report_request_received,
+      BrowserContext* browser_context);
   PrivateAggregationHost(const PrivateAggregationHost&) = delete;
   PrivateAggregationHost& operator=(const PrivateAggregationHost&) = delete;
   ~PrivateAggregationHost() override;
@@ -53,6 +57,7 @@
   // receiver was accepted. Virtual for testing.
   [[nodiscard]] virtual bool BindNewReceiver(
       url::Origin worklet_origin,
+      url::Origin top_frame_origin,
       PrivateAggregationBudgetKey::Api api_for_budgeting,
       mojo::PendingReceiver<mojom::PrivateAggregationHost> pending_receiver);
 
@@ -71,6 +76,10 @@
 
   mojo::ReceiverSet<mojom::PrivateAggregationHost, ReceiverContext>
       receiver_set_;
+
+  // `this` is indirectly owned by the StoragePartitionImpl, which itself is
+  // owned by `browser_context_`.
+  raw_ref<BrowserContext> browser_context_;
 };
 
 }  // namespace content
diff --git a/content/browser/private_aggregation/private_aggregation_host_unittest.cc b/content/browser/private_aggregation/private_aggregation_host_unittest.cc
index aadc799..a16e9a5 100644
--- a/content/browser/private_aggregation/private_aggregation_host_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_host_unittest.cc
@@ -18,8 +18,12 @@
 #include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/aggregation_service/aggregation_service_test_utils.h"
 #include "content/browser/private_aggregation/private_aggregation_budget_key.h"
+#include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/common/aggregatable_report.mojom.h"
 #include "content/common/private_aggregation_host.mojom.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_utils.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -42,7 +46,8 @@
 
   void SetUp() override {
     host_ = std::make_unique<PrivateAggregationHost>(
-        /*on_report_request_received=*/mock_callback_.Get());
+        /*on_report_request_received=*/mock_callback_.Get(),
+        /*browser_context=*/&test_browser_context_);
   }
 
   void TearDown() override { host_.reset(); }
@@ -54,8 +59,9 @@
   std::unique_ptr<PrivateAggregationHost> host_;
 
  private:
-  base::test::TaskEnvironment task_environment_{
+  BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  TestBrowserContext test_browser_context_;
 };
 
 }  // namespace
@@ -64,9 +70,11 @@
        SendHistogramReport_ReportRequestHasCorrectMembers) {
   const url::Origin kExampleOrigin =
       url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
 
   mojo::Remote<mojom::PrivateAggregationHost> remote;
-  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin,
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin, kMainFrameOrigin,
                                      PrivateAggregationBudgetKey::Api::kFledge,
                                      remote.BindNewPipeAndPassReceiver()));
 
@@ -122,6 +130,8 @@
 TEST_F(PrivateAggregationHostTest, ReportingPath) {
   const url::Origin kExampleOrigin =
       url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
 
   const PrivateAggregationBudgetKey::Api apis[] = {
       PrivateAggregationBudgetKey::Api::kFledge,
@@ -132,8 +142,9 @@
       /*n=*/2};
 
   for (int i = 0; i < 2; i++) {
-    EXPECT_TRUE(host_->BindNewReceiver(
-        kExampleOrigin, apis[i], remotes[i].BindNewPipeAndPassReceiver()));
+    EXPECT_TRUE(
+        host_->BindNewReceiver(kExampleOrigin, kMainFrameOrigin, apis[i],
+                               remotes[i].BindNewPipeAndPassReceiver()));
     EXPECT_CALL(mock_callback_,
                 Run(_, Property(&PrivateAggregationBudgetKey::api, apis[i])))
         .WillOnce(MoveArg<0>(&validated_requests[i]));
@@ -163,21 +174,25 @@
       url::Origin::Create(GURL("https://a.example"));
   const url::Origin kExampleOriginB =
       url::Origin::Create(GURL("https://b.example"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
 
   std::vector<mojo::Remote<mojom::PrivateAggregationHost>> remotes(/*n=*/4);
 
-  EXPECT_TRUE(host_->BindNewReceiver(kExampleOriginA,
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOriginA, kMainFrameOrigin,
                                      PrivateAggregationBudgetKey::Api::kFledge,
                                      remotes[0].BindNewPipeAndPassReceiver()));
-  EXPECT_TRUE(host_->BindNewReceiver(kExampleOriginB,
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOriginB, kMainFrameOrigin,
                                      PrivateAggregationBudgetKey::Api::kFledge,
                                      remotes[1].BindNewPipeAndPassReceiver()));
-  EXPECT_TRUE(host_->BindNewReceiver(
-      kExampleOriginA, PrivateAggregationBudgetKey::Api::kSharedStorage,
-      remotes[2].BindNewPipeAndPassReceiver()));
-  EXPECT_TRUE(host_->BindNewReceiver(
-      kExampleOriginB, PrivateAggregationBudgetKey::Api::kSharedStorage,
-      remotes[3].BindNewPipeAndPassReceiver()));
+  EXPECT_TRUE(
+      host_->BindNewReceiver(kExampleOriginA, kMainFrameOrigin,
+                             PrivateAggregationBudgetKey::Api::kSharedStorage,
+                             remotes[2].BindNewPipeAndPassReceiver()));
+  EXPECT_TRUE(
+      host_->BindNewReceiver(kExampleOriginB, kMainFrameOrigin,
+                             PrivateAggregationBudgetKey::Api::kSharedStorage,
+                             remotes[3].BindNewPipeAndPassReceiver()));
 
   // Use the bucket as a sentinel to ensure that calls were routed correctly.
   EXPECT_CALL(mock_callback_,
@@ -233,14 +248,16 @@
   const url::Origin kInsecureOrigin =
       url::Origin::Create(GURL("http://example.com"));
   const url::Origin kOpaqueOrigin;
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
 
   mojo::Remote<mojom::PrivateAggregationHost> remote_1;
-  EXPECT_FALSE(host_->BindNewReceiver(kInsecureOrigin,
+  EXPECT_FALSE(host_->BindNewReceiver(kInsecureOrigin, kMainFrameOrigin,
                                       PrivateAggregationBudgetKey::Api::kFledge,
                                       remote_1.BindNewPipeAndPassReceiver()));
 
   mojo::Remote<mojom::PrivateAggregationHost> remote_2;
-  EXPECT_FALSE(host_->BindNewReceiver(kOpaqueOrigin,
+  EXPECT_FALSE(host_->BindNewReceiver(kOpaqueOrigin, kMainFrameOrigin,
                                       PrivateAggregationBudgetKey::Api::kFledge,
                                       remote_2.BindNewPipeAndPassReceiver()));
 
@@ -264,9 +281,11 @@
 TEST_F(PrivateAggregationHostTest, InvalidRequest_Rejected) {
   const url::Origin kExampleOrigin =
       url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
 
   mojo::Remote<mojom::PrivateAggregationHost> remote;
-  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin,
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin, kMainFrameOrigin,
                                      PrivateAggregationBudgetKey::Api::kFledge,
                                      remote.BindNewPipeAndPassReceiver()));
 
@@ -294,4 +313,64 @@
   remote.FlushForTesting();
 }
 
+TEST_F(PrivateAggregationHostTest, PrivateAggregationAllowed_RequestSucceeds) {
+  MockPrivateAggregationContentBrowserClient browser_client;
+  ScopedContentBrowserClientSetting setting(&browser_client);
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  mojo::Remote<mojom::PrivateAggregationHost> remote;
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin, kMainFrameOrigin,
+                                     PrivateAggregationBudgetKey::Api::kFledge,
+                                     remote.BindNewPipeAndPassReceiver()));
+
+  // If the API is enabled, the call should succeed.
+  EXPECT_CALL(browser_client,
+              IsPrivateAggregationAllowed(_, kMainFrameOrigin, kExampleOrigin))
+      .WillOnce(testing::Return(true));
+  EXPECT_CALL(mock_callback_, Run);
+
+  std::vector<mojom::AggregatableReportHistogramContributionPtr> contributions;
+  contributions.push_back(mojom::AggregatableReportHistogramContribution::New(
+      /*bucket=*/123, /*value=*/456));
+  remote->SendHistogramReport(std::move(contributions),
+                              mojom::AggregationServiceMode::kDefault);
+
+  remote.FlushForTesting();
+  EXPECT_TRUE(remote.is_connected());
+}
+
+TEST_F(PrivateAggregationHostTest, PrivateAggregationDisallowed_RequestFails) {
+  MockPrivateAggregationContentBrowserClient browser_client;
+  ScopedContentBrowserClientSetting setting(&browser_client);
+
+  const url::Origin kExampleOrigin =
+      url::Origin::Create(GURL("https://example.com"));
+  const url::Origin kMainFrameOrigin =
+      url::Origin::Create(GURL("https://main_frame.com"));
+
+  mojo::Remote<mojom::PrivateAggregationHost> remote;
+  EXPECT_TRUE(host_->BindNewReceiver(kExampleOrigin, kMainFrameOrigin,
+                                     PrivateAggregationBudgetKey::Api::kFledge,
+                                     remote.BindNewPipeAndPassReceiver()));
+
+  // If the API is enabled, the call should succeed.
+  EXPECT_CALL(browser_client,
+              IsPrivateAggregationAllowed(_, kMainFrameOrigin, kExampleOrigin))
+      .WillOnce(testing::Return(false));
+  EXPECT_CALL(mock_callback_, Run).Times(0);
+
+  std::vector<mojom::AggregatableReportHistogramContributionPtr> contributions;
+  contributions.push_back(mojom::AggregatableReportHistogramContribution::New(
+      /*bucket=*/123, /*value=*/456));
+  remote->SendHistogramReport(std::move(contributions),
+                              mojom::AggregationServiceMode::kDefault);
+
+  remote.FlushForTesting();
+  EXPECT_TRUE(remote.is_connected());
+}
+
 }  // namespace content
diff --git a/content/browser/private_aggregation/private_aggregation_manager.h b/content/browser/private_aggregation/private_aggregation_manager.h
index d57a75f06..8abe8f9 100644
--- a/content/browser/private_aggregation/private_aggregation_manager.h
+++ b/content/browser/private_aggregation/private_aggregation_manager.h
@@ -31,6 +31,7 @@
   // receiver was accepted.
   [[nodiscard]] virtual bool BindNewReceiver(
       url::Origin worklet_origin,
+      url::Origin top_frame_origin,
       PrivateAggregationBudgetKey::Api api_for_budgeting,
       mojo::PendingReceiver<mojom::PrivateAggregationHost>
           pending_receiver) = 0;
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl.cc b/content/browser/private_aggregation/private_aggregation_manager_impl.cc
index c6e3eab..5b34908 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl.cc
@@ -57,7 +57,9 @@
               /*on_report_request_received=*/base::BindRepeating(
                   &PrivateAggregationManagerImpl::
                       OnReportRequestReceivedFromHost,
-                  base::Unretained(this))),
+                  base::Unretained(this)),
+              storage_partition ? storage_partition->browser_context()
+                                : nullptr),
           storage_partition) {}
 
 PrivateAggregationManagerImpl::PrivateAggregationManagerImpl(
@@ -75,9 +77,11 @@
 
 bool PrivateAggregationManagerImpl::BindNewReceiver(
     url::Origin worklet_origin,
+    url::Origin top_frame_origin,
     PrivateAggregationBudgetKey::Api api_for_budgeting,
     mojo::PendingReceiver<mojom::PrivateAggregationHost> pending_receiver) {
-  return host_->BindNewReceiver(std::move(worklet_origin), api_for_budgeting,
+  return host_->BindNewReceiver(std::move(worklet_origin),
+                                std::move(top_frame_origin), api_for_budgeting,
                                 std::move(pending_receiver));
 }
 
diff --git a/content/browser/private_aggregation/private_aggregation_manager_impl.h b/content/browser/private_aggregation/private_aggregation_manager_impl.h
index 7c45570..127f9f1 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl.h
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl.h
@@ -48,6 +48,7 @@
   // PrivateAggregationManager:
   [[nodiscard]] bool BindNewReceiver(
       url::Origin worklet_origin,
+      url::Origin top_frame_origin,
       PrivateAggregationBudgetKey::Api api_for_budgeting,
       mojo::PendingReceiver<mojom::PrivateAggregationHost> pending_receiver)
       override;
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 2650f1e5..8808f8a05 100644
--- a/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
+++ b/content/browser/private_aggregation/private_aggregation_manager_impl_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
-#include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "content/browser/aggregation_service/aggregatable_report.h"
 #include "content/browser/aggregation_service/aggregation_service.h"
@@ -25,6 +24,7 @@
 #include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/common/aggregatable_report.mojom.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -45,6 +45,7 @@
 const base::Time kExampleTime = base::Time::FromJavaTime(1652984901234);
 
 constexpr char kExampleOriginUrl[] = "https://origin.example";
+constexpr char kExampleMainFrameUrl[] = "https://main_frame.example";
 
 class PrivateAggregationManagerImplUnderTest
     : public PrivateAggregationManagerImpl {
@@ -81,15 +82,14 @@
                  base::WrapUnique(aggregation_service_)) {}
 
  protected:
+  BrowserTaskEnvironment task_environment_;
+
   // Keep pointers around for EXPECT_CALL.
   MockPrivateAggregationBudgeter* budgeter_;
   MockPrivateAggregationHost* host_;
   MockAggregationService* aggregation_service_;
 
   testing::StrictMock<PrivateAggregationManagerImplUnderTest> manager_;
-
- private:
-  base::test::TaskEnvironment task_environment_;
 };
 
 TEST_F(PrivateAggregationManagerImplTest,
@@ -265,21 +265,25 @@
        BindNewReceiver_InvokesHostMethodIdentically) {
   const url::Origin example_origin =
       url::Origin::Create(GURL(kExampleOriginUrl));
+  const url::Origin example_main_frame_origin =
+      url::Origin::Create(GURL(kExampleMainFrameUrl));
 
   EXPECT_CALL(*host_,
-              BindNewReceiver(example_origin,
+              BindNewReceiver(example_origin, example_main_frame_origin,
                               PrivateAggregationBudgetKey::Api::kFledge, _))
       .WillOnce(Return(true));
   EXPECT_TRUE(manager_.BindNewReceiver(
-      example_origin, PrivateAggregationBudgetKey::Api::kFledge,
+      example_origin, example_main_frame_origin,
+      PrivateAggregationBudgetKey::Api::kFledge,
       mojo::PendingReceiver<mojom::PrivateAggregationHost>()));
 
   EXPECT_CALL(*host_, BindNewReceiver(
-                          example_origin,
+                          example_origin, example_main_frame_origin,
                           PrivateAggregationBudgetKey::Api::kSharedStorage, _))
       .WillOnce(Return(false));
   EXPECT_FALSE(manager_.BindNewReceiver(
-      example_origin, PrivateAggregationBudgetKey::Api::kSharedStorage,
+      example_origin, example_main_frame_origin,
+      PrivateAggregationBudgetKey::Api::kSharedStorage,
       mojo::PendingReceiver<mojom::PrivateAggregationHost>()));
 }
 
diff --git a/content/browser/private_aggregation/private_aggregation_test_utils.cc b/content/browser/private_aggregation/private_aggregation_test_utils.cc
index a079968..9327e2cb 100644
--- a/content/browser/private_aggregation/private_aggregation_test_utils.cc
+++ b/content/browser/private_aggregation/private_aggregation_test_utils.cc
@@ -16,10 +16,17 @@
 
 MockPrivateAggregationHost::MockPrivateAggregationHost()
     : PrivateAggregationHost(
-          /*on_report_request_received=*/base::DoNothing()) {}
+          /*on_report_request_received=*/base::DoNothing(),
+          &test_browser_context_) {}
 
 MockPrivateAggregationHost::~MockPrivateAggregationHost() = default;
 
+MockPrivateAggregationContentBrowserClient::
+    MockPrivateAggregationContentBrowserClient() = default;
+
+MockPrivateAggregationContentBrowserClient::
+    ~MockPrivateAggregationContentBrowserClient() = 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 08ae5f1..ac4696f 100644
--- a/content/browser/private_aggregation/private_aggregation_test_utils.h
+++ b/content/browser/private_aggregation/private_aggregation_test_utils.h
@@ -13,6 +13,8 @@
 #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 "content/public/test/test_browser_context.h"
+#include "content/test/test_content_browser_client.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -35,6 +37,8 @@
               (override));
 };
 
+// Note: the `TestBrowserContext` may require a `BrowserTaskEnvironment` to be
+// set up.
 class MockPrivateAggregationHost : public PrivateAggregationHost {
  public:
   MockPrivateAggregationHost();
@@ -43,6 +47,7 @@
   MOCK_METHOD(bool,
               BindNewReceiver,
               (url::Origin,
+               url::Origin,
                PrivateAggregationBudgetKey::Api,
                mojo::PendingReceiver<mojom::PrivateAggregationHost>),
               (override));
@@ -52,6 +57,24 @@
               (std::vector<mojom::AggregatableReportHistogramContributionPtr>,
                mojom::AggregationServiceMode aggregation_mode),
               (override));
+
+ private:
+  TestBrowserContext test_browser_context_;
+};
+
+class MockPrivateAggregationContentBrowserClient
+    : public TestContentBrowserClient {
+ public:
+  MockPrivateAggregationContentBrowserClient();
+  ~MockPrivateAggregationContentBrowserClient() override;
+
+  // ContentBrowserClient:
+  MOCK_METHOD(bool,
+              IsPrivateAggregationAllowed,
+              (content::BrowserContext * browser_context,
+               const url::Origin& top_frame_origin,
+               const url::Origin& reporting_origin),
+              (override));
 };
 
 bool operator==(const PrivateAggregationBudgetKey::TimeWindow&,
diff --git a/content/browser/renderer_host/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc
index 5640ef2..a90baf0c 100644
--- a/content/browser/renderer_host/frame_tree_node.cc
+++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -33,6 +33,7 @@
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
 #include "third_party/blink/public/common/loader/loader_constants.h"
 #include "third_party/blink/public/mojom/frame/user_activation_update_types.mojom.h"
 #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h"
@@ -492,6 +493,31 @@
     return;
   }
 
+  // Inside of a fenced frame, the sandbox flags should not be able to change
+  // from its initial value. If the flags change, we have to assume the change
+  // came from a compromised renderer and terminate it.
+  // We will only do the check if the sandbox flags are already set to
+  // kFencedFrameForcedSandboxFlags. This is to allow the sandbox flags to
+  // be set initially (go from kNone -> kFencedFrameForcedSandboxFlags). Once
+  // it has been set, it cannot change to another value.
+  // Note: The bad message is only expected to hit for ShadowDOM fenced frames.
+  // For MPArch, the RFHI will detect that the change is not coming from the
+  // frame's parent in DidChangeFramePolicy() (an MPArch fenced frame parent
+  // is null since it's the root frame in its tree) and terminate the
+  // renderer before we reach this point.
+  // TODO(crbug.com/1262022) When ShadowDOM is removed, turn this into a DCHECK
+  // and remove the BadMessage call.
+  if (IsFencedFrameRoot() &&
+      pending_frame_policy_.sandbox_flags ==
+          blink::kFencedFrameForcedSandboxFlags &&
+      frame_policy.sandbox_flags != blink::kFencedFrameForcedSandboxFlags) {
+    DCHECK(frame_tree()->IsFencedFramesShadowDOMBased());
+    bad_message::ReceivedBadMessage(
+        current_frame_host()->GetProcess(),
+        bad_message::FF_FROZEN_SANDBOX_FLAGS_CHANGED);
+    return;
+  }
+
   pending_frame_policy_.sandbox_flags = frame_policy.sandbox_flags;
 
   if (parent()) {
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index 135450e..00915d0 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -771,6 +771,8 @@
         video_type_, controls.disable_local_echo,
         controls.request_pan_tilt_zoom_permission);
     ui_request_->exclude_system_audio = controls.exclude_system_audio;
+    ui_request_->exclude_self_browser_surface =
+        controls.exclude_self_browser_surface;
   }
 
   // Creates a tab capture specific MediaStreamRequest object that is used by
diff --git a/content/browser/resources/aggregation_service/BUILD.gn b/content/browser/resources/aggregation_service/BUILD.gn
new file mode 100644
index 0000000..20effe61
--- /dev/null
+++ b/content/browser/resources/aggregation_service/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//tools/polymer/html_to_wrapper.gni")
+import("//tools/typescript/ts_library.gni")
+
+html_to_wrapper("html_wrapper_files") {
+  in_files = [ "aggregation_service_internals_table.html" ]
+  template = "native"
+}
+
+# Copy (via creating sym links) all the other files into the same folder for
+# ts_library.
+copy("copy_files") {
+  deps = [ "//content/browser/aggregation_service:mojo_bindings_webui_js" ]
+  sources = [
+    "$root_gen_dir/mojom-webui/content/browser/aggregation_service/aggregation_service_internals.mojom-webui.js",
+    "aggregation_service_internals.ts",
+    "aggregation_service_internals_table.ts",
+    "table_model.ts",
+  ]
+  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+}
+
+ts_library("build_ts") {
+  root_dir = target_gen_dir
+  out_dir = "$target_gen_dir/tsc"
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
+    "aggregation_service_internals.ts",
+    "aggregation_service_internals_table.ts",
+    "aggregation_service_internals_table.html.ts",
+    "table_model.ts",
+  ]
+
+  # Generated .js files.
+  in_files += [ "aggregation_service_internals.mojom-webui.js" ]
+  deps = [
+    "//ui/webui/resources:library",
+    "//ui/webui/resources/mojo:library",
+  ]
+  definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+  extra_deps = [
+    ":copy_files",
+    ":html_wrapper_files",
+  ]
+}
diff --git a/content/browser/resources/aggregation_service/DIR_METADATA b/content/browser/resources/aggregation_service/DIR_METADATA
new file mode 100644
index 0000000..e5ef722
--- /dev/null
+++ b/content/browser/resources/aggregation_service/DIR_METADATA
@@ -0,0 +1 @@
+mixins: "//content/browser/aggregation_service/COMMON_METADATA"
diff --git a/content/browser/resources/aggregation_service/OWNERS b/content/browser/resources/aggregation_service/OWNERS
new file mode 100644
index 0000000..76fa9199
--- /dev/null
+++ b/content/browser/resources/aggregation_service/OWNERS
@@ -0,0 +1 @@
+file://content/browser/aggregation_service/OWNERS
diff --git a/content/browser/resources/aggregation_service/aggregation_service_internals.css b/content/browser/resources/aggregation_service/aggregation_service_internals.css
new file mode 100644
index 0000000..d6fa9dc
--- /dev/null
+++ b/content/browser/resources/aggregation_service/aggregation_service_internals.css
@@ -0,0 +1,47 @@
+/* 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. */
+
+* {
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+}
+
+body {
+  color: rgb(48, 57, 66);
+  display: flex;
+  flex-direction: column;
+  font-family: Roboto, sans-serif;
+  font-size: 13px;
+  height: 100%;
+  margin: 15px;
+  overflow: auto;
+}
+
+h1 {
+  color: rgb(92, 97, 102);
+  font-size: 1.5rem;
+  padding-bottom: 10px;
+}
+
+main {
+  border-top: 1px solid rgb(48, 57, 66);
+  margin-top: 1rem;
+  padding-top: 1rem;
+}
+
+.content {
+  background-color: rgb(251, 251, 251);
+}
+
+button {
+  font: inherit;
+  margin-inline-end: 30px;
+}
+
+.sendbutton {
+  margin-bottom: 16px;
+}
diff --git a/content/browser/resources/aggregation_service/aggregation_service_internals.html b/content/browser/resources/aggregation_service/aggregation_service_internals.html
new file mode 100644
index 0000000..c7d35ab1
--- /dev/null
+++ b/content/browser/resources/aggregation_service/aggregation_service_internals.html
@@ -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.-->
+<!DOCTYPE html>
+<html dir="ltr" lang="en">
+<head>
+  <meta charset="utf-8">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+  <link rel="stylesheet" href="chrome://resources/css/roboto.css">
+
+  <link rel="stylesheet" href="aggregation_service_internals.css">
+  <script type="module" src="aggregation_service_internals.js"></script>
+  <title>Private Aggregation API Internals</title>
+</head>
+<body>
+<header>
+  <h1>Private Aggregation API Internals</h1>
+  <button id="refresh">Refresh all page data</button>
+  <button id="clear-data">Clear all private aggregation data</button>
+</header>
+<main>
+<section>
+  <h2>Aggregatable reports</h2>
+  <div class="content">
+    <button class = "button sendbutton" id="send-reports" disabled>Send Selected Reports</button>
+    <aggregation-service-internals-table id="reportTable">
+    </aggregation-service-internals-table>
+  </div>
+</section>
+</main>
+</body>
+</html>
diff --git a/content/browser/resources/aggregation_service/aggregation_service_internals.ts b/content/browser/resources/aggregation_service/aggregation_service_internals.ts
new file mode 100644
index 0000000..e6ba6f5bf
--- /dev/null
+++ b/content/browser/resources/aggregation_service/aggregation_service_internals.ts
@@ -0,0 +1,368 @@
+// 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 './aggregation_service_internals_table.js';
+
+import {assert} from 'chrome://resources/js/assert_ts.js';
+
+import {AggregatableReportRequestID, Handler as AggregationServiceInternalsHandler, HandlerRemote as AggregationServiceInternalsHandlerRemote, ObserverInterface, ObserverReceiver, ReportStatus, WebUIAggregatableReport} from './aggregation_service_internals.mojom-webui.js';
+import {AggregationServiceInternalsTableElement} from './aggregation_service_internals_table.js';
+import {Column, TableModel} from './table_model.js';
+
+function compareDefault<T>(a: T, b: T): number {
+  return (a < b) ? -1 : ((a > b) ? 1 : 0);
+}
+
+function bigintReplacer(_key: string, value: any): any {
+  return typeof value === 'bigint' ? value.toString() : value;
+}
+
+class ValueColumn<T, V> implements Column<T> {
+  compare: (a: T, b: T) => number;
+  header: string;
+  private minWidth?: string;
+  protected getValue: (param: T) => V;
+
+  constructor(
+      header: string, getValue: (param: T) => V, minWidth?: string,
+      compare?: ((a: T, b: T) => number)) {
+    this.header = header;
+    this.getValue = getValue;
+    this.minWidth = minWidth;
+    this.compare =
+        compare ?? ((a: T, b: T) => compareDefault(getValue(a), getValue(b)));
+  }
+
+  render(td: HTMLElement, row: T) {
+    if (this.minWidth) {
+      td.style.minWidth = this.minWidth;
+    }
+    td.textContent = `${this.getValue(row)}`;
+  }
+
+  renderHeader(th: HTMLElement) {
+    th.textContent = `${this.header}`;
+  }
+}
+
+/**
+ * Column that holds date.
+ */
+class DateColumn<T> extends ValueColumn<T, Date> {
+  constructor(header: string, getValue: (p: T) => Date) {
+    super(header, getValue);
+  }
+
+  override render(td: HTMLElement, row: T) {
+    td.innerText = this.getValue(row).toLocaleString();
+  }
+}
+
+class CodeColumn<T> extends ValueColumn<T, string> {
+  constructor(header: string, getValue: (p: T) => string) {
+    super(header, getValue);
+  }
+
+  override render(td: HTMLElement, row: T) {
+    const code = td.ownerDocument.createElement('code');
+    code.innerText = this.getValue(row);
+
+    const pre = td.ownerDocument.createElement('pre');
+    pre.appendChild(code);
+
+    td.appendChild(pre);
+  }
+}
+
+/**
+ * Wraps a checkbox.
+ */
+class Selectable {
+  selectCheckbox: HTMLInputElement;
+
+  constructor() {
+    this.selectCheckbox = document.createElement('input');
+    this.selectCheckbox.type = 'checkbox';
+  }
+}
+
+/**
+ * Checkbox column for selection.
+ */
+class SelectionColumn<T extends Selectable> implements Column<T> {
+  compare: ((a: T, b: T) => number)|null;
+  private model: TableModel<T>;
+  private selectAllCheckbox: HTMLInputElement;
+  selectionChangedListeners: Set<(param: boolean) => void>;
+  header: string|null;
+  private rowChangedListener: () => void;
+
+  constructor(model: TableModel<T>) {
+    // Selection column is not sortable.
+    this.compare = null;
+    this.model = model;
+    this.header = null;
+
+    this.selectAllCheckbox = document.createElement('input');
+    this.selectAllCheckbox.type = 'checkbox';
+    this.selectAllCheckbox.addEventListener('input', () => {
+      const checked = this.selectAllCheckbox.checked;
+      this.model.getRows().forEach((row) => {
+        if (!row.selectCheckbox.disabled) {
+          row.selectCheckbox.checked = checked;
+        }
+      });
+      this.notifySelectionChanged(checked);
+    });
+
+    this.rowChangedListener = () => this.onChange();
+    this.model.rowsChangedListeners.add(this.rowChangedListener);
+    this.selectionChangedListeners = new Set();
+  }
+
+  render(td: HTMLElement, row: T) {
+    td.appendChild(row.selectCheckbox);
+  }
+
+  renderHeader(th: HTMLElement) {
+    th.appendChild(this.selectAllCheckbox);
+  }
+
+  onChange() {
+    let anySelectable = false;
+    let anySelected = false;
+    let anyUnselected = false;
+
+    this.model.getRows().forEach((row) => {
+      // addEventListener deduplicates, so only one event will be fired per
+      // input.
+      row.selectCheckbox.addEventListener('input', this.rowChangedListener);
+
+      if (row.selectCheckbox.disabled) {
+        return;
+      }
+
+      anySelectable = true;
+      if (row.selectCheckbox.checked) {
+        anySelected = true;
+      } else {
+        anyUnselected = true;
+      }
+    });
+
+    this.selectAllCheckbox.disabled = !anySelectable;
+    this.selectAllCheckbox.checked = anySelected && !anyUnselected;
+    this.selectAllCheckbox.indeterminate = anySelected && anyUnselected;
+
+    this.notifySelectionChanged(anySelected);
+  }
+
+  notifySelectionChanged(anySelected: boolean) {
+    this.selectionChangedListeners.forEach((f) => f(anySelected));
+  }
+}
+
+function reportStatusToText(status: ReportStatus) {
+  switch (status) {
+    case ReportStatus.kPending:
+      return 'Pending';
+    case ReportStatus.kSent:
+      return 'Sent';
+    case ReportStatus.kFailedToAssemble:
+      return 'Failed to assemble';
+    case ReportStatus.kFailedToSend:
+      return 'Failed to send';
+    default:
+      return status.toString();
+  }
+}
+
+class Report extends Selectable {
+  id: AggregatableReportRequestID;
+  reportBody: string;
+  reportUrl: string;
+  reportTime: Date;
+  status: string;
+  apiIdentifier: string;
+  apiVersion: string;
+  contributions: string;
+
+  constructor(mojo: WebUIAggregatableReport) {
+    super();
+
+    this.id = mojo.id;
+    this.reportBody = mojo.reportBody;
+    this.reportUrl = mojo.reportUrl.url;
+    this.reportTime = new Date(mojo.reportTime);
+    this.apiIdentifier = mojo.apiIdentifier;
+    this.apiVersion = mojo.apiVersion;
+
+    // Only pending reports are selectable.
+    if (mojo.status !== ReportStatus.kPending) {
+      this.selectCheckbox.disabled = true;
+    }
+
+    this.status = reportStatusToText(mojo.status);
+
+    this.contributions =
+        JSON.stringify(mojo.contributions, bigintReplacer, ' ');
+  }
+}
+
+class ReportTableModel extends TableModel<Report> {
+  private sendReportsButton: HTMLButtonElement;
+  private selectionColumn: SelectionColumn<Report>;
+  private handledReports: Report[] = [];
+  private storedReports: Report[] = [];
+
+  constructor(sendReportsButton: HTMLButtonElement) {
+    super();
+
+    this.sendReportsButton = sendReportsButton;
+
+    this.selectionColumn = new SelectionColumn(this);
+
+    this.cols = [
+      this.selectionColumn,
+      new ValueColumn<Report, string>('Status', (e) => e.status),
+      new ValueColumn<Report, string>(
+          'Report URL', (e) => e.reportUrl, '250px'),
+      new DateColumn<Report>('Report Time', (e) => e.reportTime),
+      new ValueColumn<Report, string>(
+          'API identifier', (e) => e.apiIdentifier, '90px'),
+      new ValueColumn<Report, string>('API version', (e) => e.apiVersion),
+      new CodeColumn<Report>(
+          'Contributions', (e) => (e as Report).contributions),
+      new CodeColumn<Report>('Report Body', (e) => e.reportBody),
+    ];
+
+    // Sort by report time by default.
+    this.sortIdx = 3;
+    assert(this.cols[this.sortIdx]!.header === 'Report Time');
+
+    this.emptyRowText = 'No sent or pending reports.';
+
+    this.sendReportsButton.addEventListener('click', () => this.sendReports_());
+    this.selectionColumn.selectionChangedListeners.add(
+        (anySelected: boolean) => {
+          this.sendReportsButton.disabled = !anySelected;
+        });
+  }
+
+  override getRows() {
+    return this.handledReports.concat(this.storedReports);
+  }
+
+  setStoredReports(storedReports: Report[]) {
+    this.storedReports = storedReports;
+    this.notifyRowsChanged();
+  }
+
+  addHandledReport(report: Report) {
+    // Prevent the page from consuming ever more memory if the user leaves the
+    // page open for a long time.
+    if (this.handledReports.length >= 1000) {
+      this.handledReports = [];
+    }
+
+    this.handledReports.push(report);
+
+    this.notifyRowsChanged();
+  }
+
+  clear() {
+    this.storedReports = [];
+    this.handledReports = [];
+    this.notifyRowsChanged();
+  }
+
+  /**
+   * Sends all selected reports.
+   * Disables the button while the reports are still being sent.
+   * Observer.onRequestStorageModified will be called automatically as reports
+   * are deleted, so there's no need to manually refresh the data on completion.
+   */
+  private sendReports_() {
+    const ids: AggregatableReportRequestID[] = [];
+    this.storedReports.forEach((report) => {
+      if (!report.selectCheckbox.disabled && report.selectCheckbox.checked) {
+        ids.push(report.id);
+      }
+    });
+
+    if (ids.length === 0) {
+      return;
+    }
+
+    const previousText = this.sendReportsButton.innerText;
+
+    this.sendReportsButton.disabled = true;
+    this.sendReportsButton.innerText = 'Sending...';
+
+    pageHandler!.sendReports(ids).then(() => {
+      this.sendReportsButton.innerText = previousText;
+    });
+  }
+}
+
+/**
+ * Reference to the backend providing all the data.
+ */
+let pageHandler: AggregationServiceInternalsHandlerRemote|null = null;
+
+let reportTableModel: ReportTableModel|null = null;
+
+/**
+ * Fetches all pending reports from the backend and populate the tables.
+ */
+function updateReports() {
+  pageHandler!.getReports().then((response) => {
+    reportTableModel!.setStoredReports(
+        response.reports.map((mojo) => new Report(mojo)));
+  });
+}
+
+/**
+ * Deletes all data stored by the aggregation service backend.
+ * Observer.onRequestStorageModified will be called automatically as reports are
+ * deleted, so there's no need to manually refresh the data on completion.
+ */
+function clearStorage() {
+  reportTableModel!.clear();
+  pageHandler!.clearStorage();
+}
+
+class Observer implements ObserverInterface {
+  onRequestStorageModified() {
+    updateReports();
+  }
+
+  onReportHandled(mojo: WebUIAggregatableReport) {
+    reportTableModel!.addHandledReport(new Report(mojo));
+  }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+  // Setup the mojo interface.
+  pageHandler = AggregationServiceInternalsHandler.getRemote();
+
+  const sendReports =
+      document.querySelector<HTMLButtonElement>('#send-reports');
+  reportTableModel = new ReportTableModel(sendReports!);
+
+  const refresh = document.querySelector('#refresh');
+  refresh!.addEventListener('click', updateReports);
+  const clearData = document.querySelector('#clear-data');
+  clearData!.addEventListener('click', clearStorage);
+
+  const reportTable =
+      document.querySelector<AggregationServiceInternalsTableElement<Report>>(
+          '#reportTable');
+  reportTable!.setModel(reportTableModel!);
+
+  const receiver = new ObserverReceiver(new Observer());
+  pageHandler!.addObserver(receiver.$.bindNewPipeAndPassRemote());
+
+  updateReports();
+});
diff --git a/content/browser/resources/aggregation_service/aggregation_service_internals_table.html b/content/browser/resources/aggregation_service/aggregation_service_internals_table.html
new file mode 100644
index 0000000..f640a65
--- /dev/null
+++ b/content/browser/resources/aggregation_service/aggregation_service_internals_table.html
@@ -0,0 +1,58 @@
+<style>
+  :host {
+    background-color: #fff;
+    border-color: rgba(0, 0, 0, .12);
+    border-radius: 4px;
+    border-style: solid;
+    border-width: 1px;
+    display: block;
+    overflow-x: scroll;
+  }
+
+  table {
+    border: 0;
+    border-collapse: collapse;
+  }
+
+  tbody tr {
+    border-top-color: rgba(0, 0, 0, .12);
+    border-top-style: solid;
+    border-top-width: 1px;
+  }
+
+  thead tr {
+    border: 0;
+  }
+
+  table td,
+  table th {
+    padding-bottom: 16px;
+    padding-inline-end: 16px;
+    padding-inline-start: 16px;
+    padding-top: 16px;
+    text-align: start;
+    vertical-align: top;
+  }
+
+  th[aria-sort] {
+    cursor: pointer;
+  }
+
+  th[aria-sort]::after {
+    content: '\u2B0D'; /* Up Down Black Arrow */
+  }
+
+  th[aria-sort='ascending']::after {
+    content: '\u2B06'; /* Upwards Black Arrow */
+  }
+
+  th[aria-sort='descending']::after {
+    content: '\u2B07'; /* Downwards Black Arrow */
+  }
+</style>
+<table>
+  <thead>
+    <tr></tr>
+  </thead>
+  <tbody></tbody>
+</table>
diff --git a/content/browser/resources/aggregation_service/aggregation_service_internals_table.ts b/content/browser/resources/aggregation_service/aggregation_service_internals_table.ts
new file mode 100644
index 0000000..cee0d72
--- /dev/null
+++ b/content/browser/resources/aggregation_service/aggregation_service_internals_table.ts
@@ -0,0 +1,141 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
+
+import {getTemplate} from './aggregation_service_internals_table.html.js';
+import {TableModel} from './table_model.js';
+
+/**
+ * Helper function for setting sort attributes on |th|.
+ */
+function setSortAttrs(th: HTMLElement, sortDesc: boolean|null) {
+  let nextDir;
+  if (sortDesc === null) {
+    th.ariaSort = 'none';
+    nextDir = 'ascending';
+  } else if (sortDesc) {
+    th.ariaSort = 'descending';
+    nextDir = 'ascending';
+  } else {
+    th.ariaSort = 'ascending';
+    nextDir = 'descending';
+  }
+
+  th.title = `Sort by ${th.innerText} ${nextDir}`;
+  th.ariaLabel = th.title;
+}
+
+/**
+ * Table abstracts the logic for rendering and sorting a table. The table's
+ * columns are supplied by a TableModel supplied to the decorate function. Each
+ * Column knows how to render the underlying value of the row type T, and
+ * optionally sort rows of type T by that value.
+ */
+export class AggregationServiceInternalsTableElement<T> extends CustomElement {
+  static override get template() {
+    return getTemplate();
+  }
+
+  private model_: TableModel<T>|null = null;
+  private sortDesc_: boolean = false;
+
+  setModel(model: TableModel<T>) {
+    this.model_ = model;
+    this.sortDesc_ = false;
+
+    const tr = this.$<HTMLElement>('tr');
+    assert(tr);
+    model.cols.forEach((col, idx) => {
+      const th = document.createElement('th');
+      th.scope = 'col';
+      col.renderHeader(th);
+
+      if (col.compare) {
+        th.setAttribute('role', 'button');
+        setSortAttrs(th, /*sortDesc=*/ null);
+        th.addEventListener('click', () => this.changeSortHeader_(idx));
+      }
+
+      tr.appendChild(th);
+    });
+
+    this.addEmptyStateRow_();
+    this.model_.rowsChangedListeners.add(() => this.updateTbody());
+  }
+
+  private addEmptyStateRow_() {
+    const td = document.createElement('td');
+    assert(this.model_);
+    td.textContent = this.model_.emptyRowText;
+    td.colSpan = this.model_.cols.length;
+    const tr = document.createElement('tr');
+    tr.appendChild(td);
+    const tbody = this.$<HTMLElement>('tbody');
+    assert(tbody);
+    tbody.appendChild(tr);
+  }
+
+  private changeSortHeader_(idx: number) {
+    const ths = this.$all<HTMLElement>('thead th');
+
+    assert(this.model_);
+    if (idx === this.model_.sortIdx) {
+      this.sortDesc_ = !this.sortDesc_;
+    } else {
+      this.sortDesc_ = false;
+      if (this.model_.sortIdx >= 0) {
+        setSortAttrs(ths[this.model_.sortIdx]!, /*descending=*/ null);
+      }
+    }
+
+    this.model_.sortIdx = idx;
+    setSortAttrs(ths[this.model_.sortIdx]!, this.sortDesc_);
+    this.updateTbody();
+  }
+
+  private sort_(rows: T[]) {
+    assert(this.model_);
+    if (this.model_.sortIdx < 0) {
+      return;
+    }
+
+    const multiplier = this.sortDesc_ ? -1 : 1;
+    rows.sort(
+        (a, b) => this.model_!.cols[this.model_!.sortIdx]!.compare!(a, b) *
+            multiplier);
+  }
+
+  updateTbody() {
+    const tbody = this.$<HTMLElement>('tbody');
+    assert(tbody);
+    tbody.innerText = '';
+
+    assert(this.model_);
+    const rows = this.model_.getRows();
+    if (rows.length === 0) {
+      this.addEmptyStateRow_();
+      return;
+    }
+
+    this.sort_(rows);
+
+    rows.forEach((row) => {
+      const tr = document.createElement('tr');
+      assert(this.model_);
+      this.model_.cols.forEach((col) => {
+        const td = document.createElement('td');
+        col.render(td, row);
+        tr.appendChild(td);
+      });
+      this.model_.styleRow(tr, row);
+      tbody.appendChild(tr);
+    });
+  }
+}
+
+customElements.define(
+    'aggregation-service-internals-table',
+    AggregationServiceInternalsTableElement);
diff --git a/content/browser/resources/aggregation_service/table_model.ts b/content/browser/resources/aggregation_service/table_model.ts
new file mode 100644
index 0000000..b44e692
--- /dev/null
+++ b/content/browser/resources/aggregation_service/table_model.ts
@@ -0,0 +1,34 @@
+// 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.
+
+export interface Column<T> {
+  compare: ((a: T, b: T) => number)|null;
+
+  render(td: HTMLElement, row: T): void;
+
+  renderHeader(th: HTMLElement): void;
+
+  header: string|null;
+}
+
+export class TableModel<T> {
+  cols: Array<Column<T>> = [];
+  emptyRowText: string = '';
+  sortIdx: number = -1;
+  rowsChangedListeners: Set<() => void>;
+
+  constructor() {
+    this.rowsChangedListeners = new Set();
+  }
+
+  styleRow(_tr: Element, _data: T) {}
+
+  getRows(): T[] {
+    return [];
+  }
+
+  notifyRowsChanged() {
+    this.rowsChangedListeners.forEach(f => f());
+  }
+}
diff --git a/content/browser/resources/aggregation_service/tsconfig_base.json b/content/browser/resources/aggregation_service/tsconfig_base.json
new file mode 100644
index 0000000..99a81eca
--- /dev/null
+++ b/content/browser/resources/aggregation_service/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 3c6ef8d..d7155ee9 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -55,6 +55,7 @@
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/public/test/fenced_frame_test_util.h"
 #include "content/public/test/navigation_handle_observer.h"
+#include "content/public/test/test_frame_navigation_observer.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/test_utils.h"
@@ -92,6 +93,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
 #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
+#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
 #include "third_party/blink/public/common/navigation/navigation_policy.h"
 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
@@ -1962,21 +1964,75 @@
             kill_waiter.Wait());
 }
 
-class SecurityExploitBrowserTestEnableFencedFrames
-    : public SecurityExploitBrowserTest {
+class SecurityExploitBrowserTestParameterizedFencedFrames
+    : public SecurityExploitBrowserTest,
+      public ::testing::WithParamInterface<
+          blink::features::FencedFramesImplementationType> {
  public:
-  SecurityExploitBrowserTestEnableFencedFrames() {
+  SecurityExploitBrowserTestParameterizedFencedFrames() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
+        blink::features::kFencedFrames,
+        {{"implementation_type",
+          GetParam() ==
+                  blink::features::FencedFramesImplementationType::kShadowDOM
+              ? "shadow_dom"
+              : "mparch"}});
   }
 
+  // Provides meaningful param names instead of /0 and /1.
+  static std::string DescribeParams(
+      const ::testing::TestParamInfo<ParamType>& info) {
+    switch (info.param) {
+      case blink::features::FencedFramesImplementationType::kShadowDOM:
+        return "ShadowDOM";
+      case blink::features::FencedFramesImplementationType::kMPArch:
+        return "MPArch";
+    }
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    https_server()->StartAcceptingConnections();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    https_server()->AddDefaultHandlers(GetTestDataFilePath());
+    https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
+    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
+    SetupCrossSiteRedirector(https_server());
+
+    // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_|
+    // which is required below. This cannot invoke Start() however as that kicks
+    // off the "EmbeddedTestServer IO Thread" which then races with
+    // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545.
+    ASSERT_TRUE(https_server()->InitializeAndListen());
+  }
+
+  test::FencedFrameTestHelper& fenced_frame_test_helper() {
+    return fenced_frame_test_helper_;
+  }
+
+  net::EmbeddedTestServer* https_server() { return &https_server_; }
+
  private:
+  test::FencedFrameTestHelper fenced_frame_test_helper_{
+      GetParam() == blink::features::FencedFramesImplementationType::kShadowDOM
+          ? test::FencedFrameTestHelper::FencedFrameType::kShadowDOM
+          : test::FencedFrameTestHelper::FencedFrameType::kMPArch};
   base::test::ScopedFeatureList scoped_feature_list_;
+  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
 };
 
-IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestEnableFencedFrames,
+IN_PROC_BROWSER_TEST_P(SecurityExploitBrowserTestParameterizedFencedFrames,
                        NavigateFencedFrameToInvalidURL) {
-  GURL main_frame_url("http://foo.com/simple_page.html");
+  // The valid URL browser check only exists for the MPArch implementation of
+  // fenced frames. So, we have to disable this test for ShadowDOM.
+  if (GetParam() ==
+      blink::features::FencedFramesImplementationType::kShadowDOM) {
+    return;
+  }
+
+  GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
   std::vector<GURL> invalid_urls = {
       GURL("http://example.com"),
       GURL("about:srcdoc"),
@@ -2026,4 +2082,68 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(SecurityExploitBrowserTestParameterizedFencedFrames,
+                       ChangeFencedFrameSandboxFlags) {
+  GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
+
+  EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
+  RenderFrameHostImpl* root_rfh = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  const GURL fenced_frame_url =
+      https_server()->GetURL("a.test", "/fenced_frames/sandbox_flags.html");
+  constexpr char kAddFencedFrameScript[] = R"({
+      const fenced_frame = document.createElement('fencedframe');
+      fenced_frame.src = $1;
+      document.body.appendChild(fenced_frame);
+    })";
+  EXPECT_TRUE(
+      ExecJs(root_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
+
+  RenderFrameHostImpl* fenced_rfh = nullptr;
+  RenderFrameHostImpl* parent_rfh = nullptr;
+
+  if (GetParam() ==
+      blink::features::FencedFramesImplementationType::kShadowDOM) {
+    // ShadowDOM
+    fenced_rfh = root_rfh->child_at(0)->current_frame_host();
+    parent_rfh = root_rfh;
+  } else {
+    // MPArch
+    std::vector<FencedFrame*> fenced_frames = root_rfh->GetFencedFrames();
+    EXPECT_EQ(fenced_frames.size(), 1u);
+    FencedFrame* new_fenced_frame = fenced_frames.back();
+    fenced_rfh = new_fenced_frame->GetInnerRoot();
+    parent_rfh = fenced_rfh;
+  }
+
+  RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
+
+  blink::FramePolicy first_policy =
+      fenced_rfh->frame_tree_node()->pending_frame_policy();
+
+  first_policy.sandbox_flags = blink::kFencedFrameMandatoryUnsandboxedFlags;
+  static_cast<blink::mojom::LocalFrameHost*>(parent_rfh)
+      ->DidChangeFramePolicy(std::move(fenced_rfh->GetFrameToken()),
+                             std::move(first_policy));
+
+  if (GetParam() ==
+      blink::features::FencedFramesImplementationType::kShadowDOM) {
+    EXPECT_EQ(bad_message::FF_FROZEN_SANDBOX_FLAGS_CHANGED, kill_waiter.Wait());
+  } else {
+    // MPArch fenced frames are the root node of its frame tree, so a different
+    // check is expected to trigger. This check makes sure only the parent can
+    // change a frame's sandbox flags.
+    EXPECT_EQ(bad_message::RFH_SANDBOX_FLAGS, kill_waiter.Wait());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SecurityExploitBrowserTestParameterizedFencedFrames,
+    ::testing::Values(
+        blink::features::FencedFramesImplementationType::kShadowDOM,
+        blink::features::FencedFramesImplementationType::kMPArch),
+    &SecurityExploitBrowserTestParameterizedFencedFrames::DescribeParams);
+
 }  // namespace content
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index b50be4b..af2a82c 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -2796,18 +2796,21 @@
 
     a_test_origin_ = https_server()->GetOrigin("a.test");
 
-    private_aggregation_host_ = new PrivateAggregationHost(
-        /*on_report_request_received=*/mock_callback_.Get());
+    auto* storage_partition_impl =
+        static_cast<StoragePartitionImpl*>(shell()
+                                               ->web_contents()
+                                               ->GetBrowserContext()
+                                               ->GetDefaultStoragePartition());
 
-    static_cast<StoragePartitionImpl*>(shell()
-                                           ->web_contents()
-                                           ->GetBrowserContext()
-                                           ->GetDefaultStoragePartition())
-        ->OverridePrivateAggregationManagerForTesting(
-            std::make_unique<TestPrivateAggregationManagerImpl>(
-                std::make_unique<MockPrivateAggregationBudgeter>(),
-                base::WrapUnique<PrivateAggregationHost>(
-                    private_aggregation_host_)));
+    private_aggregation_host_ = new PrivateAggregationHost(
+        /*on_report_request_received=*/mock_callback_.Get(),
+        storage_partition_impl->browser_context());
+
+    storage_partition_impl->OverridePrivateAggregationManagerForTesting(
+        std::make_unique<TestPrivateAggregationManagerImpl>(
+            std::make_unique<MockPrivateAggregationBudgeter>(),
+            base::WrapUnique<PrivateAggregationHost>(
+                private_aggregation_host_)));
 
     EXPECT_TRUE(NavigateToURL(
         shell(), https_server()->GetURL("a.test", kSimplePagePath)));
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index b386f91..d4f5351 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -687,7 +687,7 @@
   mojo::PendingRemote<content::mojom::PrivateAggregationHost>
       pending_pa_host_remote;
   if (!private_aggregation_manager->BindNewReceiver(
-          shared_storage_origin_,
+          shared_storage_origin_, main_frame_origin_,
           PrivateAggregationBudgetKey::Api::kSharedStorage,
           pending_pa_host_remote.InitWithNewPipeAndPassReceiver())) {
     return mojo::PendingRemote<content::mojom::PrivateAggregationHost>();
diff --git a/content/browser/speech/tts_utterance_impl.cc b/content/browser/speech/tts_utterance_impl.cc
index 5c26851..38e661c 100644
--- a/content/browser/speech/tts_utterance_impl.cc
+++ b/content/browser/speech/tts_utterance_impl.cc
@@ -48,9 +48,10 @@
 
 // static
 std::unique_ptr<TtsUtterance> TtsUtterance::Create(
-    BrowserContext* browser_context) {
-  DCHECK(browser_context);
-  return std::make_unique<TtsUtteranceImpl>(browser_context, nullptr);
+    BrowserContext* browser_context,
+    bool should_always_be_spoken) {
+  return std::make_unique<TtsUtteranceImpl>(browser_context,
+                                            should_always_be_spoken);
 }
 
 // static
@@ -72,6 +73,12 @@
   }
 }
 
+TtsUtteranceImpl::TtsUtteranceImpl(BrowserContext* browser_context,
+                                   bool should_always_be_spoken)
+    : TtsUtteranceImpl(browser_context, nullptr) {
+  should_always_be_spoken_ = should_always_be_spoken;
+}
+
 TtsUtteranceImpl::~TtsUtteranceImpl() {
   // It's an error if an Utterance is destructed without being finished,
   // unless |browser_context_| is nullptr because it's a unit test.
@@ -222,4 +229,8 @@
   return web_contents_.get();
 }
 
+bool TtsUtteranceImpl::ShouldAlwaysBeSpoken() {
+  return should_always_be_spoken_;
+}
+
 }  // namespace content
diff --git a/content/browser/speech/tts_utterance_impl.h b/content/browser/speech/tts_utterance_impl.h
index c458910..5410bf8c 100644
--- a/content/browser/speech/tts_utterance_impl.h
+++ b/content/browser/speech/tts_utterance_impl.h
@@ -11,7 +11,9 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/unguessable_token.h"
 #include "base/values.h"
+#include "build/chromeos_buildflags.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/tts_utterance.h"
 
@@ -27,6 +29,9 @@
 class CONTENT_EXPORT TtsUtteranceImpl : public TtsUtterance {
  public:
   TtsUtteranceImpl(BrowserContext* browser_context, WebContents* web_contents);
+  TtsUtteranceImpl(content::BrowserContext* browser_context,
+                   bool should_always_be_spoken);
+
   ~TtsUtteranceImpl() override;
 
   bool was_created_with_web_contents() const {
@@ -86,7 +91,9 @@
   bool IsFinished() override;
 
   // Returns the associated WebContents, may be null.
-  WebContents* GetWebContents();
+  WebContents* GetWebContents() override;
+
+  bool ShouldAlwaysBeSpoken() override;
 
  private:
   // The BrowserContext that initiated this utterance.
@@ -138,6 +145,9 @@
 
   // True if this utterance received an event indicating it's done.
   bool finished_;
+
+  // True if this utterance should always be spoken.
+  bool should_always_be_spoken_ = false;
 };
 
 }  // namespace content
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 8c102e3..26e57f0 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -292,9 +292,14 @@
 }
 
 void FederatedAuthRequestImpl::RequestToken(
-    IdentityProviderPtr identity_provider_ptr,
+    std::vector<blink::mojom::IdentityProviderPtr> identity_provider_ptrs,
     bool prefer_auto_sign_in,
     RequestTokenCallback callback) {
+  // TODO(crbug.com/1348262): Temporarily support only the first IDP, extend to
+  // support multiple IDPs.
+  blink::mojom::IdentityProviderPtr identity_provider_ptr =
+      std::move(identity_provider_ptrs[0]);
+
   if (HasPendingRequest()) {
     fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kTooManyRequests);
     std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests, "");
@@ -327,7 +332,7 @@
   network_manager_ = CreateNetworkManager(identity_provider_ptr->config_url);
 
   FederatedApiPermissionStatus permission_status =
-      api_permission_delegate_->GetApiPermissionStatus(origin());
+      api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin());
 
   absl::optional<TokenStatus> error_token_status;
   FederatedAuthRequestResult request_result =
@@ -427,7 +432,7 @@
     return;
   }
 
-  if (api_permission_delegate_->GetApiPermissionStatus(origin()) !=
+  if (api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin()) !=
       FederatedApiPermissionStatus::GRANTED) {
     CompleteLogoutRequest(LogoutRpsStatus::kError);
     return;
@@ -706,6 +711,7 @@
 
       WebContents* rp_web_contents =
           WebContents::FromRenderFrameHost(&render_frame_host());
+      DCHECK(render_frame_host().GetMainFrame()->IsInPrimaryMainFrame());
 
       ComputeLoginStateAndReorderAccounts(identity_provider, accounts);
 
@@ -748,8 +754,8 @@
     bool idp_claimed_sign_in = account.login_state == LoginState::kSignIn;
     bool browser_observed_sign_in =
         sharing_permission_delegate_->HasSharingPermission(
-            origin(), url::Origin::Create(identity_provider.config_url),
-            account.id);
+            origin(), GetEmbeddingOrigin(),
+            url::Origin::Create(identity_provider.config_url), account.id);
 
     if (idp_claimed_sign_in == browser_observed_sign_in) {
       fedcm_metrics_->RecordSignInStateMatchStatus(
@@ -796,7 +802,7 @@
   // settings are changed while an existing FedCM UI is displayed. Ideally, we
   // should enforce this check before all requests but users typically won't
   // have time to disable the FedCM API in other types of requests.
-  if (api_permission_delegate_->GetApiPermissionStatus(origin()) !=
+  if (api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin()) !=
       FederatedApiPermissionStatus::GRANTED) {
     fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kDisabledInSettings);
 
@@ -807,7 +813,7 @@
 
   RecordIsSignInUser(is_sign_in);
 
-  api_permission_delegate_->RemoveEmbargoAndResetCounts(origin());
+  api_permission_delegate_->RemoveEmbargoAndResetCounts(GetEmbeddingOrigin());
 
   account_id_ = account_id;
   select_account_time_ = base::TimeTicks::Now();
@@ -847,7 +853,7 @@
   fedcm_metrics_->RecordCancelReason(dismiss_reason);
 
   if (should_embargo) {
-    api_permission_delegate_->RecordDismissAndEmbargo(origin());
+    api_permission_delegate_->RecordDismissAndEmbargo(GetEmbeddingOrigin());
   }
 
   // Reject the promise immediately if the UI is dismissed without selecting
@@ -928,8 +934,8 @@
       // https://crbug.com/1199088
       CHECK(!account_id_.empty());
       sharing_permission_delegate_->GrantSharingPermission(
-          origin(), url::Origin::Create(identity_provider.config_url),
-          account_id_);
+          origin(), GetEmbeddingOrigin(),
+          url::Origin::Create(identity_provider.config_url), account_id_);
 
       active_session_permission_delegate_->GrantActiveSession(
           origin(), url::Origin::Create(identity_provider.config_url),
@@ -1060,6 +1066,12 @@
   return api_permission_delegate_->ShouldCompleteRequestImmediately();
 }
 
+url::Origin FederatedAuthRequestImpl::GetEmbeddingOrigin() const {
+  RenderFrameHost* main_frame = render_frame_host().GetMainFrame();
+  DCHECK(main_frame->IsInPrimaryMainFrame());
+  return main_frame->GetLastCommittedOrigin();
+}
+
 void FederatedAuthRequestImpl::CompleteLogoutRequest(
     blink::mojom::LogoutRpsStatus status) {
   network_manager_.reset();
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index ea80961..31bf3bf3 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -56,9 +56,10 @@
   ~FederatedAuthRequestImpl() override;
 
   // blink::mojom::FederatedAuthRequest:
-  void RequestToken(blink::mojom::IdentityProviderPtr identity_provider_ptr,
-                    bool prefer_auto_sign_in,
-                    RequestTokenCallback) override;
+  void RequestToken(
+      std::vector<blink::mojom::IdentityProviderPtr> identity_provider_ptrs,
+      bool prefer_auto_sign_in,
+      RequestTokenCallback) override;
   void CancelTokenRequest() override;
   void LogoutRps(std::vector<blink::mojom::LogoutRpsRequestPtr> logout_requests,
                  LogoutRpsCallback) override;
@@ -165,6 +166,8 @@
       const blink::mojom::IdentityProvider& identity_provider,
       IdpNetworkRequestManager::AccountList& accounts);
 
+  url::Origin GetEmbeddingOrigin() const;
+
   std::unique_ptr<IdpNetworkRequestManager> network_manager_;
   std::unique_ptr<IdentityRequestDialogController> request_dialog_controller_;
 
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index f064701..48c690a 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -700,10 +700,12 @@
                      const std::string& nonce,
                      bool prefer_auto_sign_in,
                      bool wait_for_callback) {
-    blink::mojom::IdentityProviderPtr identity_provider =
+    std::vector<blink::mojom::IdentityProviderPtr> identity_provider_ptrs;
+    blink::mojom::IdentityProviderPtr identity_provider_ptr =
         blink::mojom::IdentityProvider::New(provider, client_id, nonce);
+    identity_provider_ptrs.push_back(std::move(identity_provider_ptr));
 
-    request_remote_->RequestToken(std::move(identity_provider),
+    request_remote_->RequestToken(std::move(identity_provider_ptrs),
                                   prefer_auto_sign_in, auth_helper_.callback());
 
     if (wait_for_callback)
@@ -1207,7 +1209,7 @@
   // Pretend the sharing permission has been granted for this account.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
   RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
@@ -1217,11 +1219,12 @@
 
 TEST_F(FederatedAuthRequestImplTest,
        LoginStateSuccessfulSignUpGrantsSharingPermission) {
-  EXPECT_CALL(*mock_sharing_permission_delegate_, HasSharingPermission(_, _, _))
+  EXPECT_CALL(*mock_sharing_permission_delegate_,
+              HasSharingPermission(_, _, _, _))
       .WillOnce(Return(false));
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      GrantSharingPermission(OriginFromString(kRpUrl),
+      GrantSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                              OriginFromString(kProviderUrlFull), kAccountId))
       .Times(1);
   RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
@@ -1230,10 +1233,11 @@
 
 TEST_F(FederatedAuthRequestImplTest,
        LoginStateFailedSignUpNotGrantSharingPermission) {
-  EXPECT_CALL(*mock_sharing_permission_delegate_, HasSharingPermission(_, _, _))
+  EXPECT_CALL(*mock_sharing_permission_delegate_,
+              HasSharingPermission(_, _, _, _))
       .WillOnce(Return(false));
   EXPECT_CALL(*mock_sharing_permission_delegate_,
-              GrantSharingPermission(_, _, _))
+              GrantSharingPermission(_, _, _, _))
       .Times(0);
 
   MockConfiguration configuration = kConfigurationValid;
@@ -1256,7 +1260,7 @@
   // Pretend the sharing permission has been granted for this account.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
 
@@ -1330,7 +1334,7 @@
   // Pretend the sharing permission has been granted for this account.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
 
@@ -1362,9 +1366,9 @@
 
 TEST_F(FederatedAuthRequestImplTest, MetricsForSuccessfulSignInCase) {
   // Pretends that the sharing permission has been granted for this account.
-  EXPECT_CALL(
-      *mock_sharing_permission_delegate_,
-      HasSharingPermission(_, OriginFromString(kProviderUrlFull), kAccountId))
+  EXPECT_CALL(*mock_sharing_permission_delegate_,
+              HasSharingPermission(_, _, OriginFromString(kProviderUrlFull),
+                                   kAccountId))
       .WillOnce(Return(true));
 
   base::RunLoop ukm_loop;
@@ -1516,9 +1520,9 @@
             content::PageVisibilityState::kVisible);
 
   // Pretends that the sharing permission has been granted for this account.
-  EXPECT_CALL(
-      *mock_sharing_permission_delegate_,
-      HasSharingPermission(_, OriginFromString(kProviderUrlFull), kAccountId))
+  EXPECT_CALL(*mock_sharing_permission_delegate_,
+              HasSharingPermission(_, _, OriginFromString(kProviderUrlFull),
+                                   kAccountId))
       .WillOnce(Return(true));
 
   RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
@@ -1637,7 +1641,7 @@
   // Set browser observes user is signed in.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
 
@@ -1667,7 +1671,7 @@
   // Set browser observes user is not signed in.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(false));
 
@@ -1693,7 +1697,7 @@
   // Set browser observes user is not signed in.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(false));
 
@@ -1724,7 +1728,7 @@
   // Set browser observes user is signed in.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
 
@@ -1924,7 +1928,7 @@
   // Pretend the sharing permission has been granted for this account.
   EXPECT_CALL(
       *mock_sharing_permission_delegate_,
-      HasSharingPermission(OriginFromString(kRpUrl),
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                            OriginFromString(kProviderUrlFull), kAccountId))
       .WillOnce(Return(true));
 
diff --git a/content/browser/webid/test/mock_sharing_permission_delegate.h b/content/browser/webid/test/mock_sharing_permission_delegate.h
index 078a1284..64b79e2 100644
--- a/content/browser/webid/test/mock_sharing_permission_delegate.h
+++ b/content/browser/webid/test/mock_sharing_permission_delegate.h
@@ -24,13 +24,15 @@
 
   MOCK_METHOD(bool,
               HasSharingPermission,
-              (const url::Origin& relying_party,
+              (const url::Origin& relying_party_requester,
+               const url::Origin& relying_party_embedder,
                const url::Origin& identity_provider,
                const std::string& account_id),
               (override));
   MOCK_METHOD(void,
               GrantSharingPermission,
-              (const url::Origin& relying_party,
+              (const url::Origin& relying_party_requester,
+               const url::Origin& relying_party_embedder,
                const url::Origin& identity_provider,
                const std::string& account_id),
               (override));
diff --git a/content/browser/webui/content_web_ui_configs.cc b/content/browser/webui/content_web_ui_configs.cc
index 4a20d74a..615d5fa 100644
--- a/content/browser/webui/content_web_ui_configs.cc
+++ b/content/browser/webui/content_web_ui_configs.cc
@@ -4,6 +4,9 @@
 
 #include "content/browser/webui/content_web_ui_configs.h"
 
+#include <memory>
+
+#include "content/browser/aggregation_service/aggregation_service_internals_ui.h"
 #include "content/browser/attribution_reporting/attribution_internals_ui.h"
 #include "content/browser/gpu/gpu_internals_ui.h"
 #include "content/browser/indexed_db/indexed_db_internals_ui.h"
@@ -27,6 +30,7 @@
 void RegisterContentWebUIConfigs() {
   auto& map = WebUIConfigMap::GetInstance();
   map.AddWebUIConfig(std::make_unique<AttributionInternalsUIConfig>());
+  map.AddWebUIConfig(std::make_unique<AggregationServiceInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<GpuInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<IndexedDBInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<MediaInternalsUIConfig>());
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 9416bce..495261c 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -390,6 +390,8 @@
           {"SecureContextFixForWorkers",
            blink::features::kSecureContextFixForWorkers},
           {"StorageAccessAPI", net::features::kStorageAccessAPI},
+          {"StorageAccessAPIForSiteExtension",
+           blink::features::kStorageAccessAPIForSiteExtension},
           {"ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes",
            blink::features::
                kThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes},
@@ -651,6 +653,20 @@
         << blink::features::kBrowsingTopics.name << " instead.";
     WebRuntimeFeatures::EnableTopicsAPI(false);
   }
+
+  // Storage Access API ForSite cannot be enabled unless the larger Storage
+  // Access API is also enabled.
+  if (base::FeatureList::IsEnabled(
+          blink::features::kStorageAccessAPIForSiteExtension) &&
+      !base::FeatureList::IsEnabled(net::features::kStorageAccessAPI)) {
+    LOG_IF(WARNING,
+           WebRuntimeFeatures::IsStorageAccessAPIForSiteExtensionEnabled())
+        << "requestStorageAccessForSite cannot be enabled in this "
+           "configuration. Use --"
+        << switches::kEnableFeatures << "="
+        << net::features::kStorageAccessAPI.name << " in addition.";
+    WebRuntimeFeatures::EnableStorageAccessAPIForSiteExtension(false);
+  }
 }
 
 }  // namespace
diff --git a/content/dev_ui_content_resources.grd b/content/dev_ui_content_resources.grd
index 24817e94..f75bbe67 100644
--- a/content/dev_ui_content_resources.grd
+++ b/content/dev_ui_content_resources.grd
@@ -15,6 +15,13 @@
   <translations />
   <release seq="1">
     <includes>
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_HTML" file="browser/resources/aggregation_service/aggregation_service_internals.html" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_JS" file="${root_gen_dir}/content/browser/resources/aggregation_service/tsc/aggregation_service_internals.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_MODEL_JS" file="${root_gen_dir}/content/browser/resources/aggregation_service/tsc/table_model.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_JS" file="${root_gen_dir}/content/browser/resources/aggregation_service/tsc/aggregation_service_internals_table.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_HTML_JS" file="${root_gen_dir}/content/browser/resources/aggregation_service/tsc/aggregation_service_internals_table.html.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_CSS" file="browser/resources/aggregation_service/aggregation_service_internals.css" type="BINDATA" />
+      <include name="IDR_AGGREGATION_SERVICE_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/aggregation_service/aggregation_service_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_ATTRIBUTION_INTERNALS_HTML" file="browser/resources/attribution_reporting/attribution_internals.html" type="BINDATA" />
       <include name="IDR_ATTRIBUTION_INTERNALS_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_MODEL_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/table_model.js" use_base_dir="false" type="BINDATA" />
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 6044fe8..2722ee6 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -512,6 +512,13 @@
   return true;
 }
 
+bool ContentBrowserClient::IsPrivateAggregationAllowed(
+    content::BrowserContext* browser_context,
+    const url::Origin& top_frame_origin,
+    const url::Origin& reporting_origin) {
+  return true;
+}
+
 bool ContentBrowserClient::CanSendSCTAuditingReport(
     BrowserContext* browser_context) {
   return false;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 5853cf2..3f07e771 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -873,6 +873,13 @@
                                       const url::Origin& top_frame_origin,
                                       const url::Origin& accessing_origin);
 
+  // Allows the embedder to control if Private Aggregation API operations can
+  // happen in a given context.
+  virtual bool IsPrivateAggregationAllowed(
+      content::BrowserContext* browser_context,
+      const url::Origin& top_frame_origin,
+      const url::Origin& reporting_origin);
+
 #if BUILDFLAG(IS_CHROMEOS)
   // Notification that a trust anchor was used by the given user.
   virtual void OnTrustAnchorUsed(BrowserContext* browser_context) {}
diff --git a/content/public/browser/federated_identity_active_session_permission_context_delegate.h b/content/public/browser/federated_identity_active_session_permission_context_delegate.h
index 5aa3036..60711a4 100644
--- a/content/public/browser/federated_identity_active_session_permission_context_delegate.h
+++ b/content/public/browser/federated_identity_active_session_permission_context_delegate.h
@@ -19,21 +19,22 @@
   FederatedIdentityActiveSessionPermissionContextDelegate() = default;
   virtual ~FederatedIdentityActiveSessionPermissionContextDelegate() = default;
 
-  // Determine whether the relying_party has an existing active session for
-  // the specified account_identifier with the identity_provider.
-  virtual bool HasActiveSession(const url::Origin& relying_party,
+  // Determine whether the `relying_party_requester` has an existing active
+  // session for the specified `account_identifier` with the
+  // `identity_provider`.
+  virtual bool HasActiveSession(const url::Origin& relying_party_requester,
                                 const url::Origin& identity_provider,
                                 const std::string& account_identifier) = 0;
 
-  // Grant active session capabilities between the relying_party and
-  // identity_provider origins for the specified account.
-  virtual void GrantActiveSession(const url::Origin& relying_party,
+  // Grant active session capabilities between the `relying_party_requester` and
+  // `identity_provider` origins for the specified account.
+  virtual void GrantActiveSession(const url::Origin& relying_party_requester,
                                   const url::Origin& identity_provider,
                                   const std::string& account_identifier) = 0;
 
-  // Revoke a previously-provided grant from the relying_party to the provider
-  // for the specified account.
-  virtual void RevokeActiveSession(const url::Origin& relying_party,
+  // Revoke a previously-provided grant from the `relying_party_requester` to
+  // the `identity_provider` for the specified account.
+  virtual void RevokeActiveSession(const url::Origin& relying_party_requester,
                                    const url::Origin& identity_provider,
                                    const std::string& account_identifier) = 0;
 };
diff --git a/content/public/browser/federated_identity_api_permission_context_delegate.h b/content/public/browser/federated_identity_api_permission_context_delegate.h
index f209baa..a737288 100644
--- a/content/public/browser/federated_identity_api_permission_context_delegate.h
+++ b/content/public/browser/federated_identity_api_permission_context_delegate.h
@@ -28,18 +28,21 @@
   FederatedIdentityApiPermissionContextDelegate() = default;
   virtual ~FederatedIdentityApiPermissionContextDelegate() = default;
 
-  // Returns the status of the FedCM API for the passed-in |rp_origin|.
+  // Returns the status of the FedCM API for the passed-in
+  // |relying_party_embedder|.
   virtual PermissionStatus GetApiPermissionStatus(
-      const url::Origin& rp_origin) = 0;
+      const url::Origin& relying_party_embedder) = 0;
 
   // Records that the FedCM prompt was explicitly dismissed and places the
-  // permission under embargo for the passed-in |rp_origin|.
-  virtual void RecordDismissAndEmbargo(const url::Origin& rp_origin) = 0;
+  // permission under embargo for the passed-in |relying_party_embedder|.
+  virtual void RecordDismissAndEmbargo(
+      const url::Origin& relying_party_embedder) = 0;
 
-  // Clears any existing embargo status for |url| for the FEDERATED_IDENTITY_API
-  // permission for the passed-in |rp_origin|. Clears the dismiss and ignore
-  // counts.
-  virtual void RemoveEmbargoAndResetCounts(const url::Origin& rp_origin) = 0;
+  // Clears any existing embargo status for the FEDERATED_IDENTITY_API
+  // permission for the passed-in |relying_party_embedder|. Clears the dismiss
+  // and ignore counts.
+  virtual void RemoveEmbargoAndResetCounts(
+      const url::Origin& relying_party_embedder) = 0;
 
   // This function is so we can avoid the delay in tests. It does not really
   // belong on this delegate but we don't have a better one and it seems
diff --git a/content/public/browser/federated_identity_sharing_permission_context_delegate.h b/content/public/browser/federated_identity_sharing_permission_context_delegate.h
index 0a7abdce..a0e27edb 100644
--- a/content/public/browser/federated_identity_sharing_permission_context_delegate.h
+++ b/content/public/browser/federated_identity_sharing_permission_context_delegate.h
@@ -17,17 +17,21 @@
   FederatedIdentitySharingPermissionContextDelegate() = default;
   virtual ~FederatedIdentitySharingPermissionContextDelegate() = default;
 
-  // Determine whether the requester has an existing permission grant to share
-  // identity information for the given account to the relying party.
-  virtual bool HasSharingPermission(const url::Origin& relying_party,
+  // Determine whether there is an existing permission grant to share identity
+  // information for the given account to the `relying_party_requester` when
+  // embedded in `relying_party_embedder`.
+  virtual bool HasSharingPermission(const url::Origin& relying_party_requester,
+                                    const url::Origin& relying_party_embedder,
                                     const url::Origin& identity_provider,
                                     const std::string& account_id) = 0;
 
-  // Grant permission for the requester to share identity information for the
-  // given account to the relying party.
-  virtual void GrantSharingPermission(const url::Origin& relying_party,
-                                      const url::Origin& identity_provider,
-                                      const std::string& account_id) = 0;
+  // Grants permission to share identity information for the given account to
+  // `relying_party_requester` when embedded in `relying_party_embedder`.
+  virtual void GrantSharingPermission(
+      const url::Origin& relying_party_requester,
+      const url::Origin& relying_party_embedder,
+      const url::Origin& identity_provider,
+      const std::string& account_id) = 0;
 };
 
 }  // namespace content
diff --git a/content/public/browser/media_stream_request.h b/content/public/browser/media_stream_request.h
index 255a3777..89e3275 100644
--- a/content/public/browser/media_stream_request.h
+++ b/content/public/browser/media_stream_request.h
@@ -86,6 +86,10 @@
   // system-audio should nevertheless not be offered to the user.
   bool exclude_system_audio = false;
 
+  // Flag to indicate that the current tab should be excluded from the list of
+  // tabs offered to the user.
+  bool exclude_self_browser_surface = false;
+
   // Flag to indicate whether the request is for PTZ use.
   bool request_pan_tilt_zoom_permission;
 };
diff --git a/content/public/browser/tts_utterance.h b/content/public/browser/tts_utterance.h
index ea73614..ddf425e9 100644
--- a/content/public/browser/tts_utterance.h
+++ b/content/public/browser/tts_utterance.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <set>
 
+#include "base/unguessable_token.h"
 #include "base/values.h"
 #include "content/common/content_export.h"
 #include "url/gurl.h"
@@ -63,7 +64,10 @@
   // Before speaking this utterance, its other parameters like text, rate,
   // pitch, etc. should all be set.
   static std::unique_ptr<TtsUtterance> Create(WebContents* web_contents);
-  static std::unique_ptr<TtsUtterance> Create(BrowserContext* browser_context);
+  // |should_always_be_spoken|: See comment for ShouldAlwaysBeSpoken().
+  static std::unique_ptr<TtsUtterance> Create(
+      BrowserContext* browser_context,
+      bool should_always_be_spoken = false);
   static std::unique_ptr<TtsUtterance> Create();
 
   virtual ~TtsUtterance() = default;
@@ -126,6 +130,14 @@
   virtual void ClearBrowserContext() = 0;
   virtual int GetId() = 0;
   virtual bool IsFinished() = 0;
+  virtual WebContents* GetWebContents() = 0;
+
+  // An utterance could become invalid (for example, its associated WebContents
+  // has been destroyed) and therefore should not be spoken when it is
+  // processed by TtsController from the utterance queue. If this function
+  // returns true, it guarantees that the utterance must be a valid one and
+  // should be spoken.
+  virtual bool ShouldAlwaysBeSpoken() = 0;
 };
 
 }  // namespace content
diff --git a/content/public/common/url_constants.cc b/content/public/common/url_constants.cc
index a502f88..17904d6 100644
--- a/content/public/common/url_constants.cc
+++ b/content/public/common/url_constants.cc
@@ -37,6 +37,8 @@
 const char kChromeUINetworkErrorHost[] = "network-error";
 const char kChromeUINetworkErrorsListingHost[] = "network-errors";
 const char kChromeUIPrerenderInternalsHost[] = "prerender-internals";
+const char kChromeUIPrivateAggregationInternalsHost[] =
+    "private-aggregation-internals";
 const char kChromeUIProcessInternalsHost[] = "process-internals";
 const char kChromeUIQuotaInternalsHost[] = "quota-internals";
 const char kChromeUIResourcesHost[] = "resources";
diff --git a/content/public/common/url_constants.h b/content/public/common/url_constants.h
index ebbb5796..ff7a3081 100644
--- a/content/public/common/url_constants.h
+++ b/content/public/common/url_constants.h
@@ -47,6 +47,7 @@
 CONTENT_EXPORT extern const char kChromeUINetworkErrorHost[];
 CONTENT_EXPORT extern const char kChromeUINetworkErrorsListingHost[];
 CONTENT_EXPORT extern const char kChromeUIPrerenderInternalsHost[];
+CONTENT_EXPORT extern const char kChromeUIPrivateAggregationInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIProcessInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIQuotaInternalsHost[];
 CONTENT_EXPORT extern const char kChromeUIResourcesHost[];
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 0521a8f..bb2781f 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -276,6 +276,7 @@
 #elif BUILDFLAG(IS_MAC)
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       base::BindRepeating(&views::test::CreateEventGeneratorDelegateMac));
+  EnableNativeWindowActivation();
 #endif
 }
 
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 5c6d2d3..5c7793a1 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -2293,6 +2293,13 @@
 [[nodiscard]] bool HistoryGoBack(WebContents* wc);
 [[nodiscard]] bool HistoryGoForward(WebContents* wc);
 
+#if BUILDFLAG(IS_MAC)
+// Grant native windows the ability to activate, allowing them to become key
+// and/or main. This can be useful to enable when the process hosting the window
+// is a standalone executable without an Info.plist.
+bool EnableNativeWindowActivation();
+#endif  // BUILDFLAG(IS_MAC)
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_TEST_BROWSER_TEST_UTILS_H_
diff --git a/content/public/test/browser_test_utils_mac.mm b/content/public/test/browser_test_utils_mac.mm
new file mode 100644
index 0000000..fabd386
--- /dev/null
+++ b/content/public/test/browser_test_utils_mac.mm
@@ -0,0 +1,24 @@
+// 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/public/test/browser_test_utils.h"
+
+#import <AppKit/AppKit.h>
+
+namespace content {
+
+bool EnableNativeWindowActivation() {
+  // Do not downgrade the activation policy.
+  if (NSApp.activationPolicy > NSApplicationActivationPolicyProhibited) {
+    return true;
+  }
+
+  // NSApplicationActivationPolicyAccessory is the least permissive policy that
+  // still allows for programmatic activation.
+  return [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]
+             ? true
+             : false;
+}
+
+}  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 0531a26..5a70dc2 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1164,8 +1164,9 @@
     case blink::kWebNavigationPolicyNewWindow:
       return WindowOpenDisposition::NEW_WINDOW;
     case blink::kWebNavigationPolicyNewPopup:
-    case blink::kWebNavigationPolicyPictureInPicture:
       return WindowOpenDisposition::NEW_POPUP;
+    case blink::kWebNavigationPolicyPictureInPicture:
+      return WindowOpenDisposition::NEW_PICTURE_IN_PICTURE;
   }
   NOTREACHED() << "Unexpected WebNavigationPolicy";
   return WindowOpenDisposition::IGNORE_ACTION;
diff --git a/content/shell/browser/shell_federated_permission_context.cc b/content/shell/browser/shell_federated_permission_context.cc
index 45cb0bc..10422fe 100644
--- a/content/shell/browser/shell_federated_permission_context.cc
+++ b/content/shell/browser/shell_federated_permission_context.cc
@@ -16,59 +16,63 @@
 
 content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus
 ShellFederatedPermissionContext::GetApiPermissionStatus(
-    const url::Origin& rp_origin) {
+    const url::Origin& relying_party_embedder) {
   return base::FeatureList::IsEnabled(features::kFedCm)
              ? PermissionStatus::GRANTED
              : PermissionStatus::BLOCKED_VARIATIONS;
 }
 
 void ShellFederatedPermissionContext::RecordDismissAndEmbargo(
-    const url::Origin& rp_origin) {}
+    const url::Origin& relying_party_embedder) {}
 
 void ShellFederatedPermissionContext::RemoveEmbargoAndResetCounts(
-    const url::Origin& rp_origin) {}
+    const url::Origin& relying_party_embedder) {}
 
 bool ShellFederatedPermissionContext::HasSharingPermission(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
     const url::Origin& identity_provider,
     const std::string& account_id) {
   return sharing_permissions_.find(std::tuple(
-             relying_party.Serialize(), identity_provider.Serialize(),
+             relying_party_requester.Serialize(),
+             relying_party_embedder.Serialize(), identity_provider.Serialize(),
              account_id)) != sharing_permissions_.end();
 }
 
 void ShellFederatedPermissionContext::GrantSharingPermission(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
+    const url::Origin& relying_party_embedder,
     const url::Origin& identity_provider,
     const std::string& account_id) {
   sharing_permissions_.insert(std::tuple(
-      relying_party.Serialize(), identity_provider.Serialize(), account_id));
+      relying_party_requester.Serialize(), relying_party_embedder.Serialize(),
+      identity_provider.Serialize(), account_id));
 }
 
 // FederatedIdentityActiveSessionPermissionContextDelegate
 bool ShellFederatedPermissionContext::HasActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
   return active_sessions_.find(std::tuple(
-             relying_party.Serialize(), identity_provider.Serialize(),
+             relying_party_requester.Serialize(), identity_provider.Serialize(),
              account_identifier)) != active_sessions_.end();
 }
 
 void ShellFederatedPermissionContext::GrantActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
-  active_sessions_.insert(std::tuple(relying_party.Serialize(),
+  active_sessions_.insert(std::tuple(relying_party_requester.Serialize(),
                                      identity_provider.Serialize(),
                                      account_identifier));
 }
 
 void ShellFederatedPermissionContext::RevokeActiveSession(
-    const url::Origin& relying_party,
+    const url::Origin& relying_party_requester,
     const url::Origin& identity_provider,
     const std::string& account_identifier) {
-  active_sessions_.erase(std::tuple(relying_party.Serialize(),
+  active_sessions_.erase(std::tuple(relying_party_requester.Serialize(),
                                     identity_provider.Serialize(),
                                     account_identifier));
 }
diff --git a/content/shell/browser/shell_federated_permission_context.h b/content/shell/browser/shell_federated_permission_context.h
index 91eb23c..c6f5e54 100644
--- a/content/shell/browser/shell_federated_permission_context.h
+++ b/content/shell/browser/shell_federated_permission_context.h
@@ -28,38 +28,42 @@
 
   // FederatedIdentityApiPermissionContextDelegate
   content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus
-  GetApiPermissionStatus(const url::Origin& rp_origin) override;
-  void RecordDismissAndEmbargo(const url::Origin& rp_origin) override;
-  void RemoveEmbargoAndResetCounts(const url::Origin& rp_origin) override;
+  GetApiPermissionStatus(const url::Origin& relying_party_embedder) override;
+  void RecordDismissAndEmbargo(
+      const url::Origin& relying_party_embedder) override;
+  void RemoveEmbargoAndResetCounts(
+      const url::Origin& relying_party_embedder) override;
 
   // FederatedIdentitySharingPermissionContextDelegate
-  bool HasSharingPermission(const url::Origin& relying_party,
+  bool HasSharingPermission(const url::Origin& relying_party_requester,
+                            const url::Origin& relying_party_embedder,
                             const url::Origin& identity_provider,
                             const std::string& account_id) override;
-  void GrantSharingPermission(const url::Origin& relying_party,
+  void GrantSharingPermission(const url::Origin& relying_party_requester,
+                              const url::Origin& relying_party_embedder,
                               const url::Origin& identity_provider,
                               const std::string& account_id) override;
 
   // FederatedIdentityActiveSessionPermissionContextDelegate
-  bool HasActiveSession(const url::Origin& relying_party,
+  bool HasActiveSession(const url::Origin& relying_party_requester,
                         const url::Origin& identity_provider,
                         const std::string& account_identifier) override;
-  void GrantActiveSession(const url::Origin& relying_party,
+  void GrantActiveSession(const url::Origin& relying_party_requester,
                           const url::Origin& identity_provider,
                           const std::string& account_identifier) override;
-  void RevokeActiveSession(const url::Origin& relying_party,
+  void RevokeActiveSession(const url::Origin& relying_party_requester,
                            const url::Origin& identity_provider,
                            const std::string& account_identifier) override;
 
   bool ShouldCompleteRequestImmediately() const override;
 
  private:
-  // Pairs of <RP, IDP>
+  // Pairs of <RP embedder, IDP>
   std::set<std::pair<std::string, std::string>> request_permissions_;
-  // Tuples of <RP, IDP, Account>
-  std::set<std::tuple<std::string, std::string, std::string>>
+  // Tuples of <RP requester, RP embedder, IDP, Account>
+  std::set<std::tuple<std::string, std::string, std::string, std::string>>
       sharing_permissions_;
-  // Tuples of <RP, IDP, Account>
+  // Tuples of <RP requester, IDP, Account>
   std::set<std::tuple<std::string, std::string, std::string>> active_sessions_;
 };
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index e20dd32..4330e25 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -695,6 +695,7 @@
     sources += [
       "../browser/renderer_host/test_render_widget_host_view_mac_factory.h",
       "../browser/renderer_host/test_render_widget_host_view_mac_factory.mm",
+      "../public/test/browser_test_utils_mac.mm",
       "../public/test/text_input_test_utils_mac.mm",
     ]
     deps += [
@@ -1212,6 +1213,7 @@
     "../browser/accessibility/site_per_process_accessibility_browsertest.cc",
     "../browser/accessibility/snapshot_ax_tree_browsertest.cc",
     "../browser/accessibility/touch_accessibility_aura_browsertest.cc",
+    "../browser/aggregation_service/aggregation_service_internals_browsertest.cc",
     "../browser/attribution_reporting/attribution_internals_browsertest.cc",
     "../browser/attribution_reporting/attribution_src_browsertest.cc",
     "../browser/attribution_reporting/attributions_browsertest.cc",
diff --git a/device/bluetooth/floss/floss_dbus_client.cc b/device/bluetooth/floss/floss_dbus_client.cc
index 7c4dbb9..db2d90e 100644
--- a/device/bluetooth/floss/floss_dbus_client.cc
+++ b/device/bluetooth/floss/floss_dbus_client.cc
@@ -423,39 +423,12 @@
 template <>
 bool FlossDBusClient::ReadDBusParam(dbus::MessageReader* reader,
                                     FlossDeviceId* device) {
-  // Parse a FlossDeviceId from a message.
-  //
-  // The format:
-  // array (
-  //  dict_entry (
-  //    key "name"
-  //    variant string("")
-  //  )
-  //  dict entry (
-  //    key "address"
-  //    variant string("")
-  //  )
-  // )
+  static StructReader<FlossDeviceId> struct_reader({
+      {"address", CreateFieldReader(&FlossDeviceId::address)},
+      {"name", CreateFieldReader(&FlossDeviceId::name)},
+  });
 
-  dbus::MessageReader array(nullptr);
-  dbus::MessageReader dict(nullptr);
-  bool found_name = false;
-  bool found_address = false;
-
-  if (reader->PopArray(&array)) {
-    while (array.PopDictEntry(&dict)) {
-      std::string key;
-      dict.PopString(&key);
-
-      if (key == kDeviceIdNameKey) {
-        found_name = dict.PopVariantOfString(&device->name);
-      } else if (key == kDeviceIdAddressKey) {
-        found_address = dict.PopVariantOfString(&device->address);
-      }
-    }
-  }
-
-  return found_name && found_address;
+  return struct_reader.ReadDBusParam(reader, device);
 }
 
 template <>
diff --git a/device/bluetooth/floss/floss_dbus_client.h b/device/bluetooth/floss/floss_dbus_client.h
index de8a1ad..edaeb0da 100644
--- a/device/bluetooth/floss/floss_dbus_client.h
+++ b/device/bluetooth/floss/floss_dbus_client.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/containers/contains.h"
 #include "base/logging.h"
 #include "base/types/expected.h"
 #include "dbus/bus.h"
@@ -374,6 +375,84 @@
     return ReadDBusParam(reader, first) && ReadAllDBusParams(reader, args...);
   }
 
+  template <typename T>
+  using FieldReader = std::function<bool(dbus::MessageReader*, T* data)>;
+
+  // Useful to generate D-Bus reader of a struct. Example usage:
+  //
+  // template <>
+  // bool FlossDBusClient::ReadDBusParam(dbus::MessageReader* reader,
+  //                                     ScanResult* scan_result) {
+  //   static StructReader<ScanResult> struct_reader({
+  //       {"address", CreateFieldReader(&ScanResult::address)},
+  //       {"addr_type", CreateFieldReader(&ScanResult::addr_type)},
+  //       <just define more fields here>
+  //   });
+  //   return struct_reader.ReadDBusParam(reader, scan_result);
+  // }
+  template <typename T>
+  class StructReader {
+   private:
+    std::unordered_map<std::string, FieldReader<T>> fields_;
+
+   public:
+    explicit StructReader(
+        std::vector<std::pair<std::string, FieldReader<T>>> fields) {
+      for (auto const& kv : fields) {
+        fields_.insert(kv);
+      }
+    };
+
+    bool ReadDBusParam(dbus::MessageReader* reader, T* data) {
+      // Keep track of parsed fields to detect missing and duplicate fields.
+      std::unordered_set<std::string> parsed_fields;
+
+      dbus::MessageReader array_reader(nullptr);
+      if (!reader->PopArray(&array_reader))
+        return false;
+
+      // For each dictionary entry
+      while (array_reader.HasMoreData()) {
+        dbus::MessageReader entry_reader(nullptr);
+        if (!array_reader.PopDictEntry(&entry_reader))
+          return false;
+
+        std::string key;
+        if (!entry_reader.PopString(&key))
+          return false;
+
+        if (base::Contains(fields_, key)) {
+          dbus::MessageReader variant_reader(nullptr);
+          entry_reader.PopVariant(&variant_reader);
+
+          if (!fields_[key](&variant_reader, data))
+            return false;
+
+          if (base::Contains(parsed_fields, key))
+            return false;
+
+          parsed_fields.insert(key);
+        } else {
+          DBusTypeInfo type_info = GetDBusTypeInfo<T>();
+          VLOG(3) << "Does not know how to read field " << type_info.type_name
+                  << "." << key;
+        }
+      }
+
+      // All defined fields are required.
+      return parsed_fields.size() == fields_.size();
+    }
+  };
+
+  // S is the type of the container struct.
+  // T is the type of the field.
+  template <typename S, typename T>
+  static FieldReader<S> CreateFieldReader(T S::*field) {
+    return [field](dbus::MessageReader* reader, S* container) -> bool {
+      return FlossDBusClient::ReadDBusParam(reader, &(container->*field));
+    };
+  }
+
   template <typename R, typename... Args>
   void CallMethod(ResponseCallback<R> callback,
                   dbus::Bus* bus,
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index cfa38f1..7168afc8 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -433,30 +433,28 @@
 }
 
 void ExtensionPrefs::MakePathsRelative() {
-  const base::DictionaryValue* dict = &base::Value::AsDictionaryValue(
-      *prefs_->GetDictionary(pref_names::kExtensions));
-  if (!dict || dict->DictEmpty())
+  const base::Value::Dict& dict = prefs_->GetValueDict(pref_names::kExtensions);
+  if (dict.empty())
     return;
 
   // Collect all extensions ids with absolute paths in |absolute_keys|.
   std::set<std::string> absolute_keys;
-  for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) {
-    const base::DictionaryValue* extension_dict = NULL;
-    if (!i.value().GetAsDictionary(&extension_dict))
+  for (const auto [extension_id, extension_item] : dict) {
+    if (!extension_item.is_dict())
       continue;
-    absl::optional<int> location_value =
-        extension_dict->FindIntKey(kPrefLocation);
+    const base::Value::Dict& extension_dict = extension_item.GetDict();
+    absl::optional<int> location_value = extension_dict.FindInt(kPrefLocation);
     if (location_value && Manifest::IsUnpackedLocation(
                               static_cast<ManifestLocation>(*location_value))) {
       // Unpacked extensions can have absolute paths.
       continue;
     }
-    std::string path_string;
-    if (!extension_dict->GetString(kPrefPath, &path_string))
+    const std::string* path_string = extension_dict.FindString(kPrefPath);
+    if (!path_string)
       continue;
-    base::FilePath path = base::FilePath::FromUTF8Unsafe(path_string);
+    base::FilePath path = base::FilePath::FromUTF8Unsafe(*path_string);
     if (path.IsAbsolute())
-      absolute_keys.insert(i.key());
+      absolute_keys.insert(extension_id);
   }
   if (absolute_keys.empty())
     return;
@@ -487,11 +485,13 @@
   if (extension_id.empty()) {
     return nullptr;
   }
-  const base::Value* extensions =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  if (!extensions)
+  // TODO (https://crbug.com/1342019) This should call
+  // `PrefService::GetValueDict`, which will in turn require the return type to
+  // be `base::Value::Dict`.
+  const base::Value& extensions = prefs_->GetValue(pref_names::kExtensions);
+  if (!extensions.is_dict())
     return nullptr;
-  const base::Value* extension_dict = extensions->FindDictPath(extension_id);
+  const base::Value* extension_dict = extensions.FindDictPath(extension_id);
   return extension_dict ? &base::Value::AsDictionaryValue(*extension_dict)
                         : nullptr;
 }
@@ -1163,8 +1163,11 @@
 }
 
 base::Time ExtensionPrefs::BlocklistLastPingDay() const {
+  // TODO (https://crbug.com/1342019) This should call
+  // `PrefService::GetValueDict`, which will in turn require the return type to
+  // be `base::Value::Dict`.
   return ReadTime(&base::Value::AsDictionaryValue(
-                      *prefs_->GetDictionary(kExtensionsBlocklistUpdate)),
+                      prefs_->GetValue(kExtensionsBlocklistUpdate)),
                   kLastPingDay);
 }
 
@@ -1492,9 +1495,9 @@
 
 std::unique_ptr<ExtensionInfo> ExtensionPrefs::GetInstalledInfoHelper(
     const std::string& extension_id,
-    const base::Value* extension,
+    const base::Value::Dict& extension,
     bool include_component_extensions) const {
-  absl::optional<int> location_value = extension->FindIntKey(kPrefLocation);
+  absl::optional<int> location_value = extension.FindInt(kPrefLocation);
   if (!location_value)
     return nullptr;
 
@@ -1518,13 +1521,14 @@
     return nullptr;
   }
 
-  const base::Value* manifest = extension->FindDictKey(kPrefManifest);
-  if (!Manifest::IsUnpackedLocation(location) && !manifest) {
+  const base::Value* manifest = extension.Find(kPrefManifest);
+  if (!Manifest::IsUnpackedLocation(location) &&
+      !(manifest && manifest->is_dict())) {
     LOG(WARNING) << "Missing manifest for extension " << extension_id;
     // Just a warning for now.
   }
 
-  const std::string* path = extension->FindStringPath(kPrefPath);
+  const std::string* path = extension.FindString(kPrefPath);
   if (!path)
     return nullptr;
   base::FilePath file_path = base::FilePath::FromUTF8Unsafe(*path);
@@ -1533,7 +1537,9 @@
   if (!file_path.IsAbsolute())
     file_path = install_directory_.Append(file_path);
   const base::DictionaryValue* manifest_dict =
-      manifest ? &base::Value::AsDictionaryValue(*manifest) : nullptr;
+      (manifest && manifest->is_dict())
+          ? &base::Value::AsDictionaryValue(*manifest)
+          : nullptr;
   return std::make_unique<ExtensionInfo>(manifest_dict, extension_id, file_path,
                                          location);
 }
@@ -1541,21 +1547,19 @@
 std::unique_ptr<ExtensionInfo> ExtensionPrefs::GetInstalledExtensionInfo(
     const std::string& extension_id,
     bool include_component_extensions) const {
-  const base::Value* extensions =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  if (!extensions)
-    return nullptr;
-  const base::Value* ext = extensions->FindDictKey(extension_id);
+  const base::Value::Dict& extensions =
+      prefs_->GetValueDict(pref_names::kExtensions);
+  const base::Value::Dict* ext = extensions.FindDict(extension_id);
   if (!ext)
     return nullptr;
 
-  absl::optional<int> state_value = ext->FindIntKey(kPrefState);
+  absl::optional<int> state_value = ext->FindInt(kPrefState);
   // TODO(devlin): Remove this once all clients are updated with
   // MigrateToNewExternalUninstallPref().
   if (state_value == Extension::DEPRECATED_EXTERNAL_EXTENSION_UNINSTALLED)
     return nullptr;
 
-  return GetInstalledInfoHelper(extension_id, ext,
+  return GetInstalledInfoHelper(extension_id, *ext,
                                 include_component_extensions);
 }
 
@@ -1564,9 +1568,9 @@
     bool include_component_extensions) const {
   std::unique_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
 
-  const base::Value* extensions =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  for (const auto extension_id : extensions->DictItems()) {
+  const base::Value::Dict& extensions =
+      prefs_->GetValueDict(pref_names::kExtensions);
+  for (const auto extension_id : extensions) {
     if (!crx_file::id_util::IdIsValid(extension_id.first))
       continue;
 
@@ -1663,7 +1667,7 @@
   if (!ext)
     return nullptr;
 
-  return GetInstalledInfoHelper(extension_id, ext,
+  return GetInstalledInfoHelper(extension_id, ext->GetDict(),
                                 /*include_component_extensions = */ false);
 }
 
@@ -1689,14 +1693,13 @@
 ExtensionPrefs::GetAllDelayedInstallInfo() const {
   std::unique_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
 
-  const base::Value* extensions =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  for (const auto extension_id : extensions->DictItems()) {
-    if (!crx_file::id_util::IdIsValid(extension_id.first))
+  const base::Value::Dict& extensions =
+      prefs_->GetValueDict(pref_names::kExtensions);
+  for (const auto [extension_id, _] : extensions) {
+    if (!crx_file::id_util::IdIsValid(extension_id))
       continue;
 
-    std::unique_ptr<ExtensionInfo> info =
-        GetDelayedInstallInfo(extension_id.first);
+    std::unique_ptr<ExtensionInfo> info = GetDelayedInstallInfo(extension_id);
     if (info)
       extensions_info->push_back(std::move(info));
   }
@@ -1805,8 +1808,8 @@
 }
 
 void ExtensionPrefs::ClearLastLaunchTimes() {
-  const base::Value* dict = prefs_->GetDictionary(pref_names::kExtensions);
-  if (!dict || dict->DictEmpty())
+  const base::Value::Dict& dict = prefs_->GetValueDict(pref_names::kExtensions);
+  if (dict.empty())
     return;
 
   // Collect all the keys to remove the last launched preference from.
@@ -1906,7 +1909,10 @@
     const PrefMap& pref) const {
   DCHECK_EQ(PrefScope::kProfile, pref.scope);
   DCHECK_EQ(PrefType::kDictionary, pref.type);
-  return &base::Value::AsDictionaryValue(*prefs_->GetDictionary(pref.name));
+  // TODO (https://crbug.com/1342019) This should call
+  // `PrefService::GetValueDict`, which will in turn require the return type to
+  // be `base::Value::Dict`.
+  return &base::Value::AsDictionaryValue(prefs_->GetValue(pref.name));
 }
 
 std::unique_ptr<prefs::ScopedDictionaryPrefUpdate>
@@ -2021,8 +2027,10 @@
 }
 
 const base::DictionaryValue* ExtensionPrefs::GetInstallSignature() const {
-  return &base::Value::AsDictionaryValue(
-      *prefs_->GetDictionary(kInstallSignature));
+  // TODO (https://crbug.com/1342019) This should call
+  // `PrefService::GetValueDict`, which will in turn require the return type to
+  // be `base::Value::Dict`.
+  return &base::Value::AsDictionaryValue(prefs_->GetValue(kInstallSignature));
 }
 
 void ExtensionPrefs::SetInstallSignature(
@@ -2454,14 +2462,14 @@
     return;
   std::string key = extension_id + "." + scope_string;
 
-  const base::Value* source_dict =
-      pref_service()->GetDictionary(pref_names::kExtensions);
+  const base::Value::Dict& source_dict =
+      pref_service()->GetValueDict(pref_names::kExtensions);
 
-  const base::Value* preferences = source_dict->FindDictPath(key);
+  const base::Value::Dict* preferences = source_dict.FindDictByDottedPath(key);
   if (!preferences)
     return;
 
-  for (auto pair : preferences->DictItems()) {
+  for (auto pair : *preferences) {
     extension_pref_value_map_->SetExtensionPref(extension_id, pair.first, scope,
                                                 pair.second.Clone());
   }
@@ -2549,16 +2557,15 @@
 }
 
 void ExtensionPrefs::MigrateYoutubeOffBookmarkApps() {
-  const base::Value* extensions_dictionary =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  DCHECK(extensions_dictionary->is_dict());
-  const base::Value* youtube_dictionary =
-      extensions_dictionary->FindDictPath(extension_misc::kYoutubeAppId);
+  const base::Value::Dict& extensions_dictionary =
+      prefs_->GetValueDict(pref_names::kExtensions);
+  const base::Value::Dict* youtube_dictionary =
+      extensions_dictionary.FindDict(extension_misc::kYoutubeAppId);
   if (!youtube_dictionary) {
     return;
   }
   int creation_flags =
-      youtube_dictionary->FindIntKey(kPrefCreationFlags).value_or(0);
+      youtube_dictionary->FindInt(kPrefCreationFlags).value_or(0);
   if ((creation_flags & Extension::FROM_BOOKMARK) == 0)
     return;
   ScopedExtensionPrefUpdate update(prefs_, extension_misc::kYoutubeAppId);
@@ -2567,9 +2574,8 @@
 }
 
 void ExtensionPrefs::MigrateObsoleteExtensionPrefs() {
-  const base::Value* extensions_dictionary =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  DCHECK(extensions_dictionary->is_dict());
+  const base::Value::Dict& extensions_dictionary =
+      prefs_->GetValueDict(pref_names::kExtensions);
 
   // Please clean this list up periodically, removing any entries added more
   // than a year ago (with the exception of the testing key).
@@ -2580,7 +2586,7 @@
       // TODO(crbug.com/1015619): Remove 2023-05. Incorrect spelling from 2013.
       "id_mapping_dictioanry"};
 
-  for (auto key_value : extensions_dictionary->DictItems()) {
+  for (auto key_value : extensions_dictionary) {
     if (!crx_file::id_util::IdIsValid(key_value.first))
       continue;
     ScopedExtensionPrefUpdate update(prefs_, key_value.first);
@@ -2635,13 +2641,11 @@
 }
 
 void ExtensionPrefs::MigrateToNewExternalUninstallPref() {
-  const base::Value* extensions =
-      prefs_->GetDictionary(pref_names::kExtensions);
-  if (!extensions)
-    return;
+  const base::Value::Dict& extensions =
+      prefs_->GetValueDict(pref_names::kExtensions);
 
   std::vector<std::string> uninstalled_ids;
-  for (auto item : extensions->DictItems()) {
+  for (auto item : extensions) {
     if (!crx_file::id_util::IdIsValid(item.first) || !item.second.is_dict()) {
       continue;
     }
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index e98d4e5..01e889ff1 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -818,7 +818,7 @@
   // |extension| dictionary.
   std::unique_ptr<ExtensionInfo> GetInstalledInfoHelper(
       const std::string& extension_id,
-      const base::Value* extension,
+      const base::Value::Dict& extension,
       bool include_component_extensions) const;
 
   // Read the boolean preference entry and return true if the preference exists
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
index 4b663c1..16ec6ec 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
@@ -549,7 +549,7 @@
 }
 
 ui::AXNode* AutomationAXTreeWrapper::GetNodeFromTree(
-    const ui::AXTreeID tree_id,
+    const ui::AXTreeID& tree_id,
     const ui::AXNodeID node_id) const {
   AutomationAXTreeWrapper* tree_wrapper =
       owner_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.h b/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
index a790d761..c2239817 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
@@ -107,7 +107,7 @@
   bool IsTreeIgnored();
 
   // AXTreeManager overrides.
-  ui::AXNode* GetNodeFromTree(const ui::AXTreeID tree_id,
+  ui::AXNode* GetNodeFromTree(const ui::AXTreeID& tree_id,
                               const ui::AXNodeID node_id) const override;
   ui::AXNode* GetNodeFromTree(const ui::AXNodeID node_id) const override;
   ui::AXTreeID GetParentTreeID() const override;
diff --git a/extensions/renderer/script_injection.cc b/extensions/renderer/script_injection.cc
index 5fc0aba..4e98e861 100644
--- a/extensions/renderer/script_injection.cc
+++ b/extensions/renderer/script_injection.cc
@@ -312,6 +312,17 @@
       should_execute_asynchronously
           ? blink::mojom::EvaluationTiming::kAsynchronous
           : blink::mojom::EvaluationTiming::kSynchronous;
+  // Historically, when `kSynchronous` is used, we did not block the load
+  // event. We preserve this here for now to ensure we don't break anything.
+  // However, this is probably wrong, since the execution context could be
+  // paused, triggering an asynchronous execution of the script even when
+  // `kSynchronous` is used.
+  // TODO(crbug.com/1354639): Change this to block the load event, even with
+  // `kSynchronous`.
+  blink::mojom::LoadEventBlockingOption blocking_option =
+      should_execute_asynchronously
+          ? blink::mojom::LoadEventBlockingOption::kBlock
+          : blink::mojom::LoadEventBlockingOption::kDoNotBlock;
 
   int32_t world_id = blink::kMainDOMWorldId;
   switch (injector_->GetExecutionWorld()) {
@@ -328,7 +339,7 @@
   }
   render_frame_->GetWebFrame()->RequestExecuteScript(
       world_id, sources, injector_->IsUserGesture(), execution_option,
-      blink::mojom::LoadEventBlockingOption::kBlock,
+      blocking_option,
       base::BindOnce(&ScriptInjection::OnJsInjectionCompleted,
                      weak_ptr_factory_.GetWeakPtr()),
       blink::BackForwardCacheAware::kPossiblyDisallow,
diff --git a/fuchsia_web/webengine/browser/web_engine_browser_main_parts.cc b/fuchsia_web/webengine/browser/web_engine_browser_main_parts.cc
index 4d43477..e6cfe6aa 100644
--- a/fuchsia_web/webengine/browser/web_engine_browser_main_parts.cc
+++ b/fuchsia_web/webengine/browser/web_engine_browser_main_parts.cc
@@ -303,6 +303,10 @@
   // Main loop should quit only after all Context instances have been destroyed.
   DCHECK_EQ(context_bindings_.size(), 0u);
 
+  // FrameHost channels may still be active and contain live Frames. Close them
+  // here so that they are torn-down before their dependent resources.
+  frame_host_bindings_.CloseAll();
+
   // These resources must be freed while a MessageLoop is still available, so
   // that they may post cleanup tasks during teardown.
   // NOTE: Objects are destroyed in the reverse order of their creation.
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index caf3a0d3..4d4f6433 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -56,7 +56,7 @@
 // Used to limit GL version to 2.0 for skia raster and compositing.
 const base::Feature kUseGles2ForOopR {
   "UseGles2ForOopR",
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
       base::FEATURE_DISABLED_BY_DEFAULT
 #else
       base::FEATURE_ENABLED_BY_DEFAULT
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 6c7be3e..7d63690 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -4343,6 +4343,7 @@
       ref_regexp_exclude: "refs/branch-heads/5060"
       ref_regexp_exclude: "refs/branch-heads/5112"
       ref_regexp_exclude: "refs/branch-heads/5195"
+      ref_regexp_exclude: "refs/branch-heads/5249"
     }
   }
   verifiers {
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index ecbbd08..70562568 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -50042,7 +50042,6 @@
     builders {
       name: "runner"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:runner"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -50072,7 +50071,7 @@
         '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "reviver/chromium/runner"'
         '}'
-      service_account: "reviver-builder@chops-service-accounts.iam.gserviceaccount.com"
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
         key: "luci.recipes.use_python3"
         value: 100
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 741c122b..2f501fa 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -873,6 +873,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -1633,6 +1637,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -2117,6 +2125,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -2476,6 +2488,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -3173,6 +3189,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -3513,6 +3533,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -4116,6 +4140,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -4539,6 +4567,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -4948,6 +4980,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -5395,6 +5431,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -5959,6 +5999,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -6428,6 +6472,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -6809,6 +6857,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -7178,6 +7230,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -7671,6 +7727,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -8563,6 +8623,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -8994,6 +9058,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -9399,6 +9467,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -9799,6 +9871,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -10436,6 +10512,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -10850,6 +10930,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -11279,6 +11363,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -11733,6 +11821,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -12097,6 +12189,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -12551,6 +12647,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -12906,6 +13006,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -13285,6 +13389,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -13764,6 +13872,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -14138,6 +14250,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -14553,6 +14669,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -14947,6 +15067,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -15352,6 +15476,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -15690,6 +15818,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
@@ -16042,6 +16174,10 @@
         url: "/p/chromium-m105/g/main/console"
       }
       links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
         text: "trunk"
         url: "/p/chromium/g/main/console"
         alt: "Trunk (ToT) console"
diff --git a/infra/config/generated/luci/realms.cfg b/infra/config/generated/luci/realms.cfg
index d6ba620..7c2ce5cc 100644
--- a/infra/config/generated/luci/realms.cfg
+++ b/infra/config/generated/luci/realms.cfg
@@ -388,6 +388,7 @@
     principals: "project:chromium-m103"
     principals: "project:chromium-m104"
     principals: "project:chromium-m105"
+    principals: "project:chromium-m106"
     principals: "project:chromium-m96"
     principals: "project:chromium-m97"
     principals: "user:chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -441,6 +442,7 @@
     principals: "project:chromium-m103"
     principals: "project:chromium-m104"
     principals: "project:chromium-m105"
+    principals: "project:chromium-m106"
     principals: "project:chromium-m96"
     principals: "project:chromium-m97"
     principals: "user:chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
@@ -490,6 +492,7 @@
   name: "reviver"
   bindings {
     role: "role/buildbucket.builderServiceAccount"
+    principals: "user:chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     principals: "user:reviver-builder@chops-service-accounts.iam.gserviceaccount.com"
   }
   bindings {
diff --git a/infra/config/milestones.json b/infra/config/milestones.json
index 4c77587..9cd9d5e 100644
--- a/infra/config/milestones.json
+++ b/infra/config/milestones.json
@@ -33,5 +33,10 @@
         "name": "m105",
         "project": "chromium-m105",
         "ref": "refs/branch-heads/5195"
+    },
+    "106": {
+        "name": "m106",
+        "project": "chromium-m106",
+        "ref": "refs/branch-heads/5249"
     }
 }
diff --git a/infra/config/subprojects/reviver/reviver.star b/infra/config/subprojects/reviver/reviver.star
index 4c6126f..fe97b8b 100644
--- a/infra/config/subprojects/reviver/reviver.star
+++ b/infra/config/subprojects/reviver/reviver.star
@@ -83,6 +83,10 @@
 builder(
     name = "runner",
     executable = "recipe:reviver/chromium/runner",
+    auto_builder_dimension = False,
     # TODO(crbug/1346396) Figure out what machines the runnner should run on
     pool = ci.DEFAULT_POOL,
+    # TODO(crbug/1346396) Remove this once the reviver service account has
+    # necessary permissions
+    service_account = ci.DEFAULT_SERVICE_ACCOUNT,
 )
diff --git a/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
index 279c8ec3..b59eadf 100644
--- a/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
+++ b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
@@ -105,7 +105,8 @@
 
   void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
                          const bookmarks::BookmarkNode* parent,
-                         size_t index) override {
+                         size_t index,
+                         bool added_by_user) override {
     [owner_ refreshNodeInIndex:parent->children()[index].get() initial:NO];
   }
 
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 025eea4..9cfd85b 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -843,6 +843,16 @@
      flag_descriptions::kUseLensToSearchForImageName,
      flag_descriptions::kUseLensToSearchForImageDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kUseLensToSearchForImage)},
+    {"enable-lens-in-home-screen-widget",
+     flag_descriptions::kEnableLensInHomeScreenWidgetName,
+     flag_descriptions::kEnableLensInHomeScreenWidgetDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kEnableLensInHomeScreenWidget)},
+    {"enable-lens-in-keyboard", flag_descriptions::kEnableLensInKeyboardName,
+     flag_descriptions::kEnableLensInKeyboardDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kEnableLensInKeyboard)},
+    {"enable-lens-in-ntp", flag_descriptions::kEnableLensInNTPName,
+     flag_descriptions::kEnableLensInNTPDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kEnableLensInNTP)},
     {"use-load-simulated-request-for-error-page-navigation",
      flag_descriptions::kUseLoadSimulatedRequestForOfflinePageName,
      flag_descriptions::kUseLoadSimulatedRequestForOfflinePageDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index c4dbe6fd..581c63a3 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -714,6 +714,26 @@
     "When enabled, use Lens to search for images from the long press context "
     "menu when Google is the selected search engine.";
 
+const char kEnableLensInHomeScreenWidgetName[] =
+    "Enable Google Lens in the Home Screen Widget";
+const char kEnableLensInHomeScreenWidgetDescription[] =
+    "When enabled, use Lens to search for images from your device camera "
+    "menu when Google is the selected search engine, accessible from the"
+    "home screen widget.";
+
+const char kEnableLensInKeyboardName[] =
+    "Enable Google Lens in the Omnibox Keyboard";
+const char kEnableLensInKeyboardDescription[] =
+    "When enabled, use Lens to search for images from your device camera "
+    "menu when Google is the selected search engine, accessible from the"
+    "omnibox keyboard.";
+
+const char kEnableLensInNTPName[] = "Enable Google Lens in the NTP";
+const char kEnableLensInNTPDescription[] =
+    "When enabled, use Lens to search for images from your device camera "
+    "menu when Google is the selected search engine, accessible from the"
+    "new tab page.";
+
 const char kUseLoadSimulatedRequestForOfflinePageName[] =
     "Use loadSimulatedRequest:responseHTMLString: when displaying offline "
     "pages";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index a1a916b..7ff68c87 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -646,6 +646,21 @@
 extern const char kUseLensToSearchForImageName[];
 extern const char kUseLensToSearchForImageDescription[];
 
+// Title and description for the flag to enable using Lens to search using
+// the device camera from the home screen widget.
+extern const char kEnableLensInHomeScreenWidgetName[];
+extern const char kEnableLensInHomeScreenWidgetDescription[];
+
+// Title and description for the flag to enable using Lens to search using
+// the device camera from the keyboard.
+extern const char kEnableLensInKeyboardName[];
+extern const char kEnableLensInKeyboardDescription[];
+
+// Title and description for the flag to enable using Lens to search using
+// the device camera from the ntp.
+extern const char kEnableLensInNTPName[];
+extern const char kEnableLensInNTPDescription[];
+
 // Title and description for the flag to enable using the
 // loadSimulatedRequest:responseHTMLString: API for displaying error pages in
 // CRWWKNavigationHandler.
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent.h b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent.h
index e6ec3f7..e8a867c8 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent.h
@@ -26,7 +26,7 @@
 
   // Adds an InfobarInteractionHandler to make model-layer updates for
   // interactions with infobars.  An OverlayCallbackInstaller will be created
-  // from each added handler for each InfobarOverlayType.  |interaction_handler|
+  // from each added handler for each InfobarOverlayType.  `interaction_handler`
   // must not be null.  Only one interaction handler for a given InfobarType
   // can be added.
   void AddInfobarInteractionHandler(
@@ -40,7 +40,7 @@
   explicit InfobarOverlayBrowserAgent(Browser* browser);
 
   // Returns the interaction handler for the InfobarType of the infobar used to
-  // configure |request|, or nullptr if |request| is not supported.
+  // configure `request`, or nullptr if `request` is not supported.
   InfobarInteractionHandler* GetInteractionHandler(OverlayRequest* request);
 
   // Helper object that notifies interaction handler of changes in infobar UI
@@ -53,7 +53,7 @@
 
    private:
     // Notifies the BrowserAgent's interaction handler that the visibility of
-    // |request|'s UI has changed.
+    // `request`'s UI has changed.
     void OverlayVisibilityChanged(OverlayRequest* request, bool visible);
 
     // OverlayPresenterObserver:
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_unittest.mm b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_unittest.mm
index b61d71c..f7fae38a 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_unittest.mm
@@ -101,9 +101,9 @@
   }
 
   // Creates the OverlayRequestCallbackInstaller to return from
-  // CreateInstaller() for the mock interaction handler for |overlay_type|.
+  // CreateInstaller() for the mock interaction handler for `overlay_type`.
   // Returned installers forwards callbacks to the receivers in
-  // |mock_callback_receivers_|.
+  // `mock_callback_receivers_`.
   std::unique_ptr<FakeOverlayRequestCallbackInstaller> CreateInstaller(
       InfobarOverlayType overlay_type) {
     std::unique_ptr<FakeOverlayRequestCallbackInstaller> installer =
@@ -168,7 +168,7 @@
   EXPECT_CALL(*mock_handler(),
               InfobarVisibilityChanged(&infobar_, /*visible=*/true));
   queue()->AddRequest(std::move(added_request));
-  // Verify that dispatched responses sent through |request|'s callback manager
+  // Verify that dispatched responses sent through `request`'s callback manager
   // are received by the expected receiver.
   EXPECT_CALL(*mock_callback_receiver(),
               DispatchCallback(request, DispatchInfo::ResponseSupport()));
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h
index 2587034..9eab5d2 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h
@@ -7,7 +7,7 @@
 
 class Browser;
 
-// Attaches browser agents to |browser| that manage the model changes for
+// Attaches browser agents to `browser` that manage the model changes for
 // infobar UI presented via OverlayPresenter.
 void AttachInfobarOverlayBrowserAgent(Browser* browser);
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_banner_interaction_handler.h
index 5db5bc40..5dbbfb8e 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_banner_interaction_handler.h
@@ -26,7 +26,7 @@
   void BannerDismissedByUser(InfoBarIOS* infobar) override;
 
  private:
-  // Returns the SaveAddressProfile delegate from |infobar|.
+  // Returns the SaveAddressProfile delegate from `infobar`.
   autofill::AutofillSaveUpdateAddressProfileDelegateIOS* GetInfobarDelegate(
       InfoBarIOS* infobar);
 };
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_interaction_handler.h
index ace6325f..e2c2773 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_interaction_handler.h
@@ -41,7 +41,7 @@
   std::unique_ptr<InfobarModalOverlayRequestCallbackInstaller>
   CreateModalInstaller() override;
 
-  // Returns the SaveAddressProfile delegate from |infobar|.
+  // Returns the SaveAddressProfile delegate from `infobar`.
   autofill::AutofillSaveUpdateAddressProfileDelegateIOS* GetInfoBarDelegate(
       InfoBarIOS* infobar);
 };
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_overlay_request_callback_installer.h
index c9fd884..7185081 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/autofill_address_profile/save_address_profile_infobar_modal_overlay_request_callback_installer.h
@@ -16,22 +16,22 @@
     : public InfobarModalOverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks that forward
-  // interaction events to |interaction_handler|.
+  // interaction events to `interaction_handler`.
   explicit SaveAddressProfileInfobarModalOverlayRequestCallbackInstaller(
       SaveAddressProfileInfobarModalInteractionHandler* interaction_handler);
   ~SaveAddressProfileInfobarModalOverlayRequestCallbackInstaller() override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // save_address_profile_infobar_modal_responses::EditedProfileSaveAction.
   void SaveEditedProfileDetailsCallback(OverlayRequest* request,
                                         OverlayResponse* response);
 
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // save_address_profile_infobar_modal_responses::CancelViewAction.
   void CancelModalCallback(OverlayRequest* request, OverlayResponse* response);
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_interaction_handler.h
index 468603f..18ae5ff 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_interaction_handler.h
@@ -22,17 +22,17 @@
     : public InfobarInteractionHandler::Handler {
  public:
   // Constructor for a banner interaction handler that creates callback
-  // installers with |request_support|.
+  // installers with `request_support`.
   explicit InfobarBannerInteractionHandler(
       const OverlayRequestSupport* request_support);
   ~InfobarBannerInteractionHandler() override;
 
-  // Updates the model when the visibility of |infobar|'s banner is changed.
+  // Updates the model when the visibility of `infobar`'s banner is changed.
   virtual void BannerVisibilityChanged(InfoBarIOS* infobar, bool visible) {}
-  // Updates the model when the main button is tapped for |infobar|'s banner.
+  // Updates the model when the main button is tapped for `infobar`'s banner.
   virtual void MainButtonTapped(InfoBarIOS* infobar) {}
-  // Shows the modal when the modal button is tapped for |infobar|'s banner.
-  // |web_state| is the WebState associated with |infobar|'s InfoBarManager.
+  // Shows the modal when the modal button is tapped for `infobar`'s banner.
+  // `web_state` is the WebState associated with `infobar`'s InfoBarManager.
   virtual void ShowModalButtonTapped(InfoBarIOS* infobar,
                                      web::WebState* web_state);
   // Notifies the model that the upcoming dismissal is user-initiated (i.e.
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_overlay_request_callback_installer.h
index 30f6d9e..ea515da 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_overlay_request_callback_installer.h
@@ -16,8 +16,8 @@
     : public OverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks for OverlayRequests
-  // supported by |request_support| that forward interaction events to
-  // |interaction_handler|.
+  // supported by `request_support` that forward interaction events to
+  // `interaction_handler`.
   InfobarBannerOverlayRequestCallbackInstaller(
       const OverlayRequestSupport* request_support,
       InfobarBannerInteractionHandler* interaction_handler);
@@ -28,34 +28,34 @@
   void InstallCallbacksInternal(OverlayRequest* request) override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // InfobarBannerMainActionResponse.
   void MainActionButtonTapped(OverlayRequest* request,
                               OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // InfobarBannerShowModalResponse.
   void ShowModalButtonTapped(OverlayRequest* request,
                              OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // InfobarBannerUserInitiatedDismissalResponse.
   void BannerDismissedByUser(OverlayRequest* request,
                              OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // InfobarBannerRemoveInfobarResponse.
   void RemoveInfobar(OverlayRequest* request, OverlayResponse* response);
 
   // OverlayRequestCallbackInstaller:
   const OverlayRequestSupport* GetRequestSupport() const override;
 
-  // The request support for |interaction_handler_|.
+  // The request support for `interaction_handler_`.
   const OverlayRequestSupport* request_support_ = nullptr;
   // The handler for received responses.
   InfobarBannerInteractionHandler* interaction_handler_ = nullptr;
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_interaction_handler.h
index 5910e7a..81ccd805 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_interaction_handler.h
@@ -16,7 +16,7 @@
  public:
   ~InfobarModalInteractionHandler() override;
 
-  // Updates the model to perform the main action for |infobar|.
+  // Updates the model to perform the main action for `infobar`.
   virtual void PerformMainAction(InfoBarIOS* infobar) = 0;
 
  protected:
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_overlay_request_callback_installer.h
index 0a536d1..22c4623 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_modal_overlay_request_callback_installer.h
@@ -19,8 +19,8 @@
 
  protected:
   // Constructor for an instance that installs callbacks for OverlayRequests
-  // supported by |request_support| that forward interaction events to
-  // |interaction_handler|.
+  // supported by `request_support` that forward interaction events to
+  // `interaction_handler`.
   InfobarModalOverlayRequestCallbackInstaller(
       const OverlayRequestSupport* request_support,
       InfobarModalInteractionHandler* interaction_handler);
@@ -31,16 +31,16 @@
   void InstallCallbacksInternal(OverlayRequest* request) override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // InfobarModalMainActionResponse.
   void MainActionCallback(OverlayRequest* request, OverlayResponse* response);
 
   // OverlayRequestCallbackInstaller:
   const OverlayRequestSupport* GetRequestSupport() const override;
 
-  // The request support for |interaction_handler_|.
+  // The request support for `interaction_handler_`.
   const OverlayRequestSupport* request_support_ = nullptr;
   // The handler for received responses.
   InfobarModalInteractionHandler* interaction_handler_ = nullptr;
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/confirm/confirm_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/confirm/confirm_infobar_banner_interaction_handler.h
index f5b744a4..bbaf059 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/confirm/confirm_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/confirm/confirm_infobar_banner_interaction_handler.h
@@ -22,7 +22,7 @@
   void BannerVisibilityChanged(InfoBarIOS* infobar, bool visible) override;
 
  private:
-  // Returns the password delegate from |infobar|.
+  // Returns the password delegate from `infobar`.
   ConfirmInfoBarDelegate* GetInfobarDelegate(InfoBarIOS* infobar);
 };
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/infobar_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/infobar_interaction_handler.h
index 827a0aa..d0d20c2 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/infobar_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/infobar_interaction_handler.h
@@ -30,15 +30,15 @@
     // handler's InfobarOverlayType.
     virtual std::unique_ptr<OverlayRequestCallbackInstaller>
     CreateInstaller() = 0;
-    // Notifies the handler that |infobar|'s UI with the handler's InfobarType
+    // Notifies the handler that `infobar`'s UI with the handler's InfobarType
     virtual void InfobarVisibilityChanged(InfoBarIOS* infobar,
                                           bool visible) = 0;
   };
 
   // Constructor for an InfobarInteractionHandler that uses the provided
-  // handlers for each InfobarOverlayType.  |banner_handler| must be non-null.
-  // |modal_handler| may be null if its corresponding InfobarOverlayType is not
-  // supported for |infobar_type|.
+  // handlers for each InfobarOverlayType.  `banner_handler` must be non-null.
+  // `modal_handler` may be null if its corresponding InfobarOverlayType is not
+  // supported for `infobar_type`.
   InfobarInteractionHandler(InfobarType infobar_type,
                             std::unique_ptr<Handler> banner_handler,
                             std::unique_ptr<Handler> modal_handler);
@@ -58,8 +58,8 @@
   std::unique_ptr<OverlayRequestCallbackInstaller>
   CreateModalCallbackInstaller();
 
-  // Called to notify the interaction handler that |infobar|'s overlay UI with
-  // |overlay_type|'s visibility has changed.
+  // Called to notify the interaction handler that `infobar`'s overlay UI with
+  // `overlay_type`'s visibility has changed.
   void InfobarVisibilityChanged(InfoBarIOS* infobar,
                                 InfobarOverlayType overlay_type,
                                 bool visible);
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler.h
index 7e355ce2..8d9e8f69 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler.h
@@ -23,7 +23,7 @@
   void MainButtonTapped(InfoBarIOS* infobar) override;
 
  private:
-  // Returns the password delegate from |infobar|.
+  // Returns the password delegate from `infobar`.
   IOSChromeSavePasswordInfoBarDelegate* GetInfobarDelegate(InfoBarIOS* infobar);
 };
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_interaction_handler.h
index c5fb1975..4abcca9 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_interaction_handler.h
@@ -20,23 +20,23 @@
       password_modal::PasswordAction action_type);
   ~PasswordInfobarModalInteractionHandler() override;
 
-  // Instructs the handler to update the credentials with |username| and
-  // |password| for interaction with |infobar|'s modal UI.
+  // Instructs the handler to update the credentials with `username` and
+  // `password` for interaction with `infobar`'s modal UI.
   // TODO(crbug.com/1040653): This function is only virtual so it can be mocked
   // for testing purposes.  It should become non-virtual once the password
   // infobar delegate is refactored for testability.
   virtual void UpdateCredentials(InfoBarIOS* infobar,
                                  NSString* username,
                                  NSString* password);
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that credentials are never saved for the current site.
   // TODO(crbug.com/1040653): This function is only virtual so it can be mocked
   // for testing purposes.  It should become non-virtual once the password
   // infobar delegate is refactored for testability.
   virtual void NeverSaveCredentials(InfoBarIOS* infobar);
   // Instructs the handler that the user has requested the passwords settings
-  // page through |infobar|'s modal UI.  The settings will be presented after
-  // the dismissal of |infobar|'s modal UI.
+  // page through `infobar`'s modal UI.  The settings will be presented after
+  // the dismissal of `infobar`'s modal UI.
   // TODO(crbug.com/1040653): This function is only virtual so it can be mocked
   // for testing purposes.  It should become non-virtual once the password
   // infobar delegate is refactored for testability.
@@ -59,7 +59,7 @@
   std::unique_ptr<InfobarModalOverlayRequestCallbackInstaller>
   CreateModalInstaller() override;
 
-  // Returns the password delegate from |infobar|.
+  // Returns the password delegate from `infobar`.
   IOSChromeSavePasswordInfoBarDelegate* GetDelegate(InfoBarIOS* infobar);
 
   // The Browser passed on initialization.
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_overlay_request_callback_installer.h
index 77478f3..97e18d6 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_modal_overlay_request_callback_installer.h
@@ -18,38 +18,38 @@
     : public InfobarModalOverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks that forward
-  // interaction events to |interaction_handler| for an Password Infobar Overlay
-  // of type |action_type|.
+  // interaction events to `interaction_handler` for an Password Infobar Overlay
+  // of type `action_type`.
   explicit PasswordInfobarModalOverlayRequestCallbackInstaller(
       PasswordInfobarModalInteractionHandler* interaction_handler,
       password_modal::PasswordAction action_type);
   ~PasswordInfobarModalOverlayRequestCallbackInstaller() override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // password_infobar_modal_responses::UpdateCredentialsInfo.
   void UpdateCredentialsCallback(OverlayRequest* request,
                                  OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // password_infobar_modal_responses::NeverSaveCredentials.
   void NeverSaveCredentialsCallback(OverlayRequest* request,
                                     OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // password_infobar_modal_responses::PresentPasswordSettings.
   void PresentPasswordsSettingsCallback(OverlayRequest* request,
                                         OverlayResponse* response);
 
-  // Used as an optional completion callback for |request|.  Removes the
+  // Used as an optional completion callback for `request`.  Removes the
   // request's infobar from its manager upon completion.
   void RemoveInfobarCompletionCallback(OverlayRequest* request,
                                        OverlayResponse* response);
-  // Used as an optional completion callback for |request|.  Presents the
+  // Used as an optional completion callback for `request`.  Presents the
   // password settings.
   void PresentPasswordSettingsCompletionCallback(OverlayRequest* request,
                                                  OverlayResponse* response);
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_interaction_handler.h
index 779f2a0..15150ad 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_interaction_handler.h
@@ -23,8 +23,8 @@
   SaveCardInfobarBannerInteractionHandler();
   ~SaveCardInfobarBannerInteractionHandler() override;
 
-  // Instructs the handler to update the credentials with |cardholder_name|,
-  // |expiration_date_month|, and |expiration_date_year|. This replaces
+  // Instructs the handler to update the credentials with `cardholder_name`,
+  // `expiration_date_month`, and `expiration_date_year`. This replaces
   // MainButtonTapped.
   virtual void SaveCredentials(InfoBarIOS* infobar,
                                std::u16string cardholder_name,
@@ -40,7 +40,7 @@
   std::unique_ptr<InfobarBannerOverlayRequestCallbackInstaller>
   CreateBannerInstaller() override;
 
-  // Returns the SaveCard delegate from |infobar|.
+  // Returns the SaveCard delegate from `infobar`.
   autofill::AutofillSaveCardInfoBarDelegateMobile* GetInfobarDelegate(
       InfoBarIOS* infobar);
 };
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_overlay_request_callback_installer.h
index fb6e5bb..c365547 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_banner_overlay_request_callback_installer.h
@@ -14,15 +14,15 @@
     : public InfobarBannerOverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks that forward
-  // interaction events to |interaction_handler|.
+  // interaction events to `interaction_handler`.
   explicit SaveCardInfobarBannerOverlayRequestCallbackInstaller(
       SaveCardInfobarBannerInteractionHandler* interaction_handler);
   ~SaveCardInfobarBannerOverlayRequestCallbackInstaller() override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // save_card_infobar_modal_responses::SaveCardMainAction.
   void SaveCredentialsCallback(OverlayRequest* request,
                                OverlayResponse* response);
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_interaction_handler.h
index fb3ac8ad..1664eb3 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_interaction_handler.h
@@ -22,15 +22,15 @@
   SaveCardInfobarModalInteractionHandler();
   ~SaveCardInfobarModalInteractionHandler() override;
 
-  // Instructs the handler to update the credentials with |cardholder_name|,
-  // |expiration_date_month|, and |expiration_date_year|. Replaces
+  // Instructs the handler to update the credentials with `cardholder_name`,
+  // `expiration_date_month`, and `expiration_date_year`. Replaces
   // MainButtonTapped.
   virtual void UpdateCredentials(InfoBarIOS* infobar,
                                  std::u16string cardholder_name,
                                  std::u16string expiration_date_month,
                                  std::u16string expiration_date_year);
 
-  // Instructs the handler to load |url| through the delegate.
+  // Instructs the handler to load `url` through the delegate.
   virtual void LoadURL(InfoBarIOS* infobar, GURL url);
 
   // InfobarModalInteractionHandler:
@@ -42,7 +42,7 @@
   std::unique_ptr<InfobarModalOverlayRequestCallbackInstaller>
   CreateModalInstaller() override;
 
-  // Returns the SaveCard delegate from |infobar|.
+  // Returns the SaveCard delegate from `infobar`.
   autofill::AutofillSaveCardInfoBarDelegateMobile* GetInfoBarDelegate(
       InfoBarIOS* infobar);
 };
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_overlay_request_callback_installer.h
index b1ea213a..1cde498 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/save_card/save_card_infobar_modal_overlay_request_callback_installer.h
@@ -16,21 +16,21 @@
     : public InfobarModalOverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks that forward
-  // interaction events to |interaction_handler|.
+  // interaction events to `interaction_handler`.
   explicit SaveCardInfobarModalOverlayRequestCallbackInstaller(
       SaveCardInfobarModalInteractionHandler* interaction_handler);
   ~SaveCardInfobarModalOverlayRequestCallbackInstaller() override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // save_card_infobar_modal_responses::SaveCardMainAction.
   void SaveCardCredentialsCallback(OverlayRequest* request,
                                    OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager.  The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // save_card_infobar_modal_responses::SaveCardLoadURL.
   void LoadURLCallback(OverlayRequest* request, OverlayResponse* response);
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_infobar_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_infobar_interaction_handler.h
index bb5eb4b..10f0c5ab 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_infobar_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_infobar_interaction_handler.h
@@ -34,11 +34,11 @@
     ~Builder();
 
     // Constructs an InfobarInteractionHandler using mock handlers.  Calling
-    // this function also populates |mock_handlers_|.  Must only be called once
+    // this function also populates `mock_handlers_`.  Must only be called once
     // per Builder.
     std::unique_ptr<InfobarInteractionHandler> Build();
 
-    // Returns the mock handler for |overlay_type| used to build the
+    // Returns the mock handler for `overlay_type` used to build the
     // InfobarInteractionHandler.  Returns null before Build() is called.
     Handler* mock_handler(InfobarOverlayType overlay_type) {
       return mock_handlers_[overlay_type];
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
index d002252..19c31e0 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
@@ -21,7 +21,7 @@
   void MainButtonTapped(InfoBarIOS* infobar) override;
 
  private:
-  // Returns the password delegate from |infobar|.
+  // Returns the password delegate from `infobar`.
   translate::TranslateInfoBarDelegate* GetInfobarDelegate(InfoBarIOS* infobar);
 };
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.h
index 2cad065..81efa255 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.h
@@ -19,22 +19,22 @@
   TranslateInfobarModalInteractionHandler();
   ~TranslateInfobarModalInteractionHandler() override;
 
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that the always translate preference be toggled.
   virtual void ToggleAlwaysTranslate(InfoBarIOS* infobar);
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that the never translate source language preference be toggled.
   virtual void ToggleNeverTranslateLanguage(InfoBarIOS* infobar);
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that the never translate site preference be toggled.
   virtual void ToggleNeverTranslateSite(InfoBarIOS* infobar);
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that the translation be reverted.
   virtual void RevertTranslation(InfoBarIOS* infobar);
-  // Instructs the handler that the user has used |infobar|'s modal UI to
+  // Instructs the handler that the user has used `infobar`'s modal UI to
   // request that the source language change to the language at
-  // |source_language_index| and/or the target language change to the language
-  // at |target_language_index|. If either do not need to be updated, then the
+  // `source_language_index` and/or the target language change to the language
+  // at `target_language_index`. If either do not need to be updated, then the
   // index passed should be -1.
   virtual void UpdateLanguages(InfoBarIOS* infobar,
                                int source_language_index,
@@ -47,14 +47,14 @@
   void InfobarVisibilityChanged(InfoBarIOS* infobar, bool visible) override;
 
  private:
-  // Initiates a translate for |infobar|.
+  // Initiates a translate for `infobar`.
   void StartTranslation(InfoBarIOS* infobar);
 
   // InfobarModalInteractionHandler:
   std::unique_ptr<InfobarModalOverlayRequestCallbackInstaller>
   CreateModalInstaller() override;
 
-  // Returns the translate delegate from |infobar|.
+  // Returns the translate delegate from `infobar`.
   translate::TranslateInfoBarDelegate* GetDelegate(InfoBarIOS* infobar);
 };
 
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.mm b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.mm
index c53e63af..1b9dc1e 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_interaction_handler.mm
@@ -29,7 +29,7 @@
 using translate_infobar_overlay::ModalRequestCallbackInstaller;
 
 namespace {
-// Records a histogram of |histogram| for |langCode|. This is used to log the
+// Records a histogram of `histogram` for `langCode`. This is used to log the
 // language distribution of certain Translate events.
 void RecordLanguageDataHistogram(const std::string& histogram_name,
                                  const std::string& lang_code) {
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer.h
index f8c52b2..9fff441 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer.h
@@ -19,41 +19,41 @@
     : public InfobarModalOverlayRequestCallbackInstaller {
  public:
   // Constructor for an instance that installs callbacks that forward
-  // interaction events to |interaction_handler|.
+  // interaction events to `interaction_handler`.
   explicit ModalRequestCallbackInstaller(
       TranslateInfobarModalInteractionHandler* interaction_handler);
   ~ModalRequestCallbackInstaller() override;
 
  private:
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager. The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // translate_infobar_modal_responses::RevertMainAction.
   void RevertTranslationCallback(OverlayRequest* request,
                                  OverlayResponse* response);
 
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager. The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with an
+  // OverlayResponseSupport that guarantees that `response` is created with an
   // translate_infobar_modal_responses::ToggleAlwaysTranslate.
   void ToggleAlwaysTranslateCallback(OverlayRequest* request,
                                      OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager. The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // translate_infobar_modal_responses::ToggleNeverTranslateSourceLanguage.
   void ToggleNeverTranslateSourceLanguageCallback(OverlayRequest* request,
                                                   OverlayResponse* response);
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager. The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // translate_infobar_modal_responses::ToggleNeverPromptSite.
   void ToggleNeverTranslateSiteCallback(OverlayRequest* request,
                                         OverlayResponse* response);
 
-  // Used as a callback for OverlayResponses dispatched through |request|'s
+  // Used as a callback for OverlayResponses dispatched through `request`'s
   // callback manager. The OverlayDispatchCallback is created with an
-  // OverlayResponseSupport that guarantees that |response| is created with a
+  // OverlayResponseSupport that guarantees that `response` is created with a
   // translate_infobar_modal_responses::UpdateLanguageInfo.
   void UpdateLanguageCallback(OverlayRequest* request,
                               OverlayResponse* response);
diff --git a/ios/chrome/browser/infobars/overlays/infobar_banner_overlay_request_cancel_handler.h b/ios/chrome/browser/infobars/overlays/infobar_banner_overlay_request_cancel_handler.h
index 000c37c..5dedd744 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_banner_overlay_request_cancel_handler.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_banner_overlay_request_cancel_handler.h
@@ -15,9 +15,9 @@
 class InfobarBannerOverlayRequestCancelHandler
     : public InfobarOverlayRequestCancelHandler {
  public:
-  // Constructor for a handler that cancels |request| from |queue|.  |inserter|
+  // Constructor for a handler that cancels `request` from `queue`.  `inserter`
   // is used to insert replacement requests when an infobar is replaced.
-  // |modal_completion_notifier| is used to detect the completion of any modal
+  // `modal_completion_notifier` is used to detect the completion of any modal
   // UI that was presented from the banner.
   InfobarBannerOverlayRequestCancelHandler(
       OverlayRequest* request,
@@ -80,7 +80,7 @@
   void ModalPresentedFromBanner() { presenting_modal_ = true; }
 
   // Indicates that a modal completed. Only called for modal completions of
-  // infobars that match the one used to configure |request|.
+  // infobars that match the one used to configure `request`.
   void ModalCompleted();
 
   // InfobarOverlayRequestCancelHandler:
diff --git a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
index 9c8a4d6..832b66f 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
@@ -29,20 +29,20 @@
     Observer() = default;
     ~Observer() override = default;
 
-    // Called to notify observers of |notifier| that the modal requests for
-    // |infobar| have completed.  Banners should remain visible until all
+    // Called to notify observers of `notifier` that the modal requests for
+    // `infobar` have completed.  Banners should remain visible until all
     // requests for modal UI originating from the banner's infobar is completed.
     virtual void InfobarModalsCompleted(
         InfobarModalCompletionNotifier* notifier,
         InfoBarIOS* infobar) {}
 
-    // Called when |notifier| is being destroyed.
+    // Called when `notifier` is being destroyed.
     virtual void InfobarModalCompletionNotifierDestroyed(
         InfobarModalCompletionNotifier* notifier) {}
   };
 
   // Constructs a notifier that observes the completion of modal requests in
-  // |web_state|'s queue.
+  // `web_state`'s queue.
   explicit InfobarModalCompletionNotifier(web::WebState* web_state);
   ~InfobarModalCompletionNotifier();
 
@@ -60,7 +60,7 @@
 
    private:
     // Used as a completion callback for the modal OverlayRequests for
-    // |infobar|.
+    // `infobar`.
     void ModalCompleted(InfoBarIOS* infobar, OverlayResponse* response);
 
     // OverlayRequestCallbackInstaller:
@@ -72,11 +72,11 @@
     base::WeakPtrFactory<ModalCompletionInstaller> weak_factory_;
   };
 
-  // Called when a completion callback for a modal request for |infobar| has
+  // Called when a completion callback for a modal request for `infobar` has
   // been installed.
   void ModalCompletionInstalled(InfoBarIOS* infobar);
 
-  // Called when a modal request for |infobar| has been completed.
+  // Called when a modal request for `infobar` has been completed.
   void ModalRequestCompleted(InfoBarIOS* infobar);
 
   // Map storing the number of active modal OverlayRequests for a given
diff --git a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier_unittest.mm b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier_unittest.mm
index 5ed47ea1..1084bc8 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier_unittest.mm
@@ -53,10 +53,10 @@
       scoped_observation_{&observer_};
 };
 
-// Tests that the observer is notified when all modal requests for |infobar_|
+// Tests that the observer is notified when all modal requests for `infobar_`
 // have been removed.
 TEST_F(InfobarModalCompletionNotifierTest, ModalCompletion) {
-  // Add a modal request for |infobar_|.
+  // Add a modal request for `infobar_`.
   std::unique_ptr<OverlayRequest> modal_request =
       OverlayRequest::CreateWithConfig<InfobarOverlayRequestConfig>(
           &infobar_, InfobarOverlayType::kModal, false);
diff --git a/ios/chrome/browser/infobars/overlays/infobar_modal_overlay_request_cancel_handler.h b/ios/chrome/browser/infobars/overlays/infobar_modal_overlay_request_cancel_handler.h
index 49a514f2..43f69f8f 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_modal_overlay_request_cancel_handler.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_modal_overlay_request_cancel_handler.h
@@ -14,8 +14,8 @@
 class InfobarModalOverlayRequestCancelHandler
     : public InfobarOverlayRequestCancelHandler {
  public:
-  // Constructor for a handler that cancels |request| from |queue|.
-  // |modal_completion_notifier| is used to detect the completion of any modal
+  // Constructor for a handler that cancels `request` from `queue`.
+  // `modal_completion_notifier` is used to detect the completion of any modal
   // UI that was presented from the banner.
   InfobarModalOverlayRequestCancelHandler(
       OverlayRequest* request,
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_cancel_handler.h b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_cancel_handler.h
index 2628532..1833a646 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_cancel_handler.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_cancel_handler.h
@@ -24,7 +24,7 @@
   // Returns the InfoBar that the corresponding request was configured with.
   InfoBarIOS* infobar() const { return infobar_; }
 
-  // Called when the infobar triggering |request| was replaced in its manager.
+  // Called when the infobar triggering `request` was replaced in its manager.
   // Default implementation does nothing.
   virtual void HandleReplacement(InfoBarIOS* replacement);
 
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h
index a81af8b..e0e3d35a 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h
@@ -49,7 +49,7 @@
 class InfobarOverlayRequestInserter
     : public web::WebStateUserData<InfobarOverlayRequestInserter> {
  public:
-  // Creates an inserter for |web_state| that uses |request_factory| to create
+  // Creates an inserter for `web_state` that uses `request_factory` to create
   // inserted requests.
   static void CreateForWebState(
       web::WebState* web_state,
@@ -57,7 +57,7 @@
 
   ~InfobarOverlayRequestInserter() override;
 
-  // Creates an OverlayRequest with |params| configurations.
+  // Creates an OverlayRequest with `params` configurations.
   void InsertOverlayRequest(const InsertParams& params);
 
   // Notifies observers of Infobar request insertions
@@ -67,12 +67,12 @@
     ~Observer() override = default;
 
     // Called to notify observers that an Infobar request has been inserted
-    // with |params| configurations.
-    // |params.insertion_index| must be less than or equal to the size of the
+    // with `params` configurations.
+    // `params.insertion_index` must be less than or equal to the size of the
     // queue.
     virtual void InfobarRequestInserted(InfobarOverlayRequestInserter* inserter,
                                         const InsertParams& params) = 0;
-    // Called to notify observers that the |inserter| is about to be destroyed;
+    // Called to notify observers that the `inserter` is about to be destroyed;
     virtual void InserterDestroyed(InfobarOverlayRequestInserter* inserter) = 0;
   };
 
@@ -84,9 +84,9 @@
   friend class web::WebStateUserData<InfobarOverlayRequestInserter>;
   WEB_STATE_USER_DATA_KEY_DECL();
 
-  // Constructor for an inserter that uses |factory| to construct
-  // OverlayRequests to insert into |web_state|'s OverlayRequestQueues.  Both
-  // |web_state| and |factory| must be non-null.
+  // Constructor for an inserter that uses `factory` to construct
+  // OverlayRequests to insert into `web_state`'s OverlayRequestQueues.  Both
+  // `web_state` and `factory` must be non-null.
   InfobarOverlayRequestInserter(web::WebState* web_state,
                                 InfobarOverlayRequestFactory factory);
 
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.mm b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.mm
index 23d8e5e..3bfa5c14 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.mm
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.mm
@@ -51,7 +51,7 @@
       request_factory_(factory) {
   DCHECK(web_state_);
   DCHECK(request_factory_);
-  // Populate |queues_| with the request queues at the appropriate modalities.
+  // Populate `queues_` with the request queues at the appropriate modalities.
   queues_[InfobarOverlayType::kBanner] = OverlayRequestQueue::FromWebState(
       web_state_, OverlayModality::kInfobarBanner);
   queues_[InfobarOverlayType::kModal] = OverlayRequestQueue::FromWebState(
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter_unittest.mm b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter_unittest.mm
index 29a7556..453a1e8 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter_unittest.mm
@@ -57,7 +57,7 @@
   }
 
   // Adds an InfoBar created with a test delegate to the manager.  Returns a
-  // pointer to the added InfoBar.  If |message_text| matches an infobar already
+  // pointer to the added InfoBar.  If `message_text` matches an infobar already
   // added, then it the new one will be ignored.
   InfoBar* CreateInfobar(std::u16string message_text) {
     std::unique_ptr<InfoBar> added_infobar = std::make_unique<FakeInfobarIOS>(
@@ -75,7 +75,7 @@
 TEST_F(InfobarOverlayRequestInserterTest, InsertBanner) {
   OverlayRequestQueue* queue = GetQueue(InfobarOverlayType::kBanner);
   ASSERT_EQ(0U, queue->size());
-  // Insert |infobar| at front of queue and check that the queue is updated
+  // Insert `infobar` at front of queue and check that the queue is updated
   // correctly.
   InfoBar* infobar = CreateInfobar(kFirstInfobarMessageText);
   InsertParams params(static_cast<InfoBarIOS*>(infobar));
@@ -87,7 +87,7 @@
   EXPECT_EQ(infobar, queue->front_request()
                          ->GetConfig<InfobarOverlayRequestConfig>()
                          ->infobar());
-  // Insert |inserted_infobar| in front of |infobar| and check that it is now
+  // Insert `inserted_infobar` in front of `infobar` and check that it is now
   // the front request.
   InfoBar* inserted_infobar = CreateInfobar(kSecondInfobarMessageText);
   params.infobar = static_cast<InfoBarIOS*>(inserted_infobar);
@@ -102,7 +102,7 @@
 TEST_F(InfobarOverlayRequestInserterTest, AddBanner) {
   OverlayRequestQueue* queue = GetQueue(InfobarOverlayType::kBanner);
   ASSERT_EQ(0U, queue->size());
-  // Add |infobar| to the back of the queue and check that the it is updated
+  // Add `infobar` to the back of the queue and check that the it is updated
   // correctly.
   InfoBar* infobar = CreateInfobar(kFirstInfobarMessageText);
   InsertParams params(static_cast<InfoBarIOS*>(infobar));
@@ -114,7 +114,7 @@
   EXPECT_EQ(infobar, queue->front_request()
                          ->GetConfig<InfobarOverlayRequestConfig>()
                          ->infobar());
-  // Add |second_infobar| in to the queue and check that it is second in the
+  // Add `second_infobar` in to the queue and check that it is second in the
   // queue.
   InfoBar* second_infobar = CreateInfobar(kSecondInfobarMessageText);
   params.infobar = static_cast<InfoBarIOS*>(second_infobar);
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_tab_helper_unittest.mm b/ios/chrome/browser/infobars/overlays/infobar_overlay_tab_helper_unittest.mm
index 7f60061b..a9db153 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_tab_helper_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_tab_helper_unittest.mm
@@ -42,7 +42,7 @@
     InfobarOverlayTabHelper::CreateForWebState(&web_state_);
   }
 
-  // Returns the front request of |web_state_|'s OverlayRequestQueue.
+  // Returns the front request of `web_state_`'s OverlayRequestQueue.
   OverlayRequest* front_request() {
     return OverlayRequestQueue::FromWebState(&web_state_,
                                              OverlayModality::kInfobarBanner)
diff --git a/ios/chrome/browser/infobars/overlays/infobar_overlay_util.h b/ios/chrome/browser/infobars/overlays/infobar_overlay_util.h
index 6a67c0e9..749e0360 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_overlay_util.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_overlay_util.h
@@ -14,24 +14,24 @@
 class OverlayRequest;
 class OverlayRequestQueue;
 
-// Returns the InfoBarIOS used to configure |request|, or null if the InfoBarIOS
-// was already destroyed or if |request| was not created with an infobar config.
+// Returns the InfoBarIOS used to configure `request`, or null if the InfoBarIOS
+// was already destroyed or if `request` was not created with an infobar config.
 InfoBarIOS* GetOverlayRequestInfobar(OverlayRequest* request);
 
-// Returns the InfobarType of the InfoBar used to configure |request|.
-// |request| must be non-null and configured with an
+// Returns the InfobarType of the InfoBar used to configure `request`.
+// `request` must be non-null and configured with an
 // InfobarOverlayRequestConfig.
-// TODO(crbug.com/1038933): Remove requirements on |request| and return
+// TODO(crbug.com/1038933): Remove requirements on `request` and return
 // InfobarType::kNone once added.
 InfobarType GetOverlayRequestInfobarType(OverlayRequest* request);
 
-// Returns the InfobarOverlayType for |request|.  |request| must be non-null and
+// Returns the InfobarOverlayType for `request`.  `request` must be non-null and
 // configured with an InfobarOverlayRequestConfig.
 InfobarOverlayType GetOverlayRequestInfobarOverlayType(OverlayRequest* request);
 
-// Searches through |queue| for an OverlayRequest configured with |infobar|.  If
-// found, returns true and populates |index| with the index of the first request
-// configured with |infobar|.  If no matching request was found, returns false.
+// Searches through `queue` for an OverlayRequest configured with `infobar`.  If
+// found, returns true and populates `index` with the index of the first request
+// configured with `infobar`.  If no matching request was found, returns false.
 // All arguments must be non-null.
 bool GetInfobarOverlayRequestIndex(OverlayRequestQueue* queue,
                                    InfoBarIOS* infobar,
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm b/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm
index eae50eb..037c758 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm
@@ -45,13 +45,13 @@
 
   ~PermissionsOverlayTabHelperTest() override {
     InfoBarManagerImpl::FromWebState(&web_state_)->ShutDown();
-    // Observer should be removed before |scoped_feature_list_| is reset.
+    // Observer should be removed before `scoped_feature_list_` is reset.
     web_state_.RemoveObserver(
         PermissionsOverlayTabHelper::FromWebState(&web_state_));
   }
 
  protected:
-  // Returns InfoBarManager attached to |web_state()|.
+  // Returns InfoBarManager attached to `web_state()`.
   infobars::InfoBarManager* infobar_manager() {
     return InfoBarManagerImpl::FromWebState(&web_state_);
   }
diff --git a/ios/chrome/browser/infobars/overlays/translate_infobar_placeholder_overlay_request_cancel_handler.h b/ios/chrome/browser/infobars/overlays/translate_infobar_placeholder_overlay_request_cancel_handler.h
index 77c5f28..08433b3 100644
--- a/ios/chrome/browser/infobars/overlays/translate_infobar_placeholder_overlay_request_cancel_handler.h
+++ b/ios/chrome/browser/infobars/overlays/translate_infobar_placeholder_overlay_request_cancel_handler.h
@@ -22,7 +22,7 @@
 class PlaceholderRequestCancelHandler
     : public InfobarOverlayRequestCancelHandler {
  public:
-  // Constructor for a handler that cancels |request| of |translate_infobar|.
+  // Constructor for a handler that cancels `request` of `translate_infobar`.
   PlaceholderRequestCancelHandler(OverlayRequest* request,
                                   OverlayRequestQueue* queue,
                                   TranslateOverlayTabHelper* tab_helper,
diff --git a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper.h b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper.h
index 76d8d9bd..9166a75 100644
--- a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper.h
+++ b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper.h
@@ -58,7 +58,7 @@
     TranslateStepObserver(TranslateOverlayTabHelper* tab_helper);
     ~TranslateStepObserver() override;
 
-    // Starts observing |infobar|'s delegate, stores |infobar| for
+    // Starts observing `infobar`'s delegate, stores `infobar` for
     // TranslateDid[Start/Finish]
     void SetTranslateInfoBar(InfoBarIOS* infobar);
 
diff --git a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
index 9ff1597..34385b4 100644
--- a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
@@ -67,7 +67,7 @@
     InfoBarManagerImpl::FromWebState(&web_state_)->ShutDown();
   }
 
-  // Returns the front request of |web_state_|'s OverlayRequestQueue.
+  // Returns the front request of `web_state_`'s OverlayRequestQueue.
   OverlayRequest* front_request() {
     return OverlayRequestQueue::FromWebState(&web_state_,
                                              OverlayModality::kInfobarBanner)
diff --git a/ios/chrome/browser/promos_manager/constants.cc b/ios/chrome/browser/promos_manager/constants.cc
index 3c7b3b5..5f23e703 100644
--- a/ios/chrome/browser/promos_manager/constants.cc
+++ b/ios/chrome/browser/promos_manager/constants.cc
@@ -8,6 +8,8 @@
 
 namespace promos_manager {
 
+const int kLastSeenDayPromoNotFound = -1;
+
 // WARNING - PLEASE READ: Sadly, we cannot switch over strings in C++, so be
 // very careful when updating this method to ensure all enums are accounted for.
 Promo PromoForName(std::string promo) {
diff --git a/ios/chrome/browser/promos_manager/constants.h b/ios/chrome/browser/promos_manager/constants.h
index 3f30b3d..e2720bc 100644
--- a/ios/chrome/browser/promos_manager/constants.h
+++ b/ios/chrome/browser/promos_manager/constants.h
@@ -9,10 +9,23 @@
 
 namespace promos_manager {
 
+// Sentinel value returned from PromosManager::LastSeenDay() if the
+// promos_manager::Promo `promo` isn't found in the impressions list.
+extern const int kLastSeenDayPromoNotFound;
+
 enum class Promo {
   Test = 0,  // Test promo used for testing purposes (e.g. unit tests)
 };
 
+typedef struct Impression {
+  Promo promo;
+  // A day (int) is represented as the number of days since the Unix epoch
+  // (running from UTC midnight to UTC midnight).
+  int day;
+
+  Impression(Promo promo, int day) : promo(promo), day(day) {}
+} Impression;
+
 // Returns string representation of promos_manager::Promo `promo`.
 std::string NameForPromo(Promo promo);
 
diff --git a/ios/chrome/browser/promos_manager/promos_manager.h b/ios/chrome/browser/promos_manager/promos_manager.h
index 97fce47..bda2f2f 100644
--- a/ios/chrome/browser/promos_manager/promos_manager.h
+++ b/ios/chrome/browser/promos_manager/promos_manager.h
@@ -6,9 +6,11 @@
 #define IOS_CHROME_BROWSER_PROMOS_MANAGER_PROMOS_MANAGER_H_
 
 #import <Foundation/Foundation.h>
+#import <vector>
 
 #import "base/values.h"
 #import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/promos_manager/constants.h"
 #import "ios/chrome/browser/promos_manager/impression_limit.h"
 
 // Centralized promos manager for coordinating and scheduling the display of
@@ -38,6 +40,20 @@
 
   // Impression limits that count against any given promo.
   NSArray<ImpressionLimit*>* GlobalPerPromoImpressionLimits();
+
+  // Returns the most recent day (int) that `promo` was seen by the user.
+  //
+  // A day (int) is represented as the number of days since the Unix epoch
+  // (running from UTC midnight to UTC midnight).
+  //
+  // Assumes that `sorted_impressions` is sorted by day (most recent -> least
+  // recent).
+  //
+  // Returns promos_manager::kLastSeenDayPromoNotFound if `promo` isn't
+  // found in the impressions list.
+  int LastSeenDay(
+      promos_manager::Promo promo,
+      std::vector<promos_manager::Impression>& sorted_impressions) const;
 };
 
 #endif  // IOS_CHROME_BROWSER_PROMOS_MANAGER_PROMOS_MANAGER_H_
diff --git a/ios/chrome/browser/promos_manager/promos_manager.mm b/ios/chrome/browser/promos_manager/promos_manager.mm
index 3825229..8ad40235c 100644
--- a/ios/chrome/browser/promos_manager/promos_manager.mm
+++ b/ios/chrome/browser/promos_manager/promos_manager.mm
@@ -5,10 +5,12 @@
 #import "ios/chrome/browser/promos_manager/promos_manager.h"
 
 #import <Foundation/Foundation.h>
+#import <vector>
 
 #import "base/values.h"
 #import "components/prefs/pref_service.h"
 #import "ios/chrome/browser/pref_names.h"
+#import "ios/chrome/browser/promos_manager/constants.h"
 #import "ios/chrome/browser/promos_manager/features.h"
 #import "ios/chrome/browser/promos_manager/impression_limit.h"
 
@@ -16,6 +18,15 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+// Comparator for descending sort evaluation using std::is_sorted.
+bool Compare(promos_manager::Impression a, promos_manager::Impression b) {
+  return a.day > b.day;
+}
+
+}  // namespace
+
 #pragma mark - PromosManager
 
 #pragma mark - Constructor/Destructor
@@ -73,3 +84,22 @@
 
   return limits;
 }
+
+int PromosManager::LastSeenDay(
+    promos_manager::Promo promo,
+    std::vector<promos_manager::Impression>& sorted_impressions) const {
+  if (sorted_impressions.empty())
+    return promos_manager::kLastSeenDayPromoNotFound;
+
+  DCHECK(std::is_sorted(sorted_impressions.begin(), sorted_impressions.end(),
+                        Compare));
+
+  // Find first occurrence of `promo` in list (i.e. find the most recent
+  // occurrence of `promo`).
+  for (size_t j = 0; j < sorted_impressions.size(); ++j) {
+    if (sorted_impressions[j].promo == promo)
+      return sorted_impressions[j].day;
+  }
+
+  return promos_manager::kLastSeenDayPromoNotFound;
+}
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h b/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h
index 8f8c348a..3f7a05be 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h
@@ -54,7 +54,8 @@
                          size_t new_index) override;
   void BookmarkNodeAdded(BookmarkModel* model,
                          const BookmarkNode* parent,
-                         size_t index) override;
+                         size_t index,
+                         bool added_by_user) override;
   void BookmarkNodeRemoved(BookmarkModel* model,
                            const BookmarkNode* parent,
                            size_t old_index,
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.mm b/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.mm
index d22d442..4f5ff51 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.mm
@@ -53,7 +53,8 @@
 
 void BookmarkModelBridge::BookmarkNodeAdded(BookmarkModel* model,
                                             const BookmarkNode* parent,
-                                            size_t index) {
+                                            size_t index,
+                                            bool added_by_user) {
   [observer_ bookmarkNodeChildrenChanged:parent];
 }
 
diff --git a/ios/chrome/browser/ui/first_run/trending_queries_field_trial.cc b/ios/chrome/browser/ui/first_run/trending_queries_field_trial.cc
index e9e5644586..12cd30b 100644
--- a/ios/chrome/browser/ui/first_run/trending_queries_field_trial.cc
+++ b/ios/chrome/browser/ui/first_run/trending_queries_field_trial.cc
@@ -53,6 +53,12 @@
       weight_by_id[kTrendingQueriesControlID] = 10;
       break;
     case version_info::Channel::STABLE:
+      weight_by_id[kTrendingQueriesEnabledAllUsersID] = 1;
+      weight_by_id[kTrendingQueriesEnabledAllUsersHideShortcutsID] = 1;
+      weight_by_id[kTrendingQueriesEnabledDisabledFeedID] = 1;
+      weight_by_id[kTrendingQueriesEnabledSignedOutID] = 1;
+      weight_by_id[kTrendingQueriesEnabledNeverShowModuleID] = 1;
+      weight_by_id[kTrendingQueriesControlID] = 1;
       break;
   }
   return weight_by_id;
diff --git a/ios/chrome/browser/ui/lens/BUILD.gn b/ios/chrome/browser/ui/lens/BUILD.gn
index 412b1ec..872fd90 100644
--- a/ios/chrome/browser/ui/lens/BUILD.gn
+++ b/ios/chrome/browser/ui/lens/BUILD.gn
@@ -26,3 +26,12 @@
   ]
   deps = []
 }
+
+source_set("lens_entrypoint") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "lens_entrypoint.h",
+    "lens_entrypoint.mm",
+  ]
+  deps = []
+}
diff --git a/ios/chrome/browser/ui/lens/lens_entrypoint.h b/ios/chrome/browser/ui/lens/lens_entrypoint.h
new file mode 100644
index 0000000..5f19b62
--- /dev/null
+++ b/ios/chrome/browser/ui/lens/lens_entrypoint.h
@@ -0,0 +1,21 @@
+// Copyright 2022 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_LENS_LENS_ENTRYPOINT_H_
+#define IOS_CHROME_BROWSER_UI_LENS_LENS_ENTRYPOINT_H_
+
+// Enum representing the possible Lens entrypoints on iOS.
+// Current values should not be renumbered. Please keep in sync with
+// "IOSLensEntrypoint" in src/tools/metrics/histograms/enums.xml.
+enum class LensEntrypoint {
+  ContextMenu = 0,
+  HomeScreenWidget = 1,
+  NewTabPage = 2,
+  Keyboard = 3,
+  kMaxValue = Keyboard,
+};
+
+extern const char kIOSLensEntrypoint[];
+
+#endif  // IOS_CHROME_BROWSER_UI_LENS_LENS_ENTRYPOINT_H_
diff --git a/ios/chrome/browser/ui/lens/lens_entrypoint.mm b/ios/chrome/browser/ui/lens/lens_entrypoint.mm
new file mode 100644
index 0000000..412218b
--- /dev/null
+++ b/ios/chrome/browser/ui/lens/lens_entrypoint.mm
@@ -0,0 +1,11 @@
+// Copyright 2022 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/lens/lens_entrypoint.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+const char kIOSLensEntrypoint[] = "ContextMenu.iOS.LensEntrypoint";
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index 9bbc68ca..fbcbb1ca 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -61,6 +61,15 @@
 const base::Feature kUseLensToSearchForImage{"UseLensToSearchForImage",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEnableLensInHomeScreenWidget{
+    "EnableLensInHomeScreenWidget", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kEnableLensInKeyboard{"EnableLensInKeyboard",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kEnableLensInNTP{"EnableLensInNTP",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kRemoveExcessNTPs{"RemoveExcessNTPs",
                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/ios/chrome/browser/ui/ui_feature_flags.h b/ios/chrome/browser/ui/ui_feature_flags.h
index a55461b..c852c53 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.h
+++ b/ios/chrome/browser/ui/ui_feature_flags.h
@@ -77,6 +77,15 @@
 // Feature flag to enable using Lens to search for images.
 extern const base::Feature kUseLensToSearchForImage;
 
+// Feature flag to enable the Lens entrypoint in the home screen widget.
+extern const base::Feature kEnableLensInHomeScreenWidget;
+
+// Feature flag to enable the Lens entrypoint in the keyboard.
+extern const base::Feature kEnableLensInKeyboard;
+
+// Feature flag to enable the Lens entrypoint in the new tab page.
+extern const base::Feature kEnableLensInNTP;
+
 // Feature flag to enable duplicate NTP cleanup.
 extern const base::Feature kRemoveExcessNTPs;
 
diff --git a/ios/chrome/browser/web/web_share_egtest.mm b/ios/chrome/browser/web/web_share_egtest.mm
index 90d354f..65129448 100644
--- a/ios/chrome/browser/web/web_share_egtest.mm
+++ b/ios/chrome/browser/web/web_share_egtest.mm
@@ -28,6 +28,7 @@
 const char kWebShareFileUrl[] = "/share_file.html";
 const char kWebShareRelativeLinkUrl[] = "/share_relative_link.html";
 const char kWebShareRelativeFilenameFileUrl[] = "/share_filename_file.html";
+const char kWebShareUrlObjectUrl[] = "/share_url_object.html";
 
 const char kWebSharePageContents[] =
     "<html>"
@@ -36,8 +37,8 @@
     "async function tryUrl() {"
     "  document.getElementById(\"result\").innerHTML = '';"
     "  try {"
-    "    var opts = {url: \"%s\"};"
-    "    navigator.share(opts);"
+    "    var opts = {url: %s};"
+    "    await navigator.share(opts);"
     "    document.getElementById(\"result\").innerHTML = 'success';"
     "  } catch {"
     "    document.getElementById(\"result\").innerHTML = 'failure';"
@@ -57,19 +58,23 @@
 
   if (request.relative_url == kWebShareValidLinkUrl) {
     std::string content =
-        base::StringPrintf(kWebSharePageContents, "https://example.com");
+        base::StringPrintf(kWebSharePageContents, "\"https://example.com\"");
     http_response->set_content(content);
   } else if (request.relative_url == kWebShareFileUrl) {
     std::string content =
-        base::StringPrintf(kWebSharePageContents, "file:///Users/u/data");
+        base::StringPrintf(kWebSharePageContents, "\"file:///Users/u/data\"");
     http_response->set_content(content);
   } else if (request.relative_url == kWebShareRelativeLinkUrl) {
     std::string content =
-        base::StringPrintf(kWebSharePageContents, "/something.png");
+        base::StringPrintf(kWebSharePageContents, "\"/something.png\"");
     http_response->set_content(content);
   } else if (request.relative_url == kWebShareRelativeFilenameFileUrl) {
     std::string content =
-        base::StringPrintf(kWebSharePageContents, "filename.zip");
+        base::StringPrintf(kWebSharePageContents, "\"filename.zip\"");
+    http_response->set_content(content);
+  } else if (request.relative_url == kWebShareUrlObjectUrl) {
+    std::string content =
+        base::StringPrintf(kWebSharePageContents, "window.location");
     http_response->set_content(content);
   } else {
     return nullptr;
@@ -145,4 +150,17 @@
       assertWithMatcher:grey_nil()];
 }
 
+// Tests that an url object can be shared.
+- (void)testShareUrlObject {
+  const GURL pageURL = self.testServer->GetURL(kWebShareUrlObjectUrl);
+  [ChromeEarlGrey loadURL:pageURL];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElementWithId(kWebShareButtonId)];
+
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"Copy")]
+      performAction:grey_tap()];
+
+  [ChromeEarlGrey waitForWebStateContainingText:kWebShareStatusSuccess];
+}
+
 @end
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 3c622a05..223b23f 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 @@
-efa0c0eebae6942a02964c7cf482a4ed4a7e3b2a
\ No newline at end of file
+7bd464603dd765c23feb18aceefeb1c1dc671a9d
\ 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 969f22af..42607c9 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 @@
-1ccd5df11f4570bfce8bb8217891712a02f6294b
\ No newline at end of file
+2e5d4633d6328ea13da22410b382e19b8c38274e
\ 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 c53fc9e..41f2afd9 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 @@
-0bffa95c454c08afa4a4f45ce9a02d002b2bd179
\ No newline at end of file
+b2298131b8e0a9c26bdbd9b1d6eb2702bd3d6dcf
\ 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 5f89b49..cc1b00f 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 @@
-3af08f4429cd4d43f1dde3f91e25eba6cd8e8147
\ No newline at end of file
+258ea725c0b9400e9f176196a9035086e47de93a
\ 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 b178199..c83351e1 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 @@
-e468aa4f006ab935bc9bd3edd71336d7c9d55bf6
\ No newline at end of file
+5460a5f2851583d0fca4f5588e8322abd26b2cf2
\ 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 7739934..6b2990e 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 @@
-1110f7e96e1aff6a555c1a1c6b7a2127996ff918
\ No newline at end of file
+a87805c062b890080eafef65eb487691264721c8
\ 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 cd73652..fc7d2dd 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 @@
-05758d525e85d31d3a5a9d552dad8e991bb321f1
\ No newline at end of file
+0a342e12b6a97d90ee850575677cd2f284fc7eb3
\ 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 2fe603b..0ee8ba1b 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 @@
-97ee4087024faa1aa3c8820d3a6113f23e09c9bd
\ No newline at end of file
+1ee4b7151fe65989fa458a6c5d94a7758f09ded9
\ 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 de6d59e..d930628 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 @@
-5344966552648b2a67fa401de7e3b90ee602587f
\ No newline at end of file
+602ec65e2727f7d5bcddc9f1a7fae3c47f325098
\ 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 3e5d4d76..af37ded 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 @@
-78008e6ccb24cfa6ede586ec69f76985fc66ab16
\ No newline at end of file
+79e9f77b88ef9336b96d049aa5c7752d97c6ce29
\ No newline at end of file
diff --git a/ios/web/web_state/js/resources/share_workaround.js b/ios/web/web_state/js/resources/share_workaround.js
index e8c0da05..704f598 100644
--- a/ios/web/web_state/js/resources/share_workaround.js
+++ b/ios/web/web_state/js/resources/share_workaround.js
@@ -48,13 +48,13 @@
 
   let url = undefined;
   if (data.hasOwnProperty('url')) {
-    url = data['url'];
+    url = data['url']?.toString();
 
     let proceed = false;
     if (url === undefined) {
       // Allow url key to be set without value.
       proceed = true;
-    } else if (typeof url === "string") {
+    } else {
       // file: URLs are not allowed.
       if (url.length >= 5 &&
           (url[0] == 'f' || url[0] == 'F') &&
diff --git a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
index 0af34aa..79b2399 100644
--- a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
+++ b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
@@ -96,7 +96,7 @@
     pref_service_.registry()->RegisterListPref(
         translate::TranslatePrefs::kPrefNeverPromptSitesDeprecated);
     pref_service_.registry()->RegisterDictionaryPref(
-        translate::TranslatePrefs::kPrefNeverPromptSitesWithTime);
+        translate::prefs::kPrefNeverPromptSitesWithTime);
     pref_service_.registry()->RegisterDictionaryPref(
         translate::prefs::kPrefAlwaysTranslateList);
     pref_service_.registry()->RegisterDictionaryPref(
diff --git a/ipc/DEPS b/ipc/DEPS
index d58fa4c1..9a8d5bd 100644
--- a/ipc/DEPS
+++ b/ipc/DEPS
@@ -22,7 +22,7 @@
     "+third_party/protobuf/src/google/protobuf/repeated_field.h",
   ],
   "run_all_unittests\.cc": [
+    "+third_party/ipcz/src/test",
     "+third_party/ipcz/src/test_buildflags.h",
-    "+third_party/ipcz/src/test/test_child_launcher.h",
   ],
 }
diff --git a/ipc/run_all_unittests.cc b/ipc/run_all_unittests.cc
index bfd91e2..913cb5e 100644
--- a/ipc/run_all_unittests.cc
+++ b/ipc/run_all_unittests.cc
@@ -9,6 +9,7 @@
 #include "base/test/test_suite.h"
 #include "mojo/core/embedder/embedder.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
+#include "third_party/ipcz/src/test/multinode_test.h"
 #include "third_party/ipcz/src/test_buildflags.h"
 
 #if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
@@ -21,6 +22,7 @@
 #endif
 
   base::TestSuite test_suite(argc, argv);
+  ipcz::test::RegisterMultinodeTests();
   mojo::core::Init();
   base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
   mojo::core::ScopedIPCSupport ipc_support(
diff --git a/media/cast/encoding/external_video_encoder.cc b/media/cast/encoding/external_video_encoder.cc
index d0de515..fdc5980e 100644
--- a/media/cast/encoding/external_video_encoder.cc
+++ b/media/cast/encoding/external_video_encoder.cc
@@ -89,7 +89,7 @@
   }
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS) && ARCH_CPU_64_BITS
+#if BUILDFLAG(IS_CHROMEOS) && ARCH_CPU_X86_64
   // The encoder also doesn't work well with some first party Chromecast
   // devices. See https://crbug.com/1342276 for more information.
   if (base::StartsWith(receiver_model_name, "Chromecast")) {
diff --git a/media/cast/encoding/external_video_encoder_unittest.cc b/media/cast/encoding/external_video_encoder_unittest.cc
index 752f8d9..275118c 100644
--- a/media/cast/encoding/external_video_encoder_unittest.cc
+++ b/media/cast/encoding/external_video_encoder_unittest.cc
@@ -114,7 +114,7 @@
 }
 
 TEST(ExternalVideoEncoderTest, RecommendsExternalVp8EncoderForChromecast) {
-#if BUILDFLAG(IS_CHROMEOS) && ARCH_CPU_64_BITS
+#if BUILDFLAG(IS_CHROMEOS) && ARCH_CPU_X86_64
   EXPECT_TRUE(ExternalVideoEncoder::IsRecommended(
       CODEC_VIDEO_VP8, "Eureka Dongle", kValidVeaProfiles));
   EXPECT_FALSE(ExternalVideoEncoder::IsRecommended(
diff --git a/media/gpu/chromeos/oop_video_decoder.cc b/media/gpu/chromeos/oop_video_decoder.cc
index 471e077..277c7d89 100644
--- a/media/gpu/chromeos/oop_video_decoder.cc
+++ b/media/gpu/chromeos/oop_video_decoder.cc
@@ -164,7 +164,7 @@
                                  InitCB init_cb,
                                  const OutputCB& output_cb,
                                  const WaitingCB& waiting_cb) {
-  VLOGF(2);
+  DVLOGF(2) << config.AsHumanReadableString();
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!init_cb_);
@@ -235,7 +235,7 @@
 
 void OOPVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
                              DecodeCB decode_cb) {
-  VLOGF(2);
+  DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!init_cb_);
@@ -279,6 +279,7 @@
 void OOPVideoDecoder::OnDecodeDone(uint64_t decode_id,
                                    bool is_flushing,
                                    const DecoderStatus& status) {
+  DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!has_error_);
@@ -306,7 +307,7 @@
 }
 
 void OOPVideoDecoder::Reset(base::OnceClosure reset_cb) {
-  VLOGF(2);
+  DVLOGF(2);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!init_cb_);
@@ -427,7 +428,7 @@
     const scoped_refptr<VideoFrame>& frame,
     bool can_read_without_stalling,
     const base::UnguessableToken& release_token) {
-  VLOGF(2);
+  DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!has_error_);
@@ -451,7 +452,7 @@
 }
 
 void OOPVideoDecoder::OnWaiting(WaitingReason reason) {
-  VLOGF(2);
+  DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   CHECK(!has_error_);
diff --git a/media/mojo/clients/win/media_foundation_renderer_client.cc b/media/mojo/clients/win/media_foundation_renderer_client.cc
index dbf3a7b4..1cc5230 100644
--- a/media/mojo/clients/win/media_foundation_renderer_client.cc
+++ b/media/mojo/clients/win/media_foundation_renderer_client.cc
@@ -545,7 +545,9 @@
   }
 
   dcomp_video_frame_ = video_frame;
-  sink_->PaintSingleFrame(dcomp_video_frame_, true);
+  if (!IsFrameServerMode()) {
+    sink_->PaintSingleFrame(dcomp_video_frame_, true);
+  }
 }
 
 void MediaFoundationRendererClient::OnCdmAttached(bool success) {
diff --git a/mojo/core/ipcz_driver/transport.cc b/mojo/core/ipcz_driver/transport.cc
index 831f9a8..98ea0f2d 100644
--- a/mojo/core/ipcz_driver/transport.cc
+++ b/mojo/core/ipcz_driver/transport.cc
@@ -50,6 +50,10 @@
   // Indicates what type of destination the other end of this serialized
   // transport is connected to.
   Transport::Destination destination;
+
+  // Indicates whether the remote process on the other end of this transport
+  // is the same process sending this object.
+  bool is_same_remote_process;
 };
 
 #if BUILDFLAG(IS_WIN)
@@ -338,7 +342,7 @@
   auto object_handles = base::make_span(platform_handles.container());
   switch (header.type) {
     case ObjectBase::kTransport:
-      object = Transport::Deserialize(object_data, object_handles);
+      object = Deserialize(*this, object_data, object_handles);
       break;
 
     case ObjectBase::kSharedBuffer:
@@ -377,7 +381,11 @@
                                         size_t& num_bytes,
                                         size_t& num_handles) {
   num_bytes = sizeof(TransportHeader);
+#if BUILDFLAG(IS_WIN)
+  num_handles = ShouldSerializeProcessHandle(transmitter) ? 2 : 1;
+#else
   num_handles = 1;
+#endif
   return true;
 }
 
@@ -387,25 +395,50 @@
   DCHECK_EQ(sizeof(TransportHeader), data.size());
   auto& header = *reinterpret_cast<TransportHeader*>(data.data());
   header.destination = destination_;
+  header.is_same_remote_process = remote_process_.is_current();
 
-  DCHECK_EQ(1u, handles.size());
+#if BUILDFLAG(IS_WIN)
+  if (ShouldSerializeProcessHandle(transmitter)) {
+    DCHECK_EQ(handles.size(), 2u);
+    DCHECK(remote_process_.IsValid());
+    DCHECK(!remote_process_.is_current());
+    handles[1] = PlatformHandle(
+        base::win::ScopedHandle(remote_process_.Duplicate().Release()));
+  } else {
+    DCHECK_EQ(handles.size(), 1u);
+  }
+#else
+  DCHECK_EQ(handles.size(), 1u);
+#endif
+
   DCHECK(inactive_endpoint_.is_valid());
   handles[0] = inactive_endpoint_.TakePlatformHandle();
-
   return true;
 }
 
 // static
 scoped_refptr<Transport> Transport::Deserialize(
+    Transport& from_transport,
     base::span<const uint8_t> data,
     base::span<PlatformHandle> handles) {
   if (data.size() < sizeof(TransportHeader) || handles.size() < 1) {
     return nullptr;
   }
 
+  base::Process process;
   const auto& header = *reinterpret_cast<const TransportHeader*>(data.data());
+#if BUILDFLAG(IS_WIN)
+  if (handles.size() >= 2 && from_transport.remote_process().IsValid()) {
+    process = base::Process(handles[1].ReleaseHandle());
+  }
+#endif
+  if (header.is_same_remote_process &&
+      from_transport.remote_process().IsValid()) {
+    process = from_transport.remote_process().Duplicate();
+  }
   return base::MakeRefCounted<Transport>(
-      header.destination, PlatformChannelEndpoint(std::move(handles[0])));
+      header.destination, PlatformChannelEndpoint(std::move(handles[0])),
+      std::move(process));
 }
 
 bool Transport::IsIpczTransport() const {
@@ -455,6 +488,16 @@
 #endif
 }
 
+bool Transport::ShouldSerializeProcessHandle(Transport& transmitter) const {
+#if BUILDFLAG(IS_WIN)
+  return remote_process_.IsValid() && !remote_process_.is_current() &&
+         destination_ == kToBroker;
+#else
+  // We have no need for the process handle on other platforms.
+  return false;
+#endif
+}
+
 Transport::PendingTransmission::PendingTransmission() = default;
 
 Transport::PendingTransmission::PendingTransmission(PendingTransmission&&) =
diff --git a/mojo/core/ipcz_driver/transport.h b/mojo/core/ipcz_driver/transport.h
index c9d224a..eae0922 100644
--- a/mojo/core/ipcz_driver/transport.h
+++ b/mojo/core/ipcz_driver/transport.h
@@ -88,6 +88,7 @@
                  base::span<PlatformHandle> handles) override;
 
   static scoped_refptr<Transport> Deserialize(
+      Transport& from_transport,
       base::span<const uint8_t> data,
       base::span<PlatformHandle> handles);
 
@@ -114,6 +115,13 @@
 
   bool CanTransmitHandles() const;
 
+  // Indicates whether this transport should serialize its remote process handle
+  // along with its endpoint handle being serialized for transmission over
+  // `transmitter`. This must only be true if we have a valid remote process
+  // handle and `transmitter` goes to a broker. Always false on non-Windows
+  // platforms.
+  bool ShouldSerializeProcessHandle(Transport& transmitter) const;
+
   const Destination destination_;
   const base::Process remote_process_;
 
diff --git a/mojo/public/js/mojo_bindings_resources.grd b/mojo/public/js/mojo_bindings_resources.grd
index 8af1efb..6fea1f7 100644
--- a/mojo/public/js/mojo_bindings_resources.grd
+++ b/mojo/public/js/mojo_bindings_resources.grd
@@ -64,6 +64,11 @@
           use_base_dir="false"
           resource_path="mojo/mojo/public/mojom/base/file_path.mojom-webui.js"
           type="BINDATA" />
+      <include name="IDR_MOJO_INT128_MOJOM_WEBUI_JS"
+          file="${root_gen_dir}/mojom-webui/mojo/public/mojom/base/int128.mojom-webui.js"
+          use_base_dir="false"
+          resource_path="mojo/mojo/public/mojom/base/int128.mojom-webui.js"
+          type="BINDATA" />
       <include name="IDR_MOJO_READ_ONLY_BUFFER_MOJOM_WEBUI_JS"
           file="${root_gen_dir}/mojom-webui/mojo/public/mojom/base/read_only_buffer.mojom-webui.js"
           use_base_dir="false"
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index e13d8bb..7d2469f 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -171,13 +171,16 @@
 
     // Updates the number of find results for the current search term.  If
     // there are no matches 0 should be passed in.  Only when the plugin has
-    // finished searching should it pass in the final count with final_result
+    // finished searching should it pass in the final count with `final_result`
     // set to true.
     virtual void NotifyNumberOfFindResultsChanged(int total,
                                                   bool final_result) {}
 
-    // Updates the index of the currently selected search item.
-    virtual void NotifySelectedFindResultChanged(int current_find_index) {}
+    // Updates the index of the currently selected search item. Set
+    // `final_result` to true only when there is no subsequent
+    // `NotifyNumberOfFindResultsChanged()` call.
+    virtual void NotifySelectedFindResultChanged(int current_find_index,
+                                                 bool final_result) {}
 
     virtual void NotifyTouchSelectionOccurred() {}
 
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 30f2214..92daa54a 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -841,12 +841,14 @@
       kFindResultCooldown);
 }
 
-void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index) {
+void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index,
+                                                       bool final_result) {
   if (find_identifier_ == -1 || !client_->PluginContainer())
     return;
 
   DCHECK_GE(current_find_index, -1);
-  client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1);
+  client_->ReportFindInPageSelection(find_identifier_, current_find_index + 1,
+                                     final_result);
 }
 
 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 4fc0ce8..5771ba23 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -133,7 +133,9 @@
                                             bool final_update) = 0;
 
     // Notify the web plugin container about the selected find result in plugin.
-    virtual void ReportFindInPageSelection(int identifier, int index) = 0;
+    virtual void ReportFindInPageSelection(int identifier,
+                                           int index,
+                                           bool final_update) = 0;
 
     // Notify the web plugin container about find result tickmarks.
     virtual void ReportFindInPageTickmarks(
@@ -278,7 +280,8 @@
   void UpdateCursor(ui::mojom::CursorType new_cursor_type) override;
   void UpdateTickMarks(const std::vector<gfx::Rect>& tickmarks) override;
   void NotifyNumberOfFindResultsChanged(int total, bool final_result) override;
-  void NotifySelectedFindResultChanged(int current_find_index) override;
+  void NotifySelectedFindResultChanged(int current_find_index,
+                                       bool final_result) override;
   void GetDocumentPassword(
       base::OnceCallback<void(const std::string&)> callback) override;
   void Beep() override;
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index d7f149b..32ce138 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -254,7 +254,7 @@
 
   MOCK_METHOD(void, ReportFindInPageMatchCount, (int, int, bool), (override));
 
-  MOCK_METHOD(void, ReportFindInPageSelection, (int, int), (override));
+  MOCK_METHOD(void, ReportFindInPageSelection, (int, int, bool), (override));
 
   MOCK_METHOD(void,
               ReportFindInPageTickmarks,
diff --git a/pdf/pdfium/findtext_unittest.cc b/pdf/pdfium/findtext_unittest.cc
index 242b1d58..3aa922e 100644
--- a/pdf/pdfium/findtext_unittest.cc
+++ b/pdf/pdfium/findtext_unittest.cc
@@ -28,7 +28,7 @@
 
   // PDFEngine::Client:
   MOCK_METHOD(void, NotifyNumberOfFindResultsChanged, (int, bool), (override));
-  MOCK_METHOD(void, NotifySelectedFindResultChanged, (int), (override));
+  MOCK_METHOD(void, NotifySelectedFindResultChanged, (int, bool), (override));
 
   std::vector<SearchStringResult> SearchString(const char16_t* string,
                                                const char16_t* term,
@@ -73,7 +73,8 @@
 
   EXPECT_CALL(client,
               NotifyNumberOfFindResultsChanged(1, /*final_result=*/false));
-  EXPECT_CALL(client, NotifySelectedFindResultChanged(0));
+  EXPECT_CALL(client,
+              NotifySelectedFindResultChanged(0, /*final_result=*/false));
   for (int i = 2; i < count + 1; ++i) {
     EXPECT_CALL(client,
                 NotifyNumberOfFindResultsChanged(i, /*final_result=*/false));
@@ -185,14 +186,17 @@
   engine->StartFind("world", /*case_sensitive=*/true);
 
   EXPECT_CALL(client, NotifyNumberOfFindResultsChanged(_, _)).Times(0);
-  EXPECT_CALL(client, NotifySelectedFindResultChanged(1));
+  EXPECT_CALL(client,
+              NotifySelectedFindResultChanged(1, /*final_result=*/true));
 
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/true));
 
-  EXPECT_CALL(client, NotifySelectedFindResultChanged(2));
+  EXPECT_CALL(client,
+              NotifySelectedFindResultChanged(2, /*final_result=*/true));
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/true));
 
-  EXPECT_CALL(client, NotifySelectedFindResultChanged(1));
+  EXPECT_CALL(client,
+              NotifySelectedFindResultChanged(1, /*final_result=*/true));
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/false));
 }
 
@@ -208,8 +212,10 @@
   {
     InSequence sequence;
 
-    EXPECT_CALL(client, NotifySelectedFindResultChanged(1));
-    EXPECT_CALL(client, NotifySelectedFindResultChanged(2));
+    EXPECT_CALL(client,
+                NotifySelectedFindResultChanged(1, /*final_result=*/true));
+    EXPECT_CALL(client,
+                NotifySelectedFindResultChanged(2, /*final_result=*/true));
   }
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/true));
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/true));
@@ -229,7 +235,8 @@
   {
     InSequence sequence;
 
-    EXPECT_CALL(client, NotifySelectedFindResultChanged(2));
+    EXPECT_CALL(client,
+                NotifySelectedFindResultChanged(2, /*final_result=*/true));
   }
   ASSERT_TRUE(engine->SelectFindResult(/*forward=*/true));
 }
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index ddb8d4a5..38d9d82 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -1761,6 +1761,7 @@
       character_to_start_searching_from = old_selection[0].char_index();
       last_page_to_search_ = next_page_to_search_;
     }
+    search_in_progress_ = true;
   }
 
   int current_page = next_page_to_search_;
@@ -1801,6 +1802,8 @@
        (pages_.size() > 1 && current_page == next_page_to_search_));
 
   if (end_of_search) {
+    search_in_progress_ = false;
+
     // Send the final notification.
     client_->NotifyNumberOfFindResultsChanged(find_results_.size(), true);
     return;
@@ -2023,8 +2026,9 @@
     if ((forward && current_index >= last_index) ||
         (!forward && current_index == 0)) {
       current_find_index_.reset();
-      client_->NotifySelectedFindResultChanged(-1);
-      client_->NotifyNumberOfFindResultsChanged(find_results_.size(), true);
+      client_->NotifySelectedFindResultChanged(-1, /*final_result=*/false);
+      client_->NotifyNumberOfFindResultsChanged(find_results_.size(),
+                                                /*final_result=*/true);
       return true;
     }
     int increment = forward ? 1 : -1;
@@ -2070,7 +2074,8 @@
     }
   }
 
-  client_->NotifySelectedFindResultChanged(current_find_index_.value());
+  client_->NotifySelectedFindResultChanged(
+      current_find_index_.value(), /*final_result=*/!search_in_progress_);
   return true;
 }
 
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index c0f83d9..998071c 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -726,6 +726,8 @@
   std::string current_find_text_;
   // The results found.
   std::vector<PDFiumRange> find_results_;
+  // Whether a search is in progress.
+  bool search_in_progress_ = false;
   // Which page to search next.
   int next_page_to_search_ = -1;
   // Where to stop searching.
diff --git a/pdf/preview_mode_client.cc b/pdf/preview_mode_client.cc
index 28ffbe41..2c9fcaa 100644
--- a/pdf/preview_mode_client.cc
+++ b/pdf/preview_mode_client.cc
@@ -72,8 +72,8 @@
   NOTREACHED();
 }
 
-void PreviewModeClient::NotifySelectedFindResultChanged(
-    int current_find_index) {
+void PreviewModeClient::NotifySelectedFindResultChanged(int current_find_index,
+                                                        bool final_result) {
   NOTREACHED();
 }
 
diff --git a/pdf/preview_mode_client.h b/pdf/preview_mode_client.h
index 43514fd..c96bbfc 100644
--- a/pdf/preview_mode_client.h
+++ b/pdf/preview_mode_client.h
@@ -41,7 +41,8 @@
   void UpdateCursor(ui::mojom::CursorType cursor_type) override;
   void UpdateTickMarks(const std::vector<gfx::Rect>& tickmarks) override;
   void NotifyNumberOfFindResultsChanged(int total, bool final_result) override;
-  void NotifySelectedFindResultChanged(int current_find_index) override;
+  void NotifySelectedFindResultChanged(int current_find_index,
+                                       bool final_result) override;
   void GetDocumentPassword(
       base::OnceCallback<void(const std::string&)> callback) override;
   void Alert(const std::string& message) override;
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 9b4b488..aa398af 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -101,6 +101,7 @@
     "input_injector.h",
     "input_injector_metadata.h",
     "sas_injector.h",
+    "usage_stats_consent.h",
     "worker_process_ipc_delegate.h",
   ]
   if (!is_chromeos_ash) {
@@ -368,7 +369,6 @@
     "token_validator_base.h",
     "token_validator_factory_impl.cc",
     "token_validator_factory_impl.h",
-    "usage_stats_consent.h",
     "xmpp_register_support_host_request.cc",
     "xmpp_register_support_host_request.h",
     "xsession_chooser_ui.inc",
diff --git a/remoting/host/remote_open_url/BUILD.gn b/remoting/host/remote_open_url/BUILD.gn
index 10cc4e32..f3fa656 100644
--- a/remoting/host/remote_open_url/BUILD.gn
+++ b/remoting/host/remote_open_url/BUILD.gn
@@ -79,6 +79,7 @@
     "//base",
     "//base:i18n",
     "//mojo/core/embedder",
+    "//remoting/base:breakpad",
     "//remoting/host",
     "//remoting/host:chromoting_host_services_client",
     "//remoting/host:resources",
diff --git a/remoting/host/remote_open_url/remote_open_url_main.cc b/remoting/host/remote_open_url/remote_open_url_main.cc
index 1bce849..5e2bd8e 100644
--- a/remoting/host/remote_open_url/remote_open_url_main.cc
+++ b/remoting/host/remote_open_url/remote_open_url_main.cc
@@ -13,12 +13,14 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "mojo/core/embedder/embedder.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
+#include "remoting/base/breakpad.h"
 #include "remoting/base/host_settings.h"
 #include "remoting/base/logging.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/chromoting_host_services_client.h"
 #include "remoting/host/remote_open_url/remote_open_url_client.h"
 #include "remoting/host/resources.h"
+#include "remoting/host/usage_stats_consent.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace remoting {
@@ -35,6 +37,12 @@
   base::CommandLine::Init(argc, argv);
   InitHostLogging();
 
+#if defined(REMOTING_ENABLE_BREAKPAD)
+  if (IsUsageStatsAllowed()) {
+    InitializeCrashReporting();
+  }
+#endif  // defined(REMOTING_ENABLE_BREAKPAD)
+
   if (!ChromotingHostServicesClient::Initialize()) {
     return kInitializationFailed;
   }
diff --git a/remoting/host/security_key/BUILD.gn b/remoting/host/security_key/BUILD.gn
index 8423fdf3..d843abf 100644
--- a/remoting/host/security_key/BUILD.gn
+++ b/remoting/host/security_key/BUILD.gn
@@ -66,6 +66,7 @@
     ":security_key",
     "//base:debugging_buildflags",
     "//mojo/core/embedder",
+    "//remoting/base:breakpad",
     "//remoting/host:common",
     "//remoting/host/base",
   ]
diff --git a/remoting/host/security_key/remote_security_key_main.cc b/remoting/host/security_key/remote_security_key_main.cc
index a46c6ac5..293afeb 100644
--- a/remoting/host/security_key/remote_security_key_main.cc
+++ b/remoting/host/security_key/remote_security_key_main.cc
@@ -17,10 +17,12 @@
 #include "build/build_config.h"
 #include "mojo/core/embedder/embedder.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
+#include "remoting/base/breakpad.h"
 #include "remoting/base/logging.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/security_key/security_key_ipc_client.h"
 #include "remoting/host/security_key/security_key_message_handler.h"
+#include "remoting/host/usage_stats_consent.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <windows.h>
@@ -88,6 +90,12 @@
   base::CommandLine::Init(argc, argv);
   remoting::InitHostLogging();
 
+#if defined(REMOTING_ENABLE_BREAKPAD)
+  if (IsUsageStatsAllowed()) {
+    InitializeCrashReporting();
+  }
+#endif  // defined(REMOTING_ENABLE_BREAKPAD)
+
   return StartRemoteSecurityKey();
 }
 
diff --git a/remoting/host/setup/BUILD.gn b/remoting/host/setup/BUILD.gn
index 14da0a2..170bf6f 100644
--- a/remoting/host/setup/BUILD.gn
+++ b/remoting/host/setup/BUILD.gn
@@ -103,6 +103,7 @@
     "//mojo/core/embedder",
     "//net",
     "//remoting/base:base",
+    "//remoting/base:breakpad",
     "//remoting/host:common",
     "//remoting/host/setup",
     "//remoting/host/setup:start_host_main_headers",
diff --git a/remoting/host/setup/start_host_main.cc b/remoting/host/setup/start_host_main.cc
index d5b2490..4080b55 100644
--- a/remoting/host/setup/start_host_main.cc
+++ b/remoting/host/setup/start_host_main.cc
@@ -19,10 +19,12 @@
 #include "build/build_config.h"
 #include "mojo/core/embedder/embedder.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "remoting/base/breakpad.h"
 #include "remoting/base/logging.h"
 #include "remoting/base/url_request_context_getter.h"
 #include "remoting/host/setup/host_starter.h"
 #include "remoting/host/setup/pin_validator.h"
+#include "remoting/host/usage_stats_consent.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/transitional_url_loader_factory_owner.h"
 
@@ -146,6 +148,12 @@
       logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
   logging::InitLogging(settings);
 
+#if defined(REMOTING_ENABLE_BREAKPAD)
+  if (IsUsageStatsAllowed()) {
+    InitializeCrashReporting();
+  }
+#endif  // defined(REMOTING_ENABLE_BREAKPAD)
+
   base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
       "RemotingHostSetup");
 
diff --git a/remoting/host/webauthn/BUILD.gn b/remoting/host/webauthn/BUILD.gn
index 523fef2..5a34b48b 100644
--- a/remoting/host/webauthn/BUILD.gn
+++ b/remoting/host/webauthn/BUILD.gn
@@ -52,7 +52,9 @@
     "//base",
     "//mojo/core/embedder:embedder",
     "//remoting/base",
+    "//remoting/base:breakpad",
     "//remoting/host:chromoting_host_services_client",
+    "//remoting/host:common_headers",
     "//remoting/host/base",
     "//remoting/host/native_messaging",
   ]
diff --git a/remoting/host/webauthn/remote_webauthn_main.cc b/remoting/host/webauthn/remote_webauthn_main.cc
index 734d98e..21ffeb49 100644
--- a/remoting/host/webauthn/remote_webauthn_main.cc
+++ b/remoting/host/webauthn/remote_webauthn_main.cc
@@ -18,11 +18,13 @@
 #include "mojo/core/embedder/embedder.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
 #include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/base/breakpad.h"
 #include "remoting/base/logging.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/chromoting_host_services_client.h"
 #include "remoting/host/native_messaging/native_messaging_pipe.h"
 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
+#include "remoting/host/usage_stats_consent.h"
 #include "remoting/host/webauthn/remote_webauthn_caller_security_utils.h"
 #include "remoting/host/webauthn/remote_webauthn_native_messaging_host.h"
 
@@ -41,6 +43,12 @@
   base::CommandLine::Init(argc, argv);
   InitHostLogging();
 
+#if defined(REMOTING_ENABLE_BREAKPAD)
+  if (IsUsageStatsAllowed()) {
+    InitializeCrashReporting();
+  }
+#endif  // defined(REMOTING_ENABLE_BREAKPAD)
+
   if (!IsLaunchedByTrustedProcess()) {
     LOG(ERROR) << "Current process is not launched by a trusted process.";
     return kNoPermissionExitCode;
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 7a096b9..14e5c776 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -429,13 +429,11 @@
       "//third_party/skia/src/ports/SkOSFile_win.cpp",
       "//third_party/skia/src/ports/SkRemotableFontMgr_win_dw.cpp",
       "//third_party/skia/src/ports/SkScalerContext_win_dw.cpp",
-      "//third_party/skia/src/ports/SkTLS_win.cpp",
       "//third_party/skia/src/ports/SkTypeface_win_dw.cpp",
     ]
   } else {
     sources += [
       "//third_party/skia/src/ports/SkOSFile_posix.cpp",
-      "//third_party/skia/src/ports/SkTLS_pthread.cpp",
     ]
   }
 
diff --git a/skia/OWNERS b/skia/OWNERS
index ef55d67b..2d792d9 100644
--- a/skia/OWNERS
+++ b/skia/OWNERS
@@ -3,9 +3,9 @@
 brianosman@google.com
 bsalomon@google.com
 bungeman@google.com
+egdaniel@google.com
 fmalita@chromium.org
 michaelludwig@google.com
-reed@google.com
 robertphillips@google.com
 scroggo@google.com
 senorblanco@chromium.org
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 289671a1..a42fc44 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -197,6 +197,8 @@
 //
 // Remove these as we update our sites.
 
+#define SK_LEGACY_LAYER_BOUNDS_EXPANSION  // skbug.com/12083, skbug.com/12303
+
 // Workaround for poor anisotropic mipmap quality,
 // pending Skia ripmap support.
 // (https://bugs.chromium.org/p/skia/issues/detail?id=4863)
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 48c090f..23f5fb5 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -2393,6 +2393,26 @@
         "test_id_prefix": "ninja://ash/components:ash_components_unittests/"
       },
       {
+        "ci_only": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04",
+              "pool": "chrome.tests",
+              "ssd": "0"
+            }
+          ],
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index b940a40..a2abdd9 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1598,6 +1598,24 @@
         "test_id_prefix": "ninja://ash/components:ash_components_unittests/"
       },
       {
+        "ci_only": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3198,6 +3216,25 @@
         "test_id_prefix": "ninja://ash/components:ash_components_unittests/"
       },
       {
+        "ci_only": true,
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
         "args": [
           "--enable-pixel-output-in-tests",
           "--git-revision=${got_revision}"
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 6e088688..e73e4f3d 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -1686,6 +1686,30 @@
         "args": [
           "--test-launcher-print-test-stdio=always"
         ],
+        "ci_only": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 7200,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
+        "args": [
+          "--test-launcher-print-test-stdio=always"
+        ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -94464,6 +94488,25 @@
         "test_id_prefix": "ninja://ash/components:ash_components_unittests/"
       },
       {
+        "ci_only": true,
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
         "isolate_profile_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 1fc0456c..aca138f 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -5289,6 +5289,27 @@
         "args": [
           "--test-launcher-print-test-stdio=always"
         ],
+        "ci_only": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
+        "args": [
+          "--test-launcher-print-test-stdio=always"
+        ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -7131,6 +7152,28 @@
         "args": [
           "--test-launcher-print-test-stdio=always"
         ],
+        "ci_only": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_browsertests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_browsertests/"
+      },
+      {
+        "args": [
+          "--test-launcher-print-test-stdio=always"
+        ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter b/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
index b193ab92..6f2f5e9e 100644
--- a/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
+++ b/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
@@ -45,3 +45,5 @@
 -ChromeVoxLiveRegionsTest.LiveRegionAddElement
 -ChromeVoxLiveRegionsTest.LiveRegionRemoveElement
 -ChromeVoxUserActionMonitorTest.Gestures
+# TODO(crbug.com/1339456) Disabled when switch to LacrosOnly.
+-DesksTemplatesClientLacrosTest.SystemUILaunchBrowser
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index a0a1724d..0745fcd 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -180,6 +180,10 @@
     "label": "//ash/components:ash_components_unittests",
     "type": "windowed_test_launcher",
   },
+  "ash_crosapi_browsertests": {
+    "label": "//chrome/test:ash_crosapi_browsertests",
+    "type": "windowed_test_launcher",
+  },
   "ash_webui_unittests": {
     "label": "//ash/webui:ash_webui_unittests",
     "type": "windowed_test_launcher",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index cc34d90..c99ff03 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3779,6 +3779,10 @@
     'linux_chromeos_specific_gtests': {
       # Chrome OS only.
       'ash_components_unittests': {},
+      # TODO(crbug.com/1351793) Enable on CQ when stable.
+      'ash_crosapi_browsertests': {
+        'ci_only': True,
+      },
       'ash_unittests': {
         'swarming': {
           'shards': 5,
diff --git a/testing/multiprocess_func_list.cc b/testing/multiprocess_func_list.cc
index f96c2b5..740bbe7 100644
--- a/testing/multiprocess_func_list.cc
+++ b/testing/multiprocess_func_list.cc
@@ -12,6 +12,8 @@
 
 namespace {
 
+ChildProcessTestRunner g_test_runner = nullptr;
+
 struct ProcessFunctions {
   ProcessFunctions() : main(NULL), setup(NULL) {}
   ProcessFunctions(TestMainFunctionPtr main, SetupFunctionPtr setup)
@@ -40,7 +42,18 @@
       ProcessFunctions(main_func_ptr, setup_func_ptr);
 }
 
+void SetChildProcessTestRunner(ChildProcessTestRunner runner) {
+  g_test_runner = runner;
+}
+
 int InvokeChildProcessTest(const std::string& test_name) {
+  if (g_test_runner) {
+    return g_test_runner(test_name);
+  }
+  return InvokeChildProcessTestMain(test_name);
+}
+
+int InvokeChildProcessTestMain(const std::string& test_name) {
   MultiProcessTestMap& func_lookup_table = GetMultiprocessFuncMap();
   MultiProcessTestMap::iterator it = func_lookup_table.find(test_name);
   if (it != func_lookup_table.end()) {
diff --git a/testing/multiprocess_func_list.h b/testing/multiprocess_func_list.h
index c3d2f1f7..5b2caef7 100644
--- a/testing/multiprocess_func_list.h
+++ b/testing/multiprocess_func_list.h
@@ -45,10 +45,18 @@
                          SetupFunctionPtr setup_func_ptr);
 };
 
-// Invoke the main function of a test previously registered with
-// MULTIPROCESS_TEST_MAIN()
+using ChildProcessTestRunner = int (*)(const std::string&);
+void SetChildProcessTestRunner(ChildProcessTestRunner runner);
+
+// Invokes the ChildProcessTestRunner callback with `test_name` if the callback
+// is not null. Otherwise invokes test associated main function previously
+// registered with MULTIPROCESS_TEST_MAIN().
 int InvokeChildProcessTest(const std::string& test_name);
 
+// Invokes a the main function of a test previously registered with
+// MULTIPROCESS_TEST_MAIN().
+int InvokeChildProcessTestMain(const std::string& test_name);
+
 // This macro creates a global MultiProcessTest::AppendMultiProcessTest object
 // whose constructor does the work of adding the global mapping.
 #define MULTIPROCESS_TEST_MAIN(test_main) \
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 5329bbe..099b7603 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6717,8 +6717,7 @@
                         "OmniboxLocalZeroSuggestAgeThreshold",
                         "OmniboxMaxURLMatches",
                         "OmniboxRetainSuggestionsWithHeaders",
-                        "OmniboxUIExperimentMaxAutocompleteMatches",
-                        "ZeroSuggestPrefetching"
+                        "OmniboxUIExperimentMaxAutocompleteMatches"
                     ],
                     "disable_features": [
                         "UseSharedInstanceForZeroSuggestPrefetching"
@@ -7852,7 +7851,8 @@
                         "InterestGroupStorage",
                         "NoncedPartitionedCookies",
                         "PrivacySandboxAdsAPIs",
-                        "PrivacySandboxSettings3"
+                        "PrivacySandboxSettings3",
+                        "SharedStorageAPI"
                     ]
                 }
             ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 02aa250c..db91a892 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -503,6 +503,11 @@
 #endif
 };
 
+// Enable an experimental extension to the StorageAccessAPI.
+// https://crbug.com/1351540
+const base::Feature kStorageAccessAPIForSiteExtension{
+    "StorageAccessAPIForSiteExtension", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable text snippets in URL fragments. https://crbug.com/919204.
 const base::Feature kTextFragmentAnchor{"TextFragmentAnchor",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/third_party/blink/common/mediastream/media_stream_mojom_traits.cc b/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
index bcfbb773..85871e025 100644
--- a/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
+++ b/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
@@ -69,6 +69,7 @@
   out->hotword_enabled = input.hotword_enabled();
   out->disable_local_echo = input.disable_local_echo();
   out->exclude_system_audio = input.exclude_system_audio();
+  out->exclude_self_browser_surface = input.exclude_self_browser_surface();
   out->request_pan_tilt_zoom_permission =
       input.request_pan_tilt_zoom_permission();
   out->request_all_screens = input.request_all_screens();
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 2c14e34a..7010377 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -214,6 +214,8 @@
 BLINK_COMMON_EXPORT extern const base::Feature kServiceWorkerUpdateDelay;
 BLINK_COMMON_EXPORT extern const base::Feature kSpeculationRulesPrefetchProxy;
 BLINK_COMMON_EXPORT extern const base::Feature kStopInBackground;
+BLINK_COMMON_EXPORT extern const base::Feature
+    kStorageAccessAPIForSiteExtension;
 BLINK_COMMON_EXPORT extern const base::Feature kTextFragmentAnchor;
 BLINK_COMMON_EXPORT extern const base::Feature kCssSelectorFragmentAnchor;
 BLINK_COMMON_EXPORT extern const base::Feature kDropInputEventsBeforeFirstPaint;
diff --git a/third_party/blink/public/common/mediastream/media_stream_controls.h b/third_party/blink/public/common/mediastream/media_stream_controls.h
index 6323af93..f86f91c 100644
--- a/third_party/blink/public/common/mediastream/media_stream_controls.h
+++ b/third_party/blink/public/common/mediastream/media_stream_controls.h
@@ -55,6 +55,7 @@
   bool hotword_enabled = false;
   bool disable_local_echo = false;
   bool exclude_system_audio = false;
+  bool exclude_self_browser_surface = false;
   bool request_pan_tilt_zoom_permission = false;
   bool request_all_screens = false;
 };
diff --git a/third_party/blink/public/common/mediastream/media_stream_mojom_traits.h b/third_party/blink/public/common/mediastream/media_stream_mojom_traits.h
index f8cb97cf..5501d94b 100644
--- a/third_party/blink/public/common/mediastream/media_stream_mojom_traits.h
+++ b/third_party/blink/public/common/mediastream/media_stream_mojom_traits.h
@@ -108,6 +108,11 @@
     return controls.exclude_system_audio;
   }
 
+  static bool exclude_self_browser_surface(
+      const blink::StreamControls& controls) {
+    return controls.exclude_self_browser_surface;
+  }
+
   static bool request_pan_tilt_zoom_permission(
       const blink::StreamControls& controls) {
     return controls.request_pan_tilt_zoom_permission;
diff --git a/third_party/blink/public/mojom/mediastream/media_stream.mojom b/third_party/blink/public/mojom/mediastream/media_stream.mojom
index 5cf1603..08e7eb53 100644
--- a/third_party/blink/public/mojom/mediastream/media_stream.mojom
+++ b/third_party/blink/public/mojom/mediastream/media_stream.mojom
@@ -123,6 +123,7 @@
   bool hotword_enabled;
   bool disable_local_echo;
   bool exclude_system_audio;
+  bool exclude_self_browser_surface;
   bool request_pan_tilt_zoom_permission;
   bool request_all_screens;
 };
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 94ce877..410dc6e 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3650,6 +3650,7 @@
   kReplacedElementPaintedWithLargeOverflow = 4329,
   kFlexboxAbsPosJustifyContent = 4330,
   kMultipleFetchHandlersInServiceWorker = 4331,
+  kStorageAccessAPI_requestStorageAccessForSite_Method = 4332,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/mojom/webid/federated_auth_request.mojom b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
index 675d957..1df8053f 100644
--- a/third_party/blink/public/mojom/webid/federated_auth_request.mojom
+++ b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
@@ -56,9 +56,9 @@
 // This interface is called from a renderer process and implemented in the
 // browser process.
 interface FederatedAuthRequest {
-  // Requests a token to be generated, given an IdentityProvider.
+  // Requests a token to be generated, given an array of IdentityProviders.
   // Returns the raw content of the token.
-  RequestToken(IdentityProvider identity_provider_ptr,
+  RequestToken(array<IdentityProvider> identity_providers,
                  bool prefer_auto_sign_in) =>
       (RequestTokenStatus status, string? token);
 
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 48a65f7..6c7f175 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -162,6 +162,9 @@
   BLINK_PLATFORM_EXPORT static void EnableSharedAutofill(bool);
   BLINK_PLATFORM_EXPORT static void EnableSharedStorageAPI(bool);
   BLINK_PLATFORM_EXPORT static void EnableSharedWorker(bool);
+  BLINK_PLATFORM_EXPORT static void EnableStorageAccessAPIForSiteExtension(
+      bool);
+  BLINK_PLATFORM_EXPORT static bool IsStorageAccessAPIForSiteExtensionEnabled();
   BLINK_PLATFORM_EXPORT static void EnableTextFragmentAnchor(bool);
   BLINK_PLATFORM_EXPORT static void EnableCSSSelectorFragmentAnchor(bool);
   BLINK_PLATFORM_EXPORT static void EnableTopicsAPI(bool);
diff --git a/third_party/blink/public/web/web_plugin_container.h b/third_party/blink/public/web/web_plugin_container.h
index 2e31e4f1..073d56fc 100644
--- a/third_party/blink/public/web/web_plugin_container.h
+++ b/third_party/blink/public/web/web_plugin_container.h
@@ -133,7 +133,9 @@
   virtual void ReportFindInPageMatchCount(int identifier,
                                           int total,
                                           bool final_update) = 0;
-  virtual void ReportFindInPageSelection(int identifier, int index) = 0;
+  virtual void ReportFindInPageSelection(int identifier,
+                                         int index,
+                                         bool final_update) = 0;
 
   virtual float PageScaleFactor() = 0;
   virtual float PageZoomFactor() = 0;
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 9a957c0..93ebfd1c 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1364,6 +1364,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_sdp_semantics.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_action.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_action.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_self_capture_preference_enum.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_self_capture_preference_enum.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_state.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_state.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_update_via_cache.cc",
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 817a321..45835c81 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -6074,6 +6074,94 @@
   return promise;
 }
 
+ScriptPromise Document::requestStorageAccessForSite(ScriptState* script_state,
+                                                    const AtomicString& site) {
+  ScriptPromiseResolver* resolver =
+      MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+
+  // Access the promise first to ensure it is created so that the proper state
+  // can be changed when it is resolved or rejected.
+  ScriptPromise promise = resolver->Promise();
+
+  if (!GetFrame()) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Must not be called from a detached "
+        "frame."));
+
+    resolver->Reject();
+    return promise;
+  }
+
+  const bool has_user_gesture =
+      LocalFrame::HasTransientUserActivation(GetFrame());
+  if (!has_user_gesture) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Must be handling a user gesture to "
+        "use."));
+
+    resolver->Reject();
+    return promise;
+  }
+
+  if (!IsInOutermostMainFrame()) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Only supported in primary top-level "
+        "browsing contexts."));
+    resolver->Reject();
+    return promise;
+  }
+
+  if (dom_window_->GetSecurityOrigin()->IsOpaque()) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Cannot be used by opaque origins."));
+
+    resolver->Reject();
+    return promise;
+  }
+
+  KURL site_as_kurl{site};
+  if (!site_as_kurl.IsValid()) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Invalid site parameter."));
+    resolver->Reject();
+    return promise;
+  }
+
+  scoped_refptr<SecurityOrigin> supplied_origin =
+      SecurityOrigin::Create(site_as_kurl);
+  if (supplied_origin->IsOpaque()) {
+    AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kSecurity,
+        mojom::blink::ConsoleMessageLevel::kError,
+        "requestStorageAccessForSite: Invalid site parameter."));
+    resolver->Reject();
+    return promise;
+  }
+
+  if (dom_window_->GetSecurityOrigin()->IsSameSiteWith(supplied_origin.get())) {
+    // Access is not actually disabled, so accept the request.
+    resolver->Resolve();
+    return promise;
+  }
+
+  resolver->Reject();
+  AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+      mojom::blink::ConsoleMessageSource::kSecurity,
+      mojom::blink::ConsoleMessageLevel::kError,
+      "requestStorageAccessForSite: Rejecting experimental API request."));
+  return promise;
+}
+
 ScriptPromise Document::requestStorageAccess(ScriptState* script_state) {
   DCHECK(GetFrame());
   ScriptPromiseResolver* resolver =
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index facd4fdc..b7c74ca 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1197,6 +1197,8 @@
   // may otherwise be blocked.
   ScriptPromise hasStorageAccess(ScriptState* script_state);
   ScriptPromise requestStorageAccess(ScriptState* script_state);
+  ScriptPromise requestStorageAccessForSite(ScriptState* script_state,
+                                            const AtomicString& site);
 
   // Fragment directive API, currently used to feature detect text-fragments.
   // https://wicg.github.io/scroll-to-text-fragment/#feature-detectability
diff --git a/third_party/blink/renderer/core/dom/document.idl b/third_party/blink/renderer/core/dom/document.idl
index 88767b9..e024618 100644
--- a/third_party/blink/renderer/core/dom/document.idl
+++ b/third_party/blink/renderer/core/dom/document.idl
@@ -183,6 +183,7 @@
     // Storage Access API
     [CallWith=ScriptState, NewObject, RuntimeEnabled=StorageAccessAPI, MeasureAs=StorageAccessAPI_HasStorageAccess_Method] Promise<boolean> hasStorageAccess();
     [CallWith=ScriptState, NewObject, RuntimeEnabled=StorageAccessAPI, MeasureAs=StorageAccessAPI_requestStorageAccess_Method] Promise<void> requestStorageAccess();
+    [CallWith=ScriptState, NewObject, RuntimeEnabled=StorageAccessAPIForSiteExtension, MeasureAs=StorageAccessAPI_requestStorageAccessForSite_Method] Promise<void> requestStorageAccessForSite(USVString site);
 
     // Text fragment directive API
     // https://wicg.github.io/scroll-to-text-fragment/#feature-detectability
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
index af1e17b..3c22420 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
@@ -320,19 +320,20 @@
   if (!frame)
     return;
 
-  final_find_update_ = final_update;
   frame->GetFindInPage()->ReportFindInPageMatchCount(identifier, total,
                                                      final_update);
 }
 
 void WebPluginContainerImpl::ReportFindInPageSelection(int identifier,
-                                                       int index) {
+                                                       int index,
+                                                       bool final_update) {
   WebLocalFrameImpl* frame =
       WebLocalFrameImpl::FromFrame(element_->GetDocument().GetFrame());
   if (!frame)
     return;
-  frame->GetFindInPage()->ReportFindInPageSelection(
-      identifier, index, gfx::Rect(), final_find_update_);
+
+  frame->GetFindInPage()->ReportFindInPageSelection(identifier, index,
+                                                    gfx::Rect(), final_update);
 }
 
 float WebPluginContainerImpl::PageScaleFactor() {
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.h b/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
index b43e30f..a7ff05f 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
@@ -137,7 +137,9 @@
   void ReportFindInPageMatchCount(int identifier,
                                   int total,
                                   bool final_update) override;
-  void ReportFindInPageSelection(int identifier, int index) override;
+  void ReportFindInPageSelection(int identifier,
+                                 int index,
+                                 bool final_update) override;
   float PageScaleFactor() override;
   float PageZoomFactor() override;
   void SetCcLayer(cc::Layer*) override;
@@ -234,10 +236,6 @@
   cc::Layer* layer_ = nullptr;
   TouchEventRequestType touch_event_request_type_ = kTouchEventRequestTypeNone;
   bool wants_wheel_events_ = false;
-
-  // Whether the most recent `ReportFindInPageMatchCount()` call was the final
-  // update or not.
-  bool final_find_update_ = false;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 819b99c..c3785349 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -2579,18 +2579,11 @@
   Vector<WebScriptSource> script_sources;
   script_sources.Append(sources.data(),
                         base::checked_cast<wtf_size_t>(sources.size()));
-  auto* executor = MakeGarbageCollected<PausableScriptExecutor>(
+
+  PausableScriptExecutor::CreateAndRun(
       DomWindow(), std::move(world), std::move(script_sources), user_gesture,
-      want_result_option, std::move(callback));
-  executor->set_wait_for_promise(promise_behavior);
-  switch (evaluation_timing) {
-    case mojom::blink::EvaluationTiming::kAsynchronous:
-      executor->RunAsync(blocking_option);
-      break;
-    case mojom::blink::EvaluationTiming::kSynchronous:
-      executor->Run();
-      break;
-  }
+      evaluation_timing, blocking_option, want_result_option, promise_behavior,
+      std::move(callback));
 }
 
 void LocalFrame::SetEvictCachedSessionStorageOnFreezeOrUnload() {
diff --git a/third_party/blink/renderer/core/frame/pausable_script_executor.cc b/third_party/blink/renderer/core/frame/pausable_script_executor.cc
index 5e7e5b8..bfefb80 100644
--- a/third_party/blink/renderer/core/frame/pausable_script_executor.cc
+++ b/third_party/blink/renderer/core/frame/pausable_script_executor.cc
@@ -143,35 +143,21 @@
 
 class WebScriptExecutor : public PausableScriptExecutor::Executor {
  public:
-  WebScriptExecutor(Vector<WebScriptSource>,
-                    int32_t world_id,
-                    mojom::blink::UserActivationOption);
+  WebScriptExecutor(Vector<WebScriptSource>, int32_t world_id);
 
   Vector<v8::Local<v8::Value>> Execute(LocalDOMWindow*) override;
 
  private:
   Vector<WebScriptSource> sources_;
   int32_t world_id_;
-  mojom::blink::UserActivationOption user_gesture_;
 };
 
-WebScriptExecutor::WebScriptExecutor(
-    Vector<WebScriptSource> sources,
-    int32_t world_id,
-    mojom::blink::UserActivationOption user_gesture)
-    : sources_(std::move(sources)),
-      world_id_(world_id),
-      user_gesture_(user_gesture) {}
+WebScriptExecutor::WebScriptExecutor(Vector<WebScriptSource> sources,
+                                     int32_t world_id)
+    : sources_(std::move(sources)), world_id_(world_id) {}
 
 Vector<v8::Local<v8::Value>> WebScriptExecutor::Execute(
     LocalDOMWindow* window) {
-  if (user_gesture_ == mojom::blink::UserActivationOption::kActivate) {
-    // TODO(mustaq): Need to make sure this is safe. https://crbug.com/1082273
-    LocalFrame::NotifyUserActivation(
-        window->GetFrame(),
-        mojom::blink::UserActivationNotificationType::kWebScriptExec);
-  }
-
   Vector<v8::Local<v8::Value>> results;
   for (const auto& source : sources_) {
     // Note: An error event in an isolated world will never be dispatched to
@@ -265,12 +251,42 @@
   }
   PausableScriptExecutor* executor =
       MakeGarbageCollected<PausableScriptExecutor>(
-          window, script_state, want_result_option, std::move(callback),
+          window, script_state,
+          mojom::blink::UserActivationOption::kDoNotActivate,
+          mojom::blink::LoadEventBlockingOption::kDoNotBlock,
+          want_result_option, mojom::blink::PromiseResultOption::kDoNotWait,
+          std::move(callback),
           MakeGarbageCollected<V8FunctionExecutor>(
               window->GetIsolate(), function, receiver, argc, argv));
   executor->Run();
 }
 
+void PausableScriptExecutor::CreateAndRun(
+    LocalDOMWindow* window,
+    scoped_refptr<DOMWrapperWorld> world,
+    Vector<WebScriptSource> sources,
+    mojom::blink::UserActivationOption user_activation_option,
+    mojom::blink::EvaluationTiming evaluation_timing,
+    mojom::blink::LoadEventBlockingOption blocking_option,
+    mojom::blink::WantResultOption want_result_option,
+    mojom::blink::PromiseResultOption promise_result_option,
+    WebScriptExecutionCallback callback) {
+  auto* executor = MakeGarbageCollected<PausableScriptExecutor>(
+      window, ToScriptState(window, *world), user_activation_option,
+      blocking_option, want_result_option, promise_result_option,
+      std::move(callback),
+      MakeGarbageCollected<WebScriptExecutor>(std::move(sources),
+                                              world->GetWorldId()));
+  switch (evaluation_timing) {
+    case mojom::blink::EvaluationTiming::kAsynchronous:
+      executor->RunAsync();
+      break;
+    case mojom::blink::EvaluationTiming::kSynchronous:
+      executor->Run();
+      break;
+  }
+}
+
 void PausableScriptExecutor::ContextDestroyed() {
   if (callback_) {
     // Though the context is (about to be) destroyed, the callback is invoked
@@ -285,34 +301,25 @@
 
 PausableScriptExecutor::PausableScriptExecutor(
     LocalDOMWindow* window,
-    scoped_refptr<DOMWrapperWorld> world,
-    Vector<WebScriptSource> sources,
-    mojom::blink::UserActivationOption user_gesture,
-    mojom::blink::WantResultOption want_result_option,
-    WebScriptExecutionCallback callback)
-    : PausableScriptExecutor(
-          window,
-          ToScriptState(window, *world),
-          want_result_option,
-          std::move(callback),
-          MakeGarbageCollected<WebScriptExecutor>(std::move(sources),
-                                                  world->GetWorldId(),
-                                                  user_gesture)) {}
-
-PausableScriptExecutor::PausableScriptExecutor(
-    LocalDOMWindow* window,
     ScriptState* script_state,
+    mojom::blink::UserActivationOption user_activation_option,
+    mojom::blink::LoadEventBlockingOption blocking_option,
     mojom::blink::WantResultOption want_result_option,
+    mojom::blink::PromiseResultOption promise_result_option,
     WebScriptExecutionCallback callback,
     Executor* executor)
     : ExecutionContextLifecycleObserver(window),
       script_state_(script_state),
       callback_(std::move(callback)),
-      blocking_option_(mojom::blink::LoadEventBlockingOption::kDoNotBlock),
+      user_activation_option_(user_activation_option),
+      blocking_option_(blocking_option),
       want_result_option_(want_result_option),
+      wait_for_promise_(promise_result_option),
       executor_(executor) {
   CHECK(script_state_);
   CHECK(script_state_->ContextIsValid());
+  if (blocking_option_ == mojom::blink::LoadEventBlockingOption::kBlock)
+    window->document()->IncrementLoadEventDelayCount();
 }
 
 PausableScriptExecutor::~PausableScriptExecutor() = default;
@@ -327,14 +334,9 @@
   PostExecuteAndDestroySelf(context);
 }
 
-void PausableScriptExecutor::RunAsync(
-    mojom::blink::LoadEventBlockingOption blocking) {
+void PausableScriptExecutor::RunAsync() {
   ExecutionContext* context = GetExecutionContext();
   DCHECK(context);
-  blocking_option_ = blocking;
-  if (blocking_option_ == mojom::blink::LoadEventBlockingOption::kBlock)
-    To<LocalDOMWindow>(context)->document()->IncrementLoadEventDelayCount();
-
   PostExecuteAndDestroySelf(context);
 }
 
@@ -353,6 +355,15 @@
 
   auto* window = To<LocalDOMWindow>(GetExecutionContext());
   ScriptState::Scope script_scope(script_state_);
+
+  if (user_activation_option_ ==
+      mojom::blink::UserActivationOption::kActivate) {
+    // TODO(mustaq): Need to make sure this is safe. https://crbug.com/1082273
+    LocalFrame::NotifyUserActivation(
+        window->GetFrame(),
+        mojom::blink::UserActivationNotificationType::kWebScriptExec);
+  }
+
   Vector<v8::Local<v8::Value>> results = executor_->Execute(window);
 
   // The script may have removed the frame, in which case contextDestroyed()
diff --git a/third_party/blink/renderer/core/frame/pausable_script_executor.h b/third_party/blink/renderer/core/frame/pausable_script_executor.h
index f20e895..b137c5a 100644
--- a/third_party/blink/renderer/core/frame/pausable_script_executor.h
+++ b/third_party/blink/renderer/core/frame/pausable_script_executor.h
@@ -35,6 +35,15 @@
                            v8::Local<v8::Value> argv[],
                            mojom::blink::WantResultOption,
                            WebScriptExecutionCallback);
+  static void CreateAndRun(LocalDOMWindow*,
+                           scoped_refptr<DOMWrapperWorld>,
+                           Vector<WebScriptSource>,
+                           mojom::blink::UserActivationOption,
+                           mojom::blink::EvaluationTiming,
+                           mojom::blink::LoadEventBlockingOption,
+                           mojom::blink::WantResultOption,
+                           mojom::blink::PromiseResultOption,
+                           WebScriptExecutionCallback);
 
   class Executor : public GarbageCollected<Executor> {
    public:
@@ -46,30 +55,22 @@
   };
 
   PausableScriptExecutor(LocalDOMWindow*,
-                         scoped_refptr<DOMWrapperWorld>,
-                         Vector<WebScriptSource>,
-                         mojom::blink::UserActivationOption,
-                         mojom::blink::WantResultOption,
-                         WebScriptExecutionCallback);
-  PausableScriptExecutor(LocalDOMWindow*,
                          ScriptState*,
+                         mojom::blink::UserActivationOption,
+                         mojom::blink::LoadEventBlockingOption,
                          mojom::blink::WantResultOption,
+                         mojom::blink::PromiseResultOption,
                          WebScriptExecutionCallback,
                          Executor*);
   ~PausableScriptExecutor() override;
 
-  void Run();
-  void RunAsync(mojom::blink::LoadEventBlockingOption);
   void ContextDestroyed() override;
 
   void Trace(Visitor*) const override;
 
-  void set_wait_for_promise(
-      mojom::blink::PromiseResultOption wait_for_promise) {
-    wait_for_promise_ = wait_for_promise;
-  }
-
  private:
+  void Run();
+  void RunAsync();
   void PostExecuteAndDestroySelf(ExecutionContext* context);
   void ExecuteAndDestroySelf();
   void Dispose();
@@ -79,17 +80,17 @@
   Member<ScriptState> script_state_;
   WebScriptExecutionCallback callback_;
   base::TimeTicks start_time_;
-  mojom::blink::LoadEventBlockingOption blocking_option_;
+  const mojom::blink::UserActivationOption user_activation_option_;
+  const mojom::blink::LoadEventBlockingOption blocking_option_;
   const mojom::blink::WantResultOption want_result_option_;
+  // Whether to wait for a promise to resolve, if the executed script evaluates
+  // to a promise.
+  const mojom::blink::PromiseResultOption wait_for_promise_;
+
   TaskHandle task_handle_;
 
   Member<Executor> executor_;
 
-  // Whether to wait for a promise to resolve, if the executed script evaluates
-  // to a promise.
-  mojom::blink::PromiseResultOption wait_for_promise_ =
-      mojom::blink::PromiseResultOption::kDoNotWait;
-
   // A keepalive used when waiting on promises to settle.
   SelfKeepAlive<PausableScriptExecutor> keep_alive_;
 };
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index 4a41c8a..54d886186 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -25,6 +25,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "services/network/public/mojom/content_security_policy.mojom-blink-forward.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h"
@@ -674,18 +675,21 @@
   // https://docs.google.com/document/d/1ijTZJT3DHQ1ljp4QQe4E4XCCRaYAxmInNzN1SzeJM8s/edit.
   if (ContainingShadowRoot() && ContainingShadowRoot()->IsUserAgent() &&
       IsA<HTMLFencedFrameElement>(ContainingShadowRoot()->host())) {
-    // Note that if a fenced frame's `is_fenced` status or `mode` attribute ever
-    // changes, the browser process will bad-message the renderer since this
-    // should never happen. Therefore it is safe to just naively always set it
-    // here because:
+    // Note that if a fenced frame's `is_fenced`, `mode`, or `sandbox_flags`
+    // attribute ever changes, the browser process will terminate the renderer
+    // since this should never happen. Therefore it is safe to just naively
+    // always set it here because:
     //   - A fenced frame always has `is_fenced = true`
     //   - A fenced frame's mode is only settable once, enforced by
     //     `HTMLFencedFrameElement::ParseAttribute()` as well as the browser
     //     process
+    //   - A fenced frame's sandbox flags must be set to
+    //     kFencedFrameForcedSandboxFlags
     frame_policy_.is_fenced = true;
     frame_policy_.fenced_frame_mode =
         DynamicTo<HTMLFencedFrameElement>(ContainingShadowRoot()->host())
             ->GetMode();
+    frame_policy_.sandbox_flags = blink::kFencedFrameForcedSandboxFlags;
   }
 
   // Update the |should_lazy_load_children_| value according to the "loading"
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 078aaaba..e958c9e 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
@@ -3000,8 +3000,11 @@
   if (properties->TransformIsolationNode())
     return true;
   if (auto* offset_translation = properties->PaintOffsetTranslation()) {
-    if (offset_translation->RequiresCompositingForFixedPosition())
+    if (offset_translation->RequiresCompositingForFixedPosition() &&
+        // This is to keep the de facto CLS behavior with crrev.com/1036822.
+        object.GetFrameView()->LayoutViewport()->HasOverflow()) {
       return true;
+    }
   }
   if (auto* sticky_translation = properties->StickyTranslation())
     return true;
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
index c0bea7dc..ff99129 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
@@ -1234,14 +1234,19 @@
       options->signal()->AddAlgorithm(WTF::Bind(&AbortIdentityCredentialRequest,
                                                 WrapPersistent(script_state)));
     }
-    mojom::blink::IdentityProviderPtr identity_provider =
-        blink::mojom::blink::IdentityProvider::From(*provider);
+
+    Vector<mojom::blink::IdentityProviderPtr> identity_provider_ptrs;
+    for (const auto& provider : options->identity()->providers()) {
+      mojom::blink::IdentityProviderPtr identity_provider =
+          blink::mojom::blink::IdentityProvider::From(*provider);
+      identity_provider_ptrs.push_back(std::move(identity_provider));
+    }
     bool prefer_auto_sign_in = options->identity()->preferAutoSignIn();
     auto* auth_request =
         CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
 
     auth_request->RequestToken(
-        std::move(identity_provider), prefer_auto_sign_in,
+        std::move(identity_provider_ptrs), prefer_auto_sign_in,
         WTF::Bind(&OnRequestToken, WrapPersistent(resolver), provider_url,
                   client_id, WrapPersistent(options)));
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints.idl b/third_party/blink/renderer/modules/mediastream/media_stream_constraints.idl
index 9a16c0f..861a89f 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints.idl
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints.idl
@@ -8,6 +8,12 @@
   "exclude"
 };
 
+// https://github.com/w3c/mediacapture-screen-share/pull/216/files
+enum SelfCapturePreferenceEnum {
+  "include",
+  "exclude"
+};
+
 // https://w3c.github.io/mediacapture-main/#idl-def-mediastreamconstraints
 dictionary MediaStreamConstraints {
     (boolean or MediaTrackConstraints) video = false;
@@ -24,4 +30,9 @@
     [
       RuntimeEnabled = SystemAudioConstraint
     ] SystemAudioPreferenceEnum systemAudio;
+
+    // https://github.com/w3c/mediacapture-screen-share/pull/216/files
+    [
+      RuntimeEnabled = SelfBrowserSurfaceConstraint
+    ] SelfCapturePreferenceEnum selfBrowserSurface;
 };
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
index 5f21b76..7373388 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
@@ -813,7 +813,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(current_request_info_);
 
-  if (!current_request_info_->request()->Video()) {
+  UserMediaRequest* const request = current_request_info_->request();
+
+  if (!request->Video()) {
     absl::optional<base::UnguessableToken> audio_session_id =
         DetermineExistingAudioSessionId();
     GenerateStreamForCurrentRequestInfo(
@@ -822,40 +824,35 @@
                               : StreamSelectionStrategy::FORCE_NEW_STREAM);
     return;
   }
-  SendLogMessage(
-      base::StringPrintf("SetupVideoInput. request_id=%d, video constraints=%s",
-                         current_request_info_->request_id(),
-                         current_request_info_->request()
-                             ->VideoConstraints()
-                             .ToString()
-                             .Utf8()
-                             .c_str()));
+  SendLogMessage(base::StringPrintf(
+      "SetupVideoInput. request_id=%d, video constraints=%s",
+      current_request_info_->request_id(),
+      request->VideoConstraints().ToString().Utf8().c_str()));
 
   auto& video_controls = current_request_info_->stream_controls()->video;
-  InitializeVideoTrackControls(current_request_info_->request(),
-                               &video_controls);
+  InitializeVideoTrackControls(request, &video_controls);
 
-  current_request_info_->stream_controls()->request_pan_tilt_zoom_permission =
-      IsPanTiltZoomPermissionRequested(
-          current_request_info_->request()->VideoConstraints());
+  StreamControls* const stream_controls =
+      current_request_info_->stream_controls();
+
+  stream_controls->request_pan_tilt_zoom_permission =
+      IsPanTiltZoomPermissionRequested(request->VideoConstraints());
 
   // TODO(crbug.com/1337788): Clean up naming inconsistency with
   // auto_select_all_screens.
-  current_request_info_->stream_controls()->request_all_screens =
-      current_request_info_->request()->auto_select_all_screens();
+  stream_controls->request_all_screens = request->auto_select_all_screens();
+
+  stream_controls->exclude_self_browser_surface =
+      request->exclude_self_browser_surface();
 
   if (blink::IsDeviceMediaType(video_controls.stream_type)) {
     GetMediaDevicesDispatcher()->GetVideoInputCapabilities(
         WTF::Bind(&UserMediaProcessor::SelectVideoDeviceSettings,
-                  WrapWeakPersistent(this),
-                  WrapPersistent(current_request_info_->request())));
+                  WrapWeakPersistent(this), WrapPersistent(request)));
   } else {
     if (!blink::IsVideoInputMediaType(video_controls.stream_type)) {
-      String failed_constraint_name =
-          String(current_request_info_->request()
-                     ->VideoConstraints()
-                     .Basic()
-                     .media_stream_source.GetName());
+      String failed_constraint_name = String(
+          request->VideoConstraints().Basic().media_stream_source.GetName());
       MediaStreamRequestResult result =
           MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
       GetUserMediaRequestFailed(result, failed_constraint_name);
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.cc b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
index 5cce683f..0f01d9f 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_request.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
@@ -412,6 +412,12 @@
       options->systemAudio().AsEnum() ==
           V8SystemAudioPreferenceEnum::Enum::kExclude);
 
+  // The default is to include.
+  result->set_exclude_self_browser_surface(
+      options->hasSelfBrowserSurface() &&
+      options->selfBrowserSurface().AsEnum() ==
+          V8SelfCapturePreferenceEnum::Enum::kExclude);
+
   return result;
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.h b/third_party/blink/renderer/modules/mediastream/user_media_request.h
index 74dd4e1..9ac342a 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_request.h
+++ b/third_party/blink/renderer/modules/mediastream/user_media_request.h
@@ -150,6 +150,12 @@
 
   void set_exclude_system_audio(bool value) { exclude_system_audio_ = value; }
   bool exclude_system_audio() const { return exclude_system_audio_; }
+  void set_exclude_self_browser_surface(bool value) {
+    exclude_self_browser_surface_ = value;
+  }
+  bool exclude_self_browser_surface() const {
+    return exclude_self_browser_surface_;
+  }
   bool auto_select_all_screens() const { return auto_select_all_screens_; }
 
   // Mark this request as an GetOpenDevice request for initializing a
@@ -180,6 +186,7 @@
   MediaConstraints video_;
   const bool should_prefer_current_tab_ = false;
   bool exclude_system_audio_ = false;
+  bool exclude_self_browser_surface_ = false;
   const bool auto_select_all_screens_ = false;
   bool should_disable_hardware_noise_suppression_;
   bool has_transient_user_activation_ = false;
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 5dc521cc..c1a474db 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -369,6 +369,14 @@
   RuntimeEnabledFeatures::SetSharedWorkerEnabled(enable);
 }
 
+void WebRuntimeFeatures::EnableStorageAccessAPIForSiteExtension(bool enable) {
+  RuntimeEnabledFeatures::SetStorageAccessAPIForSiteExtensionEnabled(enable);
+}
+
+bool WebRuntimeFeatures::IsStorageAccessAPIForSiteExtensionEnabled() {
+  return RuntimeEnabledFeatures::StorageAccessAPIForSiteExtensionEnabled();
+}
+
 void WebRuntimeFeatures::EnableTextFragmentAnchor(bool enable) {
   RuntimeEnabledFeatures::SetTextFragmentIdentifiersEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index f24c0c6..266e173b 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2036,6 +2036,15 @@
       origin_trial_allows_third_party: true
     },
     {
+      // When a Web application calls getDisplayMedia() and asks for video,
+      // allow a hint to be provided as to whether the current tab should be
+      // excluded from the list of tabs offered to the user.
+      //
+      // https://github.com/w3c/mediacapture-screen-share/pull/216/files
+      name: "SelfBrowserSurfaceConstraint",
+      status: "stable",
+    },
+    {
       name: "SendBeaconThrowForBlobWithNonSimpleType",
       status: "stable",
     },
@@ -2149,6 +2158,11 @@
       status: "test",
     },
     {
+      // Enabled when blink::features::kStorageAccessAPIForSiteExtension is enabled.
+      name: "StorageAccessAPIForSiteExtension",
+      status: "test",
+    },
+    {
       name: "StorageBuckets",
       status: "experimental",
     },
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index abbe10c..a614c79 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -735,6 +735,12 @@
     "args": [ "--enable-features=StorageAccessAPI" ]
   },
   {
+    "prefix": "storage-access-api-for-site-extension",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": [ "wpt_internal/storage-access-api" ],
+    "args": [ "--enable-features=StorageAccessAPI,StorageAccessAPIForSiteExtension" ]
+  },
+  {
     "prefix": "web-bluetooth-new-permissions-backend",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": ["wpt_internal/bluetooth", "external/wpt/bluetooth"],
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html
index 9a60677..cfa471d1a 100644
--- a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html
@@ -23,7 +23,8 @@
 <div id=host>
   <template id=shadow shadowroot=open>
     <span id=toreplace>This should get removed</span>
-    <script></script> <!-- Ensure observer runs -->
+    <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+    <script> // some content, which shouldn't be necessary </script>
   </template>
 </div>
 
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html
index 6d01ced..221b252a 100644
--- a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html
@@ -21,7 +21,9 @@
 </script>
 
 <div id='has-imperative-root'>
-  <script></script> <!-- Ensure observer runs, attaches imperative shadow root -->
+  <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+  <script> // some content, which shouldn't be necessary </script>
+  <!-- The imperative shadow root should be attached now. -->
   <template id=ordinarytemplate shadowroot=open>
     <span id=toreplace>This should get removed</span>
   </template>
diff --git a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
index 6388acf..9d18b17a 100644
--- a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
@@ -1990,6 +1990,7 @@
     method releaseEvents
     method replaceChildren
     method requestStorageAccess
+    method requestStorageAccessForSite
     method webkitCancelFullScreen
     method webkitExitFullscreen
     method write
diff --git a/third_party/blink/web_tests/virtual/storage-access-api-for-site-extension/README.md b/third_party/blink/web_tests/virtual/storage-access-api-for-site-extension/README.md
new file mode 100644
index 0000000..e38dfa3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/storage-access-api-for-site-extension/README.md
@@ -0,0 +1 @@
+See third_party/blink/web_tests/wpt_internal/storage-access-api/README.md
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-anchor-position/anchor-position-grid-001.html b/third_party/blink/web_tests/wpt_internal/css/css-anchor-position/anchor-position-grid-001.html
new file mode 100644
index 0000000..32851d0
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-anchor-position/anchor-position-grid-001.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<link rel="help" href="https://tabatkins.github.io/specs/css-anchor-position/#anchor-pos">
+<link rel="help" href="https://tabatkins.github.io/specs/css-anchor-position/#anchor-size">
+<link rel="author" href="mailto:kojii@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<style>
+.cb {
+  position: relative;
+}
+.columns {
+  column-count: 3;
+  column-fill: auto;
+  column-gap: 10px;
+  width: 620px;
+  height: 100px;
+}
+.grid {
+  display: grid;
+  grid-template-columns: repeat(2, 100px);
+  grid-template-rows: 50px 100px;
+}
+.spacer {
+  background: yellow;
+}
+.anchor1 {
+  anchor-name: --a1;
+  grid-column: 2;
+  grid-row: 2;
+  border: 2px solid orange;
+  border-width: 5px 6px 7px 8px;
+}
+.target {
+  grid-column: 2;
+  grid-row: 2;
+  position: absolute;
+  width: anchor-size(--a1 width);
+  height: anchor-size(--a1 height);
+  background: lime;
+  opacity: .2;
+}
+.target1-l {
+  left: anchor(--a1 left);
+  top: anchor(--a1 top);
+  width: 8px;
+}
+.target1-r {
+  right: anchor(--a1 right);
+  bottom: anchor(--a1 bottom);
+  width: 6px;
+}
+.target1-t {
+  left: anchor(--a1 left);
+  top: anchor(--a1 top);
+  height: 5px;
+}
+.target1-b {
+  right: anchor(--a1 right);
+  bottom: anchor(--a1 bottom);
+  height: 5px;
+}
+</style>
+<body onload="checkLayout('.target')">
+  <div>
+    <div class="spacer" style="height: 10px"></div>
+    <div class="columns">
+      <div class="spacer" style="height: 10px"></div>
+      <div class="grid cb">
+        <div>1</div>
+        <div>2</div>
+        <div>3</div>
+        <div class="anchor1"></div>
+
+        <div class="target target1-l" data-offset-x=100 data-expected-height=100></div>
+        <div class="target target1-r" data-offset-x=404 data-expected-height=100></div>
+        <div class="target target1-t" data-offset-y=0 data-expected-width=310></div>
+        <div class="target target1-b" data-offset-y=95 data-expected-width=310></div>
+      </div>
+    </div>
+  </div>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/storage-access-api/README.md b/third_party/blink/web_tests/wpt_internal/storage-access-api/README.md
new file mode 100644
index 0000000..e343e47
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/storage-access-api/README.md
@@ -0,0 +1,18 @@
+# requestStorageAccessForSite Tests
+These tests are tentative. They are based on a proposed requestStorageAccessForSite extension to the Storage Access API which can be read about [in the explainer](https://github.com/mreichhoff/requestStorageAccessForSite).
+
+Note that the spec is not yet defined, though very early [prose](https://github.com/mreichhoff/requestStorageAccessForSite#proposed-draft-spec-addition) and [bikeshed](https://github.com/mreichhoff/storage-access/commit/93ba79fdbb737f57a7ce757f994b2f8c53d2cd53) drafts are available.
+
+## Running Tests
+
+The WPTs run as [virtual tests](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/VirtualTestSuites). This is required as the proposed API is behind a feature flag.
+
+```bash
+# Build web tests
+autoninja -C out/Default blink_tests
+
+# Run a single test
+third_party/blink/tools/run_web_tests.py -t Default third_party/blink/web_tests/wpt_internal/storage-access-api/requestStorageAccessForSite.sub.tentative.window.js
+```
+
+See the [web tests doc](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/web_tests.md#running-the-tests) for more details on using the test runner.
diff --git a/third_party/blink/web_tests/wpt_internal/storage-access-api/requestStorageAccessForSite.sub.tentative.window.js b/third_party/blink/web_tests/wpt_internal/storage-access-api/requestStorageAccessForSite.sub.tentative.window.js
new file mode 100644
index 0000000..8e82dbe
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/storage-access-api/requestStorageAccessForSite.sub.tentative.window.js
@@ -0,0 +1,139 @@
+// META: script=/storage-access-api/helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+// Note that this file follows the pattern in:
+// third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess.sub.window.js
+//
+// Some tests are run at the top-level, and an iframe is added to validate API
+// behavior in that context.
+
+// Prefix each test case with an indicator so we know what context they are run
+// in if they are used in multiple iframes.
+let testPrefix = 'top-level-context';
+
+// Keep track of if we run these tests in a nested context, we don't want to
+// recurse forever.
+let topLevelDocument = true;
+
+// The query string allows derivation of test conditions, like whether the tests
+// are running in a top-level context.
+let queryParams = window.location.search.substring(1).split('&');
+queryParams.forEach(function(param, index) {
+  if (param.toLowerCase() == 'rootdocument=false') {
+    topLevelDocument = false;
+  } else if (param.split('=')[0].toLowerCase() == 'testcase') {
+    testPrefix = param.split('=')[1];
+  }
+});
+
+// Common tests to run in all frames.
+test(
+    () => {
+      assert_not_equals(document.requestStorageAccessForSite, undefined);
+    },
+    '[' + testPrefix +
+        '] document.requestStorageAccessForSite() should be supported on the document interface');
+
+if (topLevelDocument) {
+  promise_test(
+      t => {
+        let promise = document.requestStorageAccessForSite('https://test.com');
+        let description =
+            'document.requestStorageAccessForSite() call without user gesture';
+        return promise
+            .then(t.unreached_func('Should have rejected: ' + description))
+            .catch(function(e) {
+              assert_equals(undefined, e, description);
+            });
+      },
+      '[' + testPrefix +
+          '] document.requestStorageAccessForSite() should be rejected by default with no user gesture');
+
+  // Create a test with a single-child same-origin iframe.
+  // This will validate that calls to requestStorageAccessForSite are rejected
+  // in non-top-level contexts.
+  RunTestsInIFrame(
+      './resources/requestStorageAccessForSite-iframe.html?testCase=same-origin-frame&rootdocument=false');
+
+  promise_test(
+      async t => {
+        let access_promise = null;
+        let testMethod = function() {
+          access_promise =
+              document.requestStorageAccessForSite(document.location.origin);
+        };
+        await ClickButtonWithGesture(testMethod);
+
+        return access_promise;
+      },
+      '[' + testPrefix +
+          '] document.requestStorageAccessForSite() should be resolved when called properly with a user gesture and the same site');
+
+  promise_test(
+      async t => {
+        let access_promise = null;
+        let description =
+            'document.requestStorageAccessForSite() call with bogus URL';
+        let testMethod = function() {
+          access_promise = document.requestStorageAccessForSite('bogus-url')
+                               .then(t.unreached_func(
+                                   'Should have rejected: ' + description))
+                               .catch(function(e) {
+                                 assert_equals(undefined, e, description);
+                               });
+          ;
+        };
+        await ClickButtonWithGesture(testMethod);
+
+        return access_promise;
+      },
+      '[' + testPrefix +
+          '] document.requestStorageAccessForSite() should be rejected when called with an invalid site');
+
+  promise_test(
+      async t => {
+        let access_promise = null;
+        let description =
+            'document.requestStorageAccessForSite() call with data URL';
+        let testMethod = function() {
+          access_promise =
+              document.requestStorageAccessForSite('data:,Hello%2C%20World%21')
+                  .then(
+                      t.unreached_func('Should have rejected: ' + description))
+                  .catch(function(e) {
+                    assert_equals(undefined, e, description);
+                  });
+          ;
+        };
+        await ClickButtonWithGesture(testMethod);
+
+        return access_promise;
+      },
+      '[' + testPrefix +
+          '] document.requestStorageAccessForSite() should be rejected when called with an opaque origin');
+
+} else {
+  promise_test(
+      async t => {
+        let access_promise = null;
+        let description =
+            'document.requestStorageAccessForSite() call in a non-top-level context';
+        let testMethod = function() {
+          access_promise =
+              document.requestStorageAccessForSite(document.location.origin)
+                  .then(
+                      t.unreached_func('Should have rejected: ' + description))
+                  .catch(function(e) {
+                    assert_equals(undefined, e, description);
+                  });
+          ;
+        };
+        await ClickButtonWithGesture(testMethod);
+
+        return access_promise;
+      },
+      '[' + testPrefix +
+          '] document.requestStorageAccessForSite() should be rejected when called in an iframe');
+}
diff --git a/third_party/blink/web_tests/wpt_internal/storage-access-api/resources/requestStorageAccessForSite-iframe.html b/third_party/blink/web_tests/wpt_internal/storage-access-api/resources/requestStorageAccessForSite-iframe.html
new file mode 100644
index 0000000..1da32c1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/storage-access-api/resources/requestStorageAccessForSite-iframe.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/storage-access-api/helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id=log></div>
+<script src="../requestStorageAccessForSite.sub.tentative.window.js"></script>
diff --git a/third_party/crashpad/crashpad/snapshot/mac/system_snapshot_mac_test.cc b/third_party/crashpad/crashpad/snapshot/mac/system_snapshot_mac_test.cc
index ab09b5b..6bf32b1f 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/system_snapshot_mac_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/system_snapshot_mac_test.cc
@@ -82,13 +82,7 @@
   EXPECT_GE(system_snapshot().CPUCount(), 1);
 }
 
-#if defined(ARCH_CPU_ARM64)
-// https://crbug.com/1222625
-#define MAYBE_CPUVendor DISABLED_CPUVendor
-#else
-#define MAYBE_CPUVendor CPUVendor
-#endif
-TEST_F(SystemSnapshotMacTest, MAYBE_CPUVendor) {
+TEST_F(SystemSnapshotMacTest, CPUVendor) {
   std::string cpu_vendor = system_snapshot().CPUVendor();
 
 #if defined(ARCH_CPU_X86_FAMILY)
diff --git a/third_party/ipcz/src/BUILD.gn b/third_party/ipcz/src/BUILD.gn
index 4fa9f4fd..78187739 100644
--- a/third_party/ipcz/src/BUILD.gn
+++ b/third_party/ipcz/src/BUILD.gn
@@ -138,13 +138,11 @@
 
   public = [
     "reference_drivers/async_reference_driver.h",
-    "reference_drivers/blob.h",
     "reference_drivers/sync_reference_driver.h",
   ]
 
   sources = [
     "reference_drivers/async_reference_driver.cc",
-    "reference_drivers/blob.cc",
     "reference_drivers/object.cc",
     "reference_drivers/object.h",
     "reference_drivers/random.cc",
diff --git a/third_party/ipcz/src/box_test.cc b/third_party/ipcz/src/box_test.cc
index f13203ab..cdbfce0a 100644
--- a/third_party/ipcz/src/box_test.cc
+++ b/third_party/ipcz/src/box_test.cc
@@ -6,7 +6,6 @@
 #include <string_view>
 
 #include "ipcz/ipcz.h"
-#include "reference_drivers/blob.h"
 #include "test/multinode_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "util/ref_counted.h"
@@ -14,71 +13,45 @@
 namespace ipcz {
 namespace {
 
-using Blob = reference_drivers::Blob;
-
 using BoxTestNode = test::TestNode;
 using BoxTest = test::MultinodeTest<BoxTestNode>;
 
-IpczDriverHandle CreateTestBlob(std::string_view message) {
-  return Blob::ReleaseAsHandle(MakeRefCounted<Blob>(message));
-}
-
-std::string GetBlobContents(IpczDriverHandle handle) {
-  Ref<Blob> blob = Blob::TakeFromHandle(handle);
-  return std::string(blob->message());
-}
-
-TEST_P(BoxTest, BoxAndUnbox) {
+MULTINODE_TEST(BoxTest, BoxAndUnbox) {
   constexpr const char kMessage[] = "Hello, world?";
-  IpczDriverHandle blob_handle = CreateTestBlob(kMessage);
-
-  IpczHandle box;
-  EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
-
-  blob_handle = IPCZ_INVALID_DRIVER_HANDLE;
-  EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
-  EXPECT_EQ(kMessage, GetBlobContents(blob_handle));
+  EXPECT_EQ(kMessage, UnboxBlob(BoxBlob(kMessage)));
 }
 
-TEST_P(BoxTest, CloseBox) {
-  Ref<Blob> blob = MakeRefCounted<Blob>("meh");
-  Ref<Blob::RefCountedFlag> destroyed = blob->destruction_flag_for_testing();
-  IpczDriverHandle blob_handle = Blob::ReleaseAsHandle(std::move(blob));
-
-  IpczHandle box;
+MULTINODE_TEST(BoxTest, CloseBox) {
+  // Verifies that box closure releases its underlying driver object. This test
+  // does not explicitly observe side effects of that release, but LSan will
+  // fail if something's off.
   EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
-
-  EXPECT_FALSE(destroyed->get());
-  EXPECT_EQ(IPCZ_RESULT_OK, ipcz().Close(box, IPCZ_NO_FLAGS, nullptr));
-  EXPECT_TRUE(destroyed->get());
+            ipcz().Close(BoxBlob("meh"), IPCZ_NO_FLAGS, nullptr));
 }
 
-TEST_P(BoxTest, Peek) {
-  constexpr const char kMessage[] = "Hello, world?";
-  IpczDriverHandle blob_handle = CreateTestBlob(kMessage);
-  IpczHandle box;
-  EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
+MULTINODE_TEST(BoxTest, Peek) {
+  constexpr std::string_view kMessage = "Hello, world?";
+  IpczHandle box = BoxBlob(kMessage);
 
-  blob_handle = IPCZ_INVALID_DRIVER_HANDLE;
+  IpczDriverHandle memory = IPCZ_INVALID_DRIVER_HANDLE;
   EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &blob_handle));
+            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &memory));
   EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &blob_handle));
+            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &memory));
   EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &blob_handle));
+            ipcz().Unbox(box, IPCZ_UNBOX_PEEK, nullptr, &memory));
+  EXPECT_NE(IPCZ_INVALID_DRIVER_HANDLE, memory);
 
-  Blob* blob = Blob::FromHandle(blob_handle);
-  EXPECT_EQ(kMessage, blob->message());
-
+  IpczDriverHandle mapping;
+  void* base;
   EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
+            GetDriver().MapSharedMemory(memory, IPCZ_NO_FLAGS, nullptr, &base,
+                                        &mapping));
+  std::string contents(static_cast<const char*>(base), kMessage.size());
+  EXPECT_EQ(kMessage, contents);
+  EXPECT_EQ(IPCZ_RESULT_OK, GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr));
 
-  Ref<Blob> released_blob = Blob::TakeFromHandle(blob_handle);
-  EXPECT_EQ(blob, released_blob.get());
+  EXPECT_EQ(kMessage, UnboxBlob(box));
 }
 
 constexpr const char kMessage1[] = "Hello, world?";
@@ -91,25 +64,14 @@
   IpczHandle box;
   EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
   EXPECT_EQ(kMessage2, message);
-
-  IpczDriverHandle blob_handle;
-  EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
-  EXPECT_EQ(kMessage1, GetBlobContents(blob_handle));
-
+  EXPECT_EQ(kMessage1, UnboxBlob(box));
   Close(b);
 }
 
-TEST_P(BoxTest, TransferBox) {
+MULTINODE_TEST(BoxTest, TransferBox) {
   IpczHandle c = SpawnTestNode<TransferBoxClient>();
-
-  IpczDriverHandle blob_handle = CreateTestBlob(kMessage1);
-  IpczHandle box;
-  EXPECT_EQ(IPCZ_RESULT_OK,
-            ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
-
+  IpczHandle box = BoxBlob(kMessage1);
   EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kMessage2, {&box, 1}));
-
   Close(c);
 }
 
@@ -121,19 +83,14 @@
   EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&q, 1}));
 
   for (size_t i = 0; i < TransferBoxBetweenNonBrokersNumIterations; ++i) {
-    IpczDriverHandle blob_handle = CreateTestBlob(kMessage1);
-    IpczHandle box;
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
+    IpczHandle box = BoxBlob(kMessage1);
     EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kMessage2, {&box, 1}));
     box = IPCZ_INVALID_DRIVER_HANDLE;
 
     std::string message;
     EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(q, &message, {&box, 1}));
     EXPECT_EQ(kMessage1, message);
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
-    EXPECT_EQ(kMessage2, GetBlobContents(blob_handle));
+    EXPECT_EQ(kMessage2, UnboxBlob(box));
   }
 
   WaitForDirectRemoteLink(q);
@@ -147,17 +104,12 @@
 
   for (size_t i = 0; i < TransferBoxBetweenNonBrokersNumIterations; ++i) {
     IpczHandle box;
-    IpczDriverHandle blob_handle;
     std::string message;
     EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(p, &message, {&box, 1}));
     EXPECT_EQ(kMessage2, message);
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
-    EXPECT_EQ(kMessage1, GetBlobContents(blob_handle));
+    EXPECT_EQ(kMessage1, UnboxBlob(box));
 
-    blob_handle = CreateTestBlob(kMessage2);
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
+    box = BoxBlob(kMessage2);
     EXPECT_EQ(IPCZ_RESULT_OK, Put(p, kMessage1, {&box, 1}));
   }
 
@@ -165,7 +117,7 @@
   CloseAll({p, b});
 }
 
-TEST_P(BoxTest, TransferBoxBetweenNonBrokers) {
+MULTINODE_TEST(BoxTest, TransferBoxBetweenNonBrokers) {
   IpczHandle c1 = SpawnTestNode<TransferBoxBetweenNonBrokersClient1>();
   IpczHandle c2 = SpawnTestNode<TransferBoxBetweenNonBrokersClient2>();
 
@@ -181,7 +133,5 @@
   CloseAll({c1, c2});
 }
 
-INSTANTIATE_MULTINODE_TEST_SUITE_P(BoxTest);
-
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/connect_test.cc b/third_party/ipcz/src/connect_test.cc
index 10d4df5..41cb3d54 100644
--- a/third_party/ipcz/src/connect_test.cc
+++ b/third_party/ipcz/src/connect_test.cc
@@ -6,7 +6,6 @@
 
 #include "ipcz/ipcz.h"
 #include "ipcz/node_messages.h"
-#include "reference_drivers/blob.h"
 #include "test/multinode_test.h"
 #include "test/test_transport_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -25,7 +24,7 @@
   Close(b);
 }
 
-TEST_P(ConnectTest, BrokerToNonBroker) {
+MULTINODE_TEST(ConnectTest, BrokerToNonBroker) {
   IpczHandle c = SpawnTestNode<BrokerToNonBrokerClient>();
   Close(c);
 }
@@ -47,7 +46,7 @@
   CloseAll(portals);
 }
 
-TEST_P(ConnectTest, SurplusPortals) {
+MULTINODE_TEST(ConnectTest, SurplusPortals) {
   IpczHandle portals[kNumBrokerPortals];
   SpawnTestNode<SurplusPortalsClient>(portals);
   CloseAll(portals);
@@ -59,12 +58,12 @@
   Close(b);
 }
 
-TEST_P(ConnectTest, DisconnectWithoutBrokerHandshake) {
-  TransportPair transports = CreateTransports();
+MULTINODE_TEST(ConnectTest, DisconnectWithoutBrokerHandshake) {
+  IpczDriverHandle our_transport;
   auto controller =
-      SpawnTestNodeWithTransport<ExpectDisconnectFromBroker>(transports.theirs);
+      SpawnTestNodeNoConnect<ExpectDisconnectFromBroker>(our_transport);
   EXPECT_EQ(IPCZ_RESULT_OK,
-            GetDriver().Close(transports.ours, IPCZ_NO_FLAGS, nullptr));
+            GetDriver().Close(our_transport, IPCZ_NO_FLAGS, nullptr));
   controller->WaitForShutdown();
 }
 
@@ -74,25 +73,25 @@
   // we never call ConnectToBroker(). No action required.
 }
 
-TEST_P(ConnectTest, DisconnectWithoutNonBrokerHandshake) {
+MULTINODE_TEST(ConnectTest, DisconnectWithoutNonBrokerHandshake) {
   IpczHandle c = SpawnTestNode<DisconnectWithoutNonBrokerHandshakeClient>();
   EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(c, IPCZ_TRAP_PEER_CLOSED));
   Close(c);
 }
 
-TEST_P(ConnectTest, DisconnectOnBadBrokerMessage) {
-  TransportPair transports = CreateTransports();
+MULTINODE_TEST(ConnectTest, DisconnectOnBadBrokerMessage) {
+  IpczDriverHandle our_transport;
   auto controller =
-      SpawnTestNodeWithTransport<ExpectDisconnectFromBroker>(transports.theirs);
+      SpawnTestNodeNoConnect<ExpectDisconnectFromBroker>(our_transport);
 
   // Send some garbage to the other node.
   const char kBadMessage[] = "this will never be a valid handshake message!";
   EXPECT_EQ(
       IPCZ_RESULT_OK,
-      GetDriver().Transmit(transports.ours, kBadMessage, std::size(kBadMessage),
+      GetDriver().Transmit(our_transport, kBadMessage, std::size(kBadMessage),
                            nullptr, 0, IPCZ_NO_FLAGS, nullptr));
   EXPECT_EQ(IPCZ_RESULT_OK,
-            GetDriver().Close(transports.ours, IPCZ_NO_FLAGS, nullptr));
+            GetDriver().Close(our_transport, IPCZ_NO_FLAGS, nullptr));
 
   // The other node will only shut down once it's observed peer closure on its
   // portal to us; which it should, because we just sent it some garbage.
@@ -115,7 +114,7 @@
   listener.StopListening();
 }
 
-TEST_P(ConnectTest, DisconnectOnBadNonBrokerMessage) {
+MULTINODE_TEST(ConnectTest, DisconnectOnBadNonBrokerMessage) {
   IpczHandle c;
   auto controller = SpawnTestNode<TransmitSomeGarbage>({&c, 1});
 
@@ -160,7 +159,7 @@
   CloseAll({c, b});
 }
 
-TEST_P(ConnectTest, NonBrokerToNonBroker) {
+MULTINODE_TEST(ConnectTest, NonBrokerToNonBroker) {
   IpczHandle c1 = SpawnTestNode<NonBrokerToNonBrokerClient>();
   IpczHandle c2 = SpawnTestNode<NonBrokerToNonBrokerClient>();
 
@@ -212,7 +211,7 @@
             GetDriver().Close(transports.theirs, IPCZ_NO_FLAGS, nullptr));
 }
 
-TEST_P(ConnectTest, BadNonBrokerReferral) {
+MULTINODE_TEST(ConnectTest, BadNonBrokerReferral) {
   IpczHandle c = SpawnTestNode<BadNonBrokerReferralClient>();
   EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(c, IPCZ_TRAP_PEER_CLOSED));
   Close(c);
@@ -230,27 +229,25 @@
 MULTINODE_TEST_NODE(ConnectTestNode, FailedNonBrokerReferralClient) {
   IpczHandle b = ConnectToBroker();
 
-  TransportPair transports = CreateTransports();
+  IpczDriverHandle our_transport;
   auto controller =
-      SpawnTestNodeWithTransport<FailedNonBrokerReferralReferredClient>(
-          transports.theirs);
+      SpawnTestNodeNoConnect<FailedNonBrokerReferralReferredClient>(
+          our_transport);
 
   // Disconnect the transport instead of passing to our broker with
   // ConnectNode(). The referred client should observe disconnection of its
   // initial portals and terminate itself.
   EXPECT_EQ(IPCZ_RESULT_OK,
-            GetDriver().Close(transports.ours, IPCZ_NO_FLAGS, nullptr));
+            GetDriver().Close(our_transport, IPCZ_NO_FLAGS, nullptr));
   controller->WaitForShutdown();
   Close(b);
 }
 
-TEST_P(ConnectTest, FailedNonBrokerReferral) {
+MULTINODE_TEST(ConnectTest, FailedNonBrokerReferral) {
   IpczHandle c = SpawnTestNode<FailedNonBrokerReferralClient>();
   EXPECT_EQ(IPCZ_RESULT_OK, WaitForConditionFlags(c, IPCZ_TRAP_PEER_CLOSED));
   Close(c);
 }
 
-INSTANTIATE_MULTINODE_TEST_SUITE_P(ConnectTest);
-
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/merge_portals_test.cc b/third_party/ipcz/src/merge_portals_test.cc
index a25008a..306605b 100644
--- a/third_party/ipcz/src/merge_portals_test.cc
+++ b/third_party/ipcz/src/merge_portals_test.cc
@@ -30,7 +30,7 @@
   Close(b);
 }
 
-TEST_P(MergePortalsTest, MergeWithInitialPortal) {
+MULTINODE_TEST(MergePortalsTest, MergeWithInitialPortal) {
   IpczHandle c = SpawnTestNode<MergeWithInitialPortalClient>();
   auto [q, p] = OpenPortals();
   EXPECT_EQ(IPCZ_RESULT_OK, Merge(c, p));
@@ -43,7 +43,7 @@
   Close(q);
 }
 
-TEST_P(MergePortalsTest, MergeWithClosedLocalPeer) {
+MULTINODE_TEST(MergePortalsTest, MergeWithClosedLocalPeer) {
   auto [q, p] = OpenPortals();
   auto [d, b] = OpenPortals();
 
@@ -64,7 +64,7 @@
   Close(b);
 }
 
-TEST_P(MergePortalsTest, MergeWithClosedRemotePeer) {
+MULTINODE_TEST(MergePortalsTest, MergeWithClosedRemotePeer) {
   IpczHandle c = SpawnTestNode<MergeWithClosedRemotePeerClient>();
   auto [r, s] = OpenPortals();
   EXPECT_EQ(IPCZ_RESULT_OK, Put(c, "", {&r, 1}));
@@ -95,7 +95,7 @@
   CloseAll({b, portal, other_client});
 }
 
-TEST_P(MergePortalsTest, MergeComplexRoutes) {
+MULTINODE_TEST(MergePortalsTest, MergeComplexRoutes) {
   IpczHandle c1 = SpawnTestNode<MergeComplexRoutesClient>();
   IpczHandle c2 = SpawnTestNode<MergeComplexRoutesClient>();
 
@@ -123,7 +123,5 @@
   CloseAll({c1, c2});
 }
 
-INSTANTIATE_MULTINODE_TEST_SUITE_P(MergePortalsTest);
-
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/queueing_test.cc b/third_party/ipcz/src/queueing_test.cc
index a3ed6b74..d560dc5 100644
--- a/third_party/ipcz/src/queueing_test.cc
+++ b/third_party/ipcz/src/queueing_test.cc
@@ -41,7 +41,7 @@
   Close(b);
 }
 
-TEST_P(QueueingTest, RemoteQueueFeedback) {
+MULTINODE_TEST(QueueingTest, RemoteQueueFeedback) {
   // Exercises operations which rely on feedback from the remote peer regarding
   // its inbound parcel queue state.
   IpczHandle c = SpawnTestNode<RemoteQueueFeedbackClient>();
@@ -126,7 +126,7 @@
   Close(b);
 }
 
-TEST_P(QueueingTest, TwoPhaseQueueing) {
+MULTINODE_TEST(QueueingTest, TwoPhaseQueueing) {
   IpczHandle c = SpawnTestNode<TwoPhaseQueueingClient>();
   WaitForDirectRemoteLink(c);
 
@@ -176,7 +176,7 @@
   Close(b);
 }
 
-TEST_P(QueueingTest, TwoPhaseFeedback) {
+MULTINODE_TEST(QueueingTest, TwoPhaseFeedback) {
   IpczHandle c = SpawnTestNode<TwoPhaseFeedbackClient>();
   WaitForDirectRemoteLink(c);
   EXPECT_EQ(IPCZ_RESULT_OK, Put(c, "hello?"));
@@ -186,7 +186,5 @@
   Close(c);
 }
 
-INSTANTIATE_MULTINODE_TEST_SUITE_P(QueueingTest);
-
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/reference_drivers/blob.cc b/third_party/ipcz/src/reference_drivers/blob.cc
deleted file mode 100644
index bbebebe9..0000000
--- a/third_party/ipcz/src/reference_drivers/blob.cc
+++ /dev/null
@@ -1,41 +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 "reference_drivers/blob.h"
-
-#include <iterator>
-
-#include "util/ref_counted.h"
-
-namespace ipcz::reference_drivers {
-
-Blob::RefCountedFlag::RefCountedFlag() = default;
-
-Blob::RefCountedFlag::~RefCountedFlag() = default;
-
-Blob::Blob(std::string_view message) : message_(message) {}
-
-Blob::~Blob() = default;
-
-IpczResult Blob::Close() {
-  destruction_flag_for_testing_->set(true);
-  return IPCZ_RESULT_OK;
-}
-
-// static
-Blob* Blob::FromHandle(IpczDriverHandle handle) {
-  Object* object = Object::FromHandle(handle);
-  if (!object || object->type() != kBlob) {
-    return nullptr;
-  }
-
-  return static_cast<Blob*>(object);
-}
-
-// static
-Ref<Blob> Blob::TakeFromHandle(IpczDriverHandle handle) {
-  return AdoptRef(FromHandle(handle));
-}
-
-}  // namespace ipcz::reference_drivers
diff --git a/third_party/ipcz/src/reference_drivers/blob.h b/third_party/ipcz/src/reference_drivers/blob.h
deleted file mode 100644
index 84be3788..0000000
--- a/third_party/ipcz/src/reference_drivers/blob.h
+++ /dev/null
@@ -1,62 +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 IPCZ_SRC_REFERENCE_DRIVERS_BLOB_H_
-#define IPCZ_SRC_REFERENCE_DRIVERS_BLOB_H_
-
-#include <string>
-#include <string_view>
-
-#include "reference_drivers/object.h"
-#include "util/ref_counted.h"
-
-namespace ipcz::reference_drivers {
-
-// A driver-managed object which packages arbitrary string data. Blobs are used
-// to exercise driver object boxing in tests.
-//
-// Note that unlike the transport and memory objects defined by the reference
-// drivers, a blob is not a type of object known to ipcz. Instead it is used to
-// demonstrate how drivers can define arbitrary new types of transferrable
-// objects to extend ipcz.
-class Blob : public ObjectImpl<Blob, Object::kBlob> {
- public:
-  class RefCountedFlag : public RefCounted {
-   public:
-    RefCountedFlag();
-
-    bool get() const { return flag_; }
-    void set(bool flag) { flag_ = flag; }
-
-   private:
-    ~RefCountedFlag() override;
-    bool flag_ = false;
-  };
-
-  explicit Blob(std::string_view message);
-
-  // Object:
-  IpczResult Close() override;
-
-  std::string& message() { return message_; }
-
-  const Ref<RefCountedFlag>& destruction_flag_for_testing() const {
-    return destruction_flag_for_testing_;
-  }
-
-  static Blob* FromHandle(IpczDriverHandle handle);
-  static Ref<Blob> TakeFromHandle(IpczDriverHandle handle);
-
- protected:
-  ~Blob() override;
-
- private:
-  std::string message_;
-  const Ref<RefCountedFlag> destruction_flag_for_testing_{
-      MakeRefCounted<RefCountedFlag>()};
-};
-
-}  // namespace ipcz::reference_drivers
-
-#endif  // IPCZ_SRC_REFERENCE_DRIVERS_BLOB_H_
diff --git a/third_party/ipcz/src/reference_drivers/multiprocess_reference_driver.cc b/third_party/ipcz/src/reference_drivers/multiprocess_reference_driver.cc
index fe967b1..9efb49d 100644
--- a/third_party/ipcz/src/reference_drivers/multiprocess_reference_driver.cc
+++ b/third_party/ipcz/src/reference_drivers/multiprocess_reference_driver.cc
@@ -11,7 +11,6 @@
 #include <utility>
 
 #include "ipcz/ipcz.h"
-#include "reference_drivers/blob.h"
 #include "reference_drivers/file_descriptor.h"
 #include "reference_drivers/memfd_memory.h"
 #include "reference_drivers/object.h"
@@ -206,11 +205,6 @@
       required_num_handles = 1;
       break;
 
-    case Object::kBlob:
-      required_num_bytes += Blob::FromObject(object)->message().size();
-      required_num_handles = 0;
-      break;
-
     default:
       return IPCZ_RESULT_INVALID_ARGUMENT;
   }
@@ -246,12 +240,6 @@
       break;
     }
 
-    case Object::kBlob: {
-      auto blob = Blob::TakeFromObject(object);
-      memcpy(&header + 1, blob->message().data(), blob->message().size());
-      break;
-    }
-
     default:
       return IPCZ_RESULT_INVALID_ARGUMENT;
   }
@@ -290,12 +278,6 @@
       }
       break;
 
-    case Object::kBlob:
-      object = MakeRefCounted<Blob>(
-          std::string_view(reinterpret_cast<const char*>(&header + 1),
-                           num_bytes - sizeof(header)));
-      break;
-
     default:
       break;
   }
diff --git a/third_party/ipcz/src/reference_drivers/object.h b/third_party/ipcz/src/reference_drivers/object.h
index 56e88cd..58bc368 100644
--- a/third_party/ipcz/src/reference_drivers/object.h
+++ b/third_party/ipcz/src/reference_drivers/object.h
@@ -21,11 +21,6 @@
     kMemory,
     kMapping,
 
-    // A non-standard driver object type, used to exercise more complex, custom
-    // driver object de/serialization via boxing and unboxing in tests. See the
-    // Blob definition in src/reference_drivers/blob.h.
-    kBlob,
-
 #if defined(OS_LINUX)
     // A non-standard driver object type which wraps a FileDescriptor object.
     kFileDescriptor,
diff --git a/third_party/ipcz/src/remote_portal_test.cc b/third_party/ipcz/src/remote_portal_test.cc
index 798fdfe9..fb554c8 100644
--- a/third_party/ipcz/src/remote_portal_test.cc
+++ b/third_party/ipcz/src/remote_portal_test.cc
@@ -6,8 +6,8 @@
 #include <string_view>
 #include <utility>
 
+#include "build/build_config.h"
 #include "ipcz/ipcz.h"
-#include "reference_drivers/blob.h"
 #include "test/multinode_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/strings/str_cat.h"
@@ -31,7 +31,7 @@
   Close(b);
 }
 
-TEST_P(RemotePortalTest, BasicConnection) {
+MULTINODE_TEST(RemotePortalTest, BasicConnection) {
   IpczHandle c = SpawnTestNode<BasicConnectionClient>();
 
   std::string message;
@@ -55,7 +55,7 @@
   CloseAll({p, b});
 }
 
-TEST_P(RemotePortalTest, PortalTransfer) {
+MULTINODE_TEST(RemotePortalTest, PortalTransfer) {
   IpczHandle c = SpawnTestNode<PortalTransferClient>();
 
   auto [q, p] = OpenPortals();
@@ -110,7 +110,7 @@
   CloseAll({p, b});
 }
 
-TEST_P(RemotePortalTest, MultipleHops) {
+MULTINODE_TEST(RemotePortalTest, MultipleHops) {
   IpczHandle c1 = SpawnTestNode<MultipleHopsClient1>();
   IpczHandle c2 = SpawnTestNode<MultipleHopsClient2>();
 
@@ -138,7 +138,7 @@
   Close(b);
 }
 
-TEST_P(RemotePortalTest, TransferBackAndForth) {
+MULTINODE_TEST(RemotePortalTest, TransferBackAndForth) {
   IpczHandle c = SpawnTestNode<TransferBackAndForthClient>();
 
   constexpr std::string_view kMessage = "hihihihi";
@@ -176,22 +176,14 @@
     EXPECT_EQ(IPCZ_RESULT_OK,
               WaitToGet(other_client, nullptr, {&portals[i], 1}));
 
-    IpczDriverHandle blob = reference_drivers::Blob::ReleaseAsHandle(
-        MakeRefCounted<reference_drivers::Blob>(absl::StrCat(absl::Dec(i))));
-    IpczHandle box;
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Box(node(), blob, IPCZ_NO_FLAGS, nullptr, &box));
+    IpczHandle box = BoxBlob(absl::StrCat(absl::Dec(i)));
     EXPECT_EQ(IPCZ_RESULT_OK, Put(portals[i], "", {&box, 1}));
   }
 
   for (size_t i = 0; i < kHugeNumberOfPortalsCount; ++i) {
     IpczHandle box;
     EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(portals[i], nullptr, {&box, 1}));
-
-    IpczDriverHandle blob;
-    EXPECT_EQ(IPCZ_RESULT_OK, ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob));
-    EXPECT_EQ(absl::StrCat(absl::Dec(i)),
-              reference_drivers::Blob::TakeFromHandle(blob)->message());
+    EXPECT_EQ(absl::StrCat(absl::Dec(i)), UnboxBlob(box));
   }
 
   EXPECT_EQ(IPCZ_RESULT_OK, Put(b, "", {&other_client, 1}));
@@ -200,7 +192,7 @@
   Close(b);
 }
 
-TEST_P(RemotePortalTest, HugeNumberOfPortals) {
+MULTINODE_TEST(RemotePortalTest, HugeNumberOfPortals) {
   // Opens a very large number of portals, and sends them all to client nodes.
   // The client nodes exchange these portals with each other and transmit
   // parcels over them, with and without driver objects. This exercises
@@ -238,7 +230,7 @@
   Close(b);
 }
 
-TEST_P(RemotePortalTest, RoutingStressTest) {
+MULTINODE_TEST(RemotePortalTest, RoutingStressTest) {
   // This test spawns a bunch of nodes and bounces two portals back and forth
   // among them over a large number of iterations, then waits for all the
   // intermediate routers to be removed. Every iteration also sends a message
@@ -254,11 +246,7 @@
 
   auto [a, b] = OpenPortals();
   for (size_t j = 0; j < kRouteExpansionStressTestNumIterations; ++j) {
-    IpczDriverHandle blob = reference_drivers::Blob::ReleaseAsHandle(
-        MakeRefCounted<reference_drivers::Blob>(absl::StrCat(absl::Dec(j))));
-    IpczHandle box;
-    EXPECT_EQ(IPCZ_RESULT_OK,
-              ipcz().Box(node(), blob, IPCZ_NO_FLAGS, nullptr, &box));
+    IpczHandle box = BoxBlob(absl::StrCat(absl::Dec(j)));
     EXPECT_EQ(IPCZ_RESULT_OK,
               Put(a, absl::StrCat("a", absl::Dec(j)), {&box, 1}));
     EXPECT_EQ(IPCZ_RESULT_OK, Put(b, absl::StrCat("b", absl::Dec(j))));
@@ -279,11 +267,7 @@
     EXPECT_EQ(absl::StrCat("b", absl::Dec(i)), message);
     EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
     EXPECT_EQ(absl::StrCat("a", absl::Dec(i)), message);
-
-    IpczDriverHandle blob;
-    EXPECT_EQ(IPCZ_RESULT_OK, ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob));
-    EXPECT_EQ(absl::StrCat(absl::Dec(i)),
-              reference_drivers::Blob::TakeFromHandle(blob)->message());
+    EXPECT_EQ(absl::StrCat(absl::Dec(i)), UnboxBlob(box));
   }
 
   for (auto& pair : client_pairs) {
@@ -340,7 +324,7 @@
 #else
 #define MAYBE_DisconnectThroughProxy DisconnectThroughProxy
 #endif
-TEST_P(RemotePortalTest, MAYBE_DisconnectThroughProxy) {
+MULTINODE_TEST(RemotePortalTest, MAYBE_DisconnectThroughProxy) {
   // Exercises node disconnection. Namely if portals on nodes 1 and 3 are
   // connected via proxy on node 2, and node 3 disappears, node 1's portal
   // should observe peer closure.
@@ -372,7 +356,5 @@
   CloseAll({c1, c2, c3});
 }
 
-INSTANTIATE_MULTINODE_TEST_SUITE_P(RemotePortalTest);
-
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/test/multinode_test.cc b/third_party/ipcz/src/test/multinode_test.cc
index 6d92c89e..e2d7855 100644
--- a/third_party/ipcz/src/test/multinode_test.cc
+++ b/third_party/ipcz/src/test/multinode_test.cc
@@ -4,15 +4,19 @@
 
 #include "test/multinode_test.h"
 
+#include <cstring>
 #include <map>
 #include <string>
 #include <thread>
 
 #include "ipcz/ipcz.h"
 #include "reference_drivers/async_reference_driver.h"
-#include "reference_drivers/blob.h"
 #include "reference_drivers/sync_reference_driver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
 #include "third_party/abseil-cpp/absl/base/macros.h"
+#include "third_party/abseil-cpp/absl/strings/str_cat.h"
+#include "third_party/abseil-cpp/absl/strings/str_split.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/ipcz/src/test_buildflags.h"
@@ -28,15 +32,43 @@
 
 namespace {
 
+using TestDriverMap = std::map<std::string, TestDriver*>;
+TestDriverMap& GetTestDrivers() {
+  static TestDriverMap* drivers = new TestDriverMap();
+  return *drivers;
+}
+
+using TestNodeMap = std::map<std::string, TestNodeFactory>;
+TestNodeMap& GetTestNodes() {
+  static TestNodeMap* nodes = new TestNodeMap();
+  return *nodes;
+}
+
+struct RegisteredMultinodeTest {
+  RegisteredMultinodeTest() = default;
+  ~RegisteredMultinodeTest() = default;
+
+  const char* test_suite_name;
+  const char* test_name;
+  const char* filename;
+  int line;
+  MultinodeTestFactory factory;
+};
+
+std::vector<RegisteredMultinodeTest>& GetRegisteredMultinodeTests() {
+  static auto* tests = new std::vector<RegisteredMultinodeTest>();
+  return *tests;
+}
+
 // Launches a new node on a dedicated thread within the same process. All
 // connections use the synchronous single-process driver.
 class InProcessTestNodeController : public TestNode::TestNodeController {
  public:
-  InProcessTestNodeController(DriverMode driver_mode,
+  InProcessTestNodeController(TestDriver* test_driver,
                               std::unique_ptr<TestNode> test_node)
       : client_thread_(absl::in_place,
                        &RunTestNode,
-                       driver_mode,
+                       test_driver,
                        std::move(test_node)) {}
 
   ~InProcessTestNodeController() override { ABSL_ASSERT(!client_thread_); }
@@ -61,15 +93,112 @@
   }
 
  private:
-  static void RunTestNode(DriverMode driver_mode,
+  static void RunTestNode(TestDriver* test_driver,
                           std::unique_ptr<TestNode> test_node) {
-    test_node->Initialize(driver_mode, IPCZ_NO_FLAGS);
+    test_node->Initialize(test_driver);
     test_node->NodeBody();
   }
 
   absl::optional<std::thread> client_thread_;
 };
 
+class InProcessTestDriverBase : public TestDriver {
+ public:
+  Ref<TestNode::TestNodeController> SpawnTestNode(
+      TestNode& source,
+      const TestNodeDetails& details,
+      IpczDriverHandle our_transport,
+      IpczDriverHandle their_transport) override {
+    std::unique_ptr<TestNode> test_node = details.factory();
+    test_node->SetTransport(their_transport);
+    return MakeRefCounted<InProcessTestNodeController>(this,
+                                                       std::move(test_node));
+  }
+
+  IpczDriverHandle GetClientTestNodeTransport() override {
+    ABSL_HARDENING_ASSERT(false);
+    return IPCZ_INVALID_DRIVER_HANDLE;
+  }
+};
+
+class SyncTestDriver : public InProcessTestDriverBase {
+ public:
+  const IpczDriver& GetIpczDriver() const override {
+    return reference_drivers::kSyncReferenceDriver;
+  }
+
+  const char* GetName() const override { return internal::kSyncTestDriverName; }
+
+  TestNode::TransportPair CreateTransports(TestNode& source) const override {
+    TestNode::TransportPair transports;
+    const IpczResult result = GetIpczDriver().CreateTransports(
+        IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE, IPCZ_NO_FLAGS,
+        nullptr, &transports.ours, &transports.theirs);
+    ABSL_ASSERT(result == IPCZ_RESULT_OK);
+    return transports;
+  }
+
+  IpczConnectNodeFlags GetExtraClientConnectNodeFlags() const override {
+    return IPCZ_NO_FLAGS;
+  }
+};
+
+class AsyncTestDriver : public InProcessTestDriverBase {
+ public:
+  enum Mode {
+    kDefault,
+    kForceBrokering,
+    kDelegateAllocation,
+    kForceBrokeringAndDelegateAllocation,
+  };
+  AsyncTestDriver(const char* name, Mode mode) : name_(name), mode_(mode) {}
+
+  const IpczDriver& GetIpczDriver() const override {
+    if (mode_ == kForceBrokering ||
+        mode_ == kForceBrokeringAndDelegateAllocation) {
+      return reference_drivers::kAsyncReferenceDriverWithForcedBrokering;
+    }
+    return reference_drivers::kAsyncReferenceDriver;
+  }
+
+  const char* GetName() const override { return name_; }
+
+  TestNode::TransportPair CreateTransports(TestNode& source) const override {
+    reference_drivers::AsyncTransportPair transports =
+        reference_drivers::CreateAsyncTransportPair();
+    return {
+        .ours = transports.broker,
+        .theirs = transports.non_broker,
+    };
+  }
+
+  IpczConnectNodeFlags GetExtraClientConnectNodeFlags() const override {
+    if (mode_ == kDelegateAllocation ||
+        mode_ == kForceBrokeringAndDelegateAllocation) {
+      return IPCZ_CONNECT_NODE_TO_ALLOCATION_DELEGATE;
+    }
+    return IPCZ_NO_FLAGS;
+  }
+
+ private:
+  const char* const name_;
+  const Mode mode_;
+};
+
+TestDriverRegistration<SyncTestDriver> kRegisterSyncDriver;
+TestDriverRegistration<AsyncTestDriver> kRegisterAsyncDriver{
+    internal::kAsyncTestDriverName, AsyncTestDriver::kDefault};
+TestDriverRegistration<AsyncTestDriver> kRegisterAsyncDriverWithDelegatedAlloc{
+    internal::kAsyncDelegatedAllocTestDriverName,
+    AsyncTestDriver::kDelegateAllocation};
+TestDriverRegistration<AsyncTestDriver> kRegisterAsyncDriverWithForcedBrokering{
+    internal::kAsyncForcedBrokeringTestDriverName,
+    AsyncTestDriver::kForceBrokering};
+TestDriverRegistration<AsyncTestDriver>
+    kRegisterAsyncDriverWithDelegatedAllocAndForcedBrokering{
+        internal::kAsyncDelegatedAllocAndForcedBrokeringTestDriverName,
+        AsyncTestDriver::kForceBrokeringAndDelegateAllocation};
+
 #if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
 // Controls a node running within an isolated child process.
 class ChildProcessTestNodeController : public TestNode::TestNodeController {
@@ -92,10 +221,73 @@
   const pid_t pid_;
   absl::optional<bool> result_;
 };
+
+class MultiprocessTestDriver : public TestDriver {
+ public:
+  const IpczDriver& GetIpczDriver() const override {
+    return reference_drivers::kMultiprocessReferenceDriver;
+  }
+
+  const char* GetName() const override {
+    return internal::kMultiprocessTestDriverName;
+  }
+
+  TestNode::TransportPair CreateTransports(TestNode& source) const override {
+    TestNode::TransportPair transports;
+    const IpczResult result = GetIpczDriver().CreateTransports(
+        IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE, IPCZ_NO_FLAGS,
+        nullptr, &transports.ours, &transports.theirs);
+    ABSL_ASSERT(result == IPCZ_RESULT_OK);
+    return transports;
+  }
+
+  Ref<TestNode::TestNodeController> SpawnTestNode(
+      TestNode& source,
+      const TestNodeDetails& details,
+      IpczDriverHandle our_transport,
+      IpczDriverHandle their_transport) override {
+    reference_drivers::FileDescriptor socket =
+        reference_drivers::TakeMultiprocessTransportDescriptor(their_transport);
+    return MakeRefCounted<ChildProcessTestNodeController>(
+        child_launcher_.Launch(details.name, std::move(socket)));
+  }
+
+  IpczConnectNodeFlags GetExtraClientConnectNodeFlags() const override {
+    return IPCZ_NO_FLAGS;
+  }
+
+  IpczDriverHandle GetClientTestNodeTransport() override {
+    auto transport = MakeRefCounted<reference_drivers::SocketTransport>(
+        TestChildLauncher::TakeChildSocketDescriptor());
+    return reference_drivers::CreateMultiprocessTransport(std::move(transport));
+  }
+
+ private:
+  TestChildLauncher child_launcher_;
+};
+
+TestDriverRegistration<MultiprocessTestDriver> kRegisterMultiprocessDriver;
+
 #endif
 
 }  // namespace
 
+namespace internal {
+
+const char kSyncTestDriverName[] = "Sync";
+const char kAsyncTestDriverName[] = "Async";
+const char kAsyncDelegatedAllocTestDriverName[] = "AsyncDelegatedAlloc";
+const char kAsyncForcedBrokeringTestDriverName[] = "AsyncForcedBrokering";
+const char kAsyncDelegatedAllocAndForcedBrokeringTestDriverName[] =
+    "AsyncDelegatedAllocAndForcedBrokering";
+const char kMultiprocessTestDriverName[] = "Multiprocess";
+
+}  // namespace internal
+
+TestDriverRegistrationImpl::TestDriverRegistrationImpl(TestDriver& driver) {
+  GetTestDrivers()[driver.GetName()] = &driver;
+}
+
 TestNode::~TestNode() {
   for (auto& spawned_node : spawned_nodes_) {
     EXPECT_TRUE(spawned_node->WaitForShutdown());
@@ -103,59 +295,31 @@
 
   // If we never connected to the broker, make sure we don't leak our transport.
   if (transport_ != IPCZ_INVALID_DRIVER_HANDLE) {
-    GetDriver().Close(transport_, IPCZ_NO_FLAGS, nullptr);
+    test_driver_->GetIpczDriver().Close(transport_, IPCZ_NO_FLAGS, nullptr);
   }
 
   CloseThisNode();
 }
 
 const IpczDriver& TestNode::GetDriver() const {
-  static IpczDriver kInvalidDriver = {};
-  switch (driver_mode_) {
-    case DriverMode::kSync:
-      return reference_drivers::kSyncReferenceDriver;
-
-    case DriverMode::kAsync:
-      return reference_drivers::kAsyncReferenceDriver;
-
-    case DriverMode::kAsyncDelegatedAlloc:
-      return reference_drivers::kAsyncReferenceDriver;
-
-    case DriverMode::kAsyncObjectBrokering:
-      return reference_drivers::kAsyncReferenceDriverWithForcedBrokering;
-
-    case DriverMode::kAsyncObjectBrokeringAndDelegatedAlloc:
-      return reference_drivers::kAsyncReferenceDriverWithForcedBrokering;
-
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-    case DriverMode::kMultiprocess:
-      return reference_drivers::kMultiprocessReferenceDriver;
-#endif
-
-    default:
-      // Other modes not yet supported.
-      ABSL_ASSERT(false);
-      return kInvalidDriver;
-  }
+  return test_driver_->GetIpczDriver();
 }
 
-void TestNode::Initialize(DriverMode driver_mode,
-                          IpczCreateNodeFlags create_node_flags) {
-  driver_mode_ = driver_mode;
+void TestNode::Initialize(TestDriver* test_driver) {
+  test_driver_ = test_driver;
 
+  const IpczCreateNodeFlags flags =
+      GetDetails().is_broker ? IPCZ_CREATE_NODE_AS_BROKER : IPCZ_NO_FLAGS;
   ABSL_ASSERT(node_ == IPCZ_INVALID_HANDLE);
   const IpczResult result =
-      ipcz().CreateNode(&GetDriver(), IPCZ_INVALID_DRIVER_HANDLE,
-                        create_node_flags, nullptr, &node_);
+      ipcz().CreateNode(&test_driver_->GetIpczDriver(),
+                        IPCZ_INVALID_DRIVER_HANDLE, flags, nullptr, &node_);
   ABSL_ASSERT(result == IPCZ_RESULT_OK);
 }
 
 void TestNode::ConnectToParent(absl::Span<IpczHandle> portals,
                                IpczConnectNodeFlags flags) {
-  if (driver_mode_ == DriverMode::kAsyncDelegatedAlloc ||
-      driver_mode_ == DriverMode::kAsyncObjectBrokeringAndDelegatedAlloc) {
-    flags |= IPCZ_CONNECT_NODE_TO_ALLOCATION_DELEGATE;
-  }
+  flags |= test_driver_->GetExtraClientConnectNodeFlags();
   IpczDriverHandle transport =
       std::exchange(transport_, IPCZ_INVALID_DRIVER_HANDLE);
   ABSL_ASSERT(transport != IPCZ_INVALID_DRIVER_HANDLE);
@@ -183,22 +347,45 @@
 }
 
 IpczHandle TestNode::BoxBlob(std::string_view contents) {
-  auto blob = MakeRefCounted<reference_drivers::Blob>(contents);
+  IpczDriverHandle memory;
+  IpczResult result = GetDriver().AllocateSharedMemory(
+      contents.size(), IPCZ_NO_FLAGS, nullptr, &memory);
+  ABSL_ASSERT(result == IPCZ_RESULT_OK);
+
+  IpczDriverHandle mapping;
+  void* base;
+  result = GetDriver().MapSharedMemory(memory, IPCZ_NO_FLAGS, nullptr, &base,
+                                       &mapping);
+  ABSL_ASSERT(result == IPCZ_RESULT_OK);
+  memcpy(base, contents.data(), contents.size());
+  GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr);
+
   IpczHandle box;
-  const IpczResult result = ipcz().Box(
-      node_, reference_drivers::Blob::ReleaseAsHandle(std::move(blob)),
-      IPCZ_NO_FLAGS, nullptr, &box);
+  result = ipcz().Box(node_, memory, IPCZ_NO_FLAGS, nullptr, &box);
   ABSL_ASSERT(result == IPCZ_RESULT_OK);
   return box;
 }
 
-// Extracts the string contents of a boxed test driver blob.
 std::string TestNode::UnboxBlob(IpczHandle box) {
-  IpczDriverHandle handle;
-  const IpczResult result = ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &handle);
+  IpczDriverHandle memory;
+  IpczResult result = ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &memory);
   ABSL_ASSERT(result == IPCZ_RESULT_OK);
-  auto blob = reference_drivers::Blob::TakeFromHandle(handle);
-  return blob->message();
+
+  IpczSharedMemoryInfo info = {.size = sizeof(info)};
+  result =
+      GetDriver().GetSharedMemoryInfo(memory, IPCZ_NO_FLAGS, nullptr, &info);
+  ABSL_ASSERT(result == IPCZ_RESULT_OK);
+
+  IpczDriverHandle mapping;
+  void* base;
+  result = GetDriver().MapSharedMemory(memory, IPCZ_NO_FLAGS, nullptr, &base,
+                                       &mapping);
+  ABSL_ASSERT(result == IPCZ_RESULT_OK);
+
+  std::string contents(static_cast<const char*>(base), info.region_num_bytes);
+  GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr);
+  GetDriver().Close(memory, IPCZ_NO_FLAGS, nullptr);
+  return contents;
 }
 
 void TestNode::CloseThisNode() {
@@ -209,74 +396,18 @@
 }
 
 Ref<TestNode::TestNodeController> TestNode::SpawnTestNodeImpl(
-    IpczHandle from_node,
-    const internal::TestNodeDetails& details,
-    PortalsOrTransport portals_or_transport,
-    IpczConnectNodeFlags flags) {
-  struct Connect {
-    Connect(TestNode& test, IpczConnectNodeFlags flags)
-        : test(test), flags(flags) {}
-
-    IpczDriverHandle operator()(absl::Span<IpczHandle> portals) {
-      TransportPair transports = test.CreateTransports();
-      const IpczResult result =
-          test.ipcz().ConnectNode(test.node(), transports.ours, portals.size(),
-                                  flags, nullptr, portals.data());
-      ABSL_ASSERT(result == IPCZ_RESULT_OK);
-      return transports.theirs;
-    }
-
-    IpczDriverHandle operator()(IpczDriverHandle transport) {
-      return transport;
-    }
-
-    TestNode& test;
-    const IpczConnectNodeFlags flags;
-  };
-
-  Connect connect(*this, flags);
-  IpczDriverHandle their_transport = absl::visit(connect, portals_or_transport);
-
-  Ref<TestNodeController> controller;
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-  if (driver_mode_ == DriverMode::kMultiprocess) {
-    reference_drivers::FileDescriptor socket =
-        reference_drivers::TakeMultiprocessTransportDescriptor(their_transport);
-    controller = MakeRefCounted<ChildProcessTestNodeController>(
-        child_launcher_.Launch(details.name, std::move(socket)));
-  }
-#endif
-
-  if (!controller) {
-    std::unique_ptr<TestNode> test_node = details.factory();
-    test_node->SetTransport(their_transport);
-    controller = MakeRefCounted<InProcessTestNodeController>(
-        driver_mode_, std::move(test_node));
-  }
-
+    const TestNodeDetails& details,
+    IpczDriverHandle& our_transport) {
+  TransportPair transports = CreateTransports();
+  Ref<TestNodeController> controller = test_driver_->SpawnTestNode(
+      *this, details, transports.ours, transports.theirs);
   spawned_nodes_.push_back(controller);
+  our_transport = transports.ours;
   return controller;
 }
 
 TestNode::TransportPair TestNode::CreateTransports() {
-  if (driver_mode_ == DriverMode::kAsync ||
-      driver_mode_ == DriverMode::kAsyncDelegatedAlloc ||
-      driver_mode_ == DriverMode::kAsyncObjectBrokering ||
-      driver_mode_ == DriverMode::kAsyncObjectBrokeringAndDelegatedAlloc) {
-    reference_drivers::AsyncTransportPair transports =
-        reference_drivers::CreateAsyncTransportPair();
-    return {
-        .ours = transports.broker,
-        .theirs = transports.non_broker,
-    };
-  }
-
-  TransportPair transports;
-  const IpczResult result = GetDriver().CreateTransports(
-      IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE, IPCZ_NO_FLAGS,
-      nullptr, &transports.ours, &transports.theirs);
-  ABSL_ASSERT(result == IPCZ_RESULT_OK);
-  return transports;
+  return test_driver_->CreateTransports(*this);
 }
 
 void TestNode::SetTransport(IpczDriverHandle transport) {
@@ -284,22 +415,72 @@
   transport_ = transport;
 }
 
-int TestNode::RunAsChild() {
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-  auto transport = MakeRefCounted<reference_drivers::SocketTransport>(
-      TestChildLauncher::TakeChildSocketDescriptor());
-  SetTransport(
-      reference_drivers::CreateMultiprocessTransport(std::move(transport)));
-  Initialize(DriverMode::kMultiprocess, IPCZ_NO_FLAGS);
+int TestNode::RunAsChild(TestDriver* test_driver) {
+  SetTransport(test_driver->GetClientTestNodeTransport());
+  Initialize(test_driver);
   NodeBody();
 
   const int exit_code = ::testing::Test::HasFailure() ? 1 : 0;
   return exit_code;
-#else
-  // Not supported outside of Linux.
-  ABSL_ASSERT(false);
-  return 0;
-#endif
+}
+
+void RegisterMultinodeTestNode(std::string_view node_name,
+                               TestNodeFactory factory) {
+  GetTestNodes()[std::string(node_name)] = factory;
+}
+
+void RegisterMultinodeTest(
+    const char* test_suite_name,
+    const char* test_name,
+    const char* filename,
+    int line,
+    std::function<testing::Test*(TestDriver* driver)> factory) {
+  RegisteredMultinodeTest test;
+  test.test_suite_name = test_suite_name;
+  test.test_name = test_name;
+  test.filename = filename;
+  test.line = line;
+  test.factory = factory;
+  GetRegisteredMultinodeTests().push_back(std::move(test));
+}
+
+int RunChildProcessTest(const std::string& test_name) {
+  std::pair<std::string, std::string> split = absl::StrSplit(test_name, '/');
+  auto [node_name, driver_name] = split;
+  if (driver_name.empty()) {
+    return multi_process_function_list::InvokeChildProcessTestMain(test_name);
+  }
+
+  auto& drivers = GetTestDrivers();
+  auto driver_it = drivers.find(driver_name);
+  if (driver_it == drivers.end()) {
+    return -1;
+  }
+
+  auto& nodes = GetTestNodes();
+  auto node_it = nodes.find(node_name);
+  if (node_it == nodes.end()) {
+    return -1;
+  }
+
+  std::unique_ptr<TestNode> node = node_it->second();
+  return node->RunAsChild(driver_it->second);
+}
+
+void RegisterMultinodeTests() {
+  multi_process_function_list::SetChildProcessTestRunner(&RunChildProcessTest);
+  for (auto& test : GetRegisteredMultinodeTests()) {
+    for (const auto& [test_driver_name, test_driver] : GetTestDrivers()) {
+      const std::string test_name =
+          absl::StrCat(test.test_name, "/", test_driver_name);
+      ::testing::RegisterTest(
+          test.test_suite_name, test_name.c_str(), nullptr, nullptr,
+          test.filename, test.line,
+          [factory = test.factory, test_driver = test_driver] {
+            return factory(test_driver);
+          });
+    }
+  }
 }
 
 }  // namespace ipcz::test
diff --git a/third_party/ipcz/src/test/multinode_test.h b/third_party/ipcz/src/test/multinode_test.h
index eb7f437..3951840 100644
--- a/third_party/ipcz/src/test/multinode_test.h
+++ b/third_party/ipcz/src/test/multinode_test.h
@@ -9,22 +9,18 @@
 #include <string>
 #include <string_view>
 #include <type_traits>
+#include <utility>
 #include <vector>
 
 #include "ipcz/ipcz.h"
 #include "test/test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/multiprocess_func_list.h"
 #include "third_party/abseil-cpp/absl/base/macros.h"
 #include "third_party/abseil-cpp/absl/types/span.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/ipcz/src/test_buildflags.h"
 #include "util/ref_counted.h"
 
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-#include "test/test_child_launcher.h"
-#endif
-
 namespace ipcz::test {
 
 class TestNode;
@@ -32,79 +28,45 @@
 template <typename TestNodeType>
 class MultinodeTest;
 
-// Selects which driver will be used by test nodes. Interconnecting nodes must
-// always use the same driver.
-//
-// Multinode tests are parameterized over these modes to provide coverage of
-// various interesting constraints encountered in production. Some platforms
-// require driver objects to be relayed through a broker. Some environments
-// prevent nodes from allocating their own shared memory regions.
-//
-// Incongruity between synchronous and asynchronous test failures generally
-// indicates race conditions within ipcz, but many bugs will cause failures in
-// all driver modes. The synchronous version is generally easier to debug in
-// such cases.
-enum class DriverMode {
-  // Use the synchronous, single-process reference driver. This driver does not
-  // create any background threads and all ipcz operations (e.g. message
-  // delivery, portal transfer, proxy elimination, etc) complete synchronously
-  // from end-to-end. Each test node runs its test body on a dedicated thread
-  // within the test process.
-  kSync,
-
-  // Use the asynchronous single-process reference driver. Transport messages
-  // are received asynchronously, similar to how most production drivers are
-  // likely to operate in practice. Such asynchrony gives rise to
-  // non-determinism throughout ipcz proper and provides good coverage of
-  // potential race conditions.
-  //
-  // As with the kSync driver, each test node runs its test body on a dedicated
-  // thread within the test process.
-  kAsync,
-
-  // Use the same driver as kAsync, but non-broker nodes are forced to delegate
-  // shared memory allocation to their broker. This simulates the production
-  // constraints of some sandbox environments and exercises additional
-  // asynchrony in ipcz proper.
-  kAsyncDelegatedAlloc,
-
-  // Use the same driver as kAsync, but driver objects cannot be transmitted
-  // directly between non-brokers and must instead be relayed by a broker. This
-  // simulates the production constraints of some sandbox environments and
-  // exercises additional asynchrony in ipcz proper.
-  kAsyncObjectBrokering,
-
-  // Use the same driver as kAsync, imposing the additional constraints of both
-  // kAsyncDelegatedAlloc and kAsyncObjectBrokering as described above.
-  kAsyncObjectBrokeringAndDelegatedAlloc,
-
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-  // Use a multiprocess-capable driver (Linux only for now), with each test node
-  // running in its own isolated child process.
-  kMultiprocess,
-#endif
-};
-
 namespace internal {
 
-using TestNodeFactory = std::unique_ptr<TestNode> (*)();
-
 template <typename TestNodeType>
 std::unique_ptr<TestNode> MakeTestNode() {
   return std::make_unique<TestNodeType>();
 }
 
-// Type used to package metadata about a MULTINODE_TEST_NODE() invocation.
-struct TestNodeDetails {
-  const std::string_view name;
-  const TestNodeFactory factory;
-};
-
 template <typename T>
 static constexpr bool IsValidTestNodeType = std::is_base_of_v<TestNode, T>;
 
+extern const char kSyncTestDriverName[];
+extern const char kAsyncTestDriverName[];
+extern const char kAsyncDelegatedAllocTestDriverName[];
+extern const char kAsyncForcedBrokeringTestDriverName[];
+extern const char kAsyncDelegatedAllocAndForcedBrokeringTestDriverName[];
+extern const char kMultiprocessTestDriverName[];
+
 }  // namespace internal
 
+class TestDriver;
+
+using TestNodeFactory = std::unique_ptr<TestNode> (*)();
+
+// Type used to package metadata about a MULTINODE_TEST_NODE() or
+// MULTINODE_TEST() invocation.
+struct TestNodeDetails {
+  // A unique display name for the defined node body.
+  const std::string_view name;
+
+  // A factory function which can be used to instantiate the TestNode. Null for
+  // main MULTINODE_TEST() invocations.
+  const TestNodeFactory factory;
+
+  // Indicates whether the node is defined as a broker or non-broker node. By
+  // default, all nodes on non-brokers except for those emitted by main
+  // MULTINODE_TEST() invocations.
+  const bool is_broker;
+};
+
 // Base class to support tests which exercise behavior across multiple ipcz
 // nodes. These may be single-process on a synchronous driver, single-process on
 // an asynchronous (e.g. multiprocess) driver, or fully multiprocess.
@@ -115,9 +77,9 @@
 // GTest's Test class is not compatible with that behavior.
 //
 // Instead, while MULTINODE_TEST_NODE() invocations should be based directly on
-// TestNode or a derivative thereof. TEST_P() invocations for multinode tests
-// should be based on derivatives of MultinodeTest<T> (see below this class),
-// where T itself is a TestNode or some derivative thereof.
+// TestNode or a derivative thereof. MULTINODE_TEST() invocations for multinode
+// tests should be based on derivatives of MultinodeTest<T> (see below this
+// class), where T itself is a TestNode or some derivative thereof.
 //
 // This arrangement allows the main test body and its related
 // MULTINODE_TEST_NODE() invocations to be based on the same essential type,
@@ -142,6 +104,9 @@
   // ConnectToBroker() hasn't been called yet.
   IpczDriverHandle transport() const { return transport_; }
 
+  // Returns metadata regarding the definition of this TestNode type.
+  virtual const TestNodeDetails& GetDetails() const = 0;
+
   // Releases transport() to the caller. After calling this, it is no longer
   // valid to call either transport() or ConnectToBroker(), and this fixture
   // will not automatically close the transport on destruction.
@@ -149,13 +114,15 @@
     return std::exchange(transport_, IPCZ_INVALID_DRIVER_HANDLE);
   }
 
-  // The driver currently in use. Selected by test parameter.
+  // The active TestDriver implementation.
+  TestDriver* GetTestDriver() { return test_driver_; }
+
+  // The ipcz driver currently in use, as specified by the active TestDriver.
   const IpczDriver& GetDriver() const;
 
   // One-time initialization. Called internally during test setup. Should never
   // be called by individual test code.
-  void Initialize(DriverMode driver_mode,
-                  IpczCreateNodeFlags create_node_flags);
+  void Initialize(TestDriver* test_driver);
 
   // May be called at most once by the TestNode body to connect initial
   // `portals` to the node that spawned this one. Extra `flags` may be passed to
@@ -174,11 +141,11 @@
   // Opens a new portal pair on this node.
   std::pair<IpczHandle, IpczHandle> OpenPortals();
 
-  // Creates a new test driver blob object and boxes it. Returns a handle to the
-  // box.
+  // Creates a new driver memory object populated with `contents`, boxes it, and
+  // returns a handle to the new box.
   IpczHandle BoxBlob(std::string_view contents);
 
-  // Extracts the string contents of a boxed test driver blob.
+  // Extracts the string contents of a boxed driver memory object.
   std::string UnboxBlob(IpczHandle box);
 
   // Spawns a new test node of TestNodeType and populates `portals` with a set
@@ -187,7 +154,12 @@
   Ref<TestNodeController> SpawnTestNode(
       absl::Span<IpczHandle> portals,
       IpczConnectNodeFlags flags = IPCZ_NO_FLAGS) {
-    return SpawnTestNodeImpl(node_, TestNodeType::kDetails, portals, flags);
+    IpczDriverHandle our_transport;
+    auto controller = SpawnTestNodeImpl(TestNodeType::kDetails, our_transport);
+    const IpczResult result = ipcz().ConnectNode(
+        node(), our_transport, portals.size(), flags, nullptr, portals.data());
+    ABSL_ASSERT(result == IPCZ_RESULT_OK);
+    return controller;
   }
 
   // Shorthand for the above, for the common case with only one initial portal
@@ -203,10 +175,8 @@
   // its broker connection. The caller is resposible for the other end of that
   // connection.
   template <typename TestNodeType>
-  Ref<TestNodeController> SpawnTestNodeWithTransport(
-      IpczDriverHandle transport,
-      IpczConnectNodeFlags flags = IPCZ_NO_FLAGS) {
-    return SpawnTestNodeImpl(node_, TestNodeType::kDetails, transport, flags);
+  Ref<TestNodeController> SpawnTestNodeNoConnect(IpczDriverHandle& transport) {
+    return SpawnTestNodeImpl(TestNodeType::kDetails, transport);
   }
 
   // Forcibly closes this Node, severing all links to other nodes and implicitly
@@ -214,8 +184,8 @@
   void CloseThisNode();
 
   // The TestNode body provided by a MULTINODE_TEST_NODE() invocation. For main
-  // test definitions via TEST_P() with a MultinodeTest<T> fixture, this is
-  // unused in favor of TestBody().
+  // test definitions via MULTINODE_TEST() with a MultinodeTest<T> fixture, this
+  // is unused in favor of TestBody().
   virtual void NodeBody() {}
 
   // Creates a pair of transports appropriate for connecting this (broker or
@@ -229,71 +199,132 @@
   TransportPair CreateTransports();
 
   // Helper used to support multiprocess TestNode invocation.
-  int RunAsChild();
+  int RunAsChild(TestDriver* test_driver);
 
- private:
   // Sets the transport to use when connecting to a broker via ConnectBroker.
   // Must only be called once.
   void SetTransport(IpczDriverHandle transport);
 
+ private:
   // Spawns a new node using an appropriate configuration for the current
   // driver. Returns a controller which can be used to interact with the node
-  // outside of ipcz (e.g. to wait on its termination). `factory` is a function
-  // which can produce an in-process instance of the TestNode; `test_node_name`
-  // is a string which can be used to run the same TestNode subclass in a child
-  // process.
-  //
-  // If `portals_or_transport` is a span of IpczHandles, this creates a new
-  // pair of transports. One is given to the new node for connection back to us,
-  // and the other is connected immediately by the broker, filling in the
-  // handles with initial portals for the connection.
-  //
-  // Otherwise it's assumed to be a transport that will be given to the new
-  // node for connecting back to us. In this case the caller is responsible for
-  // the transport's peer.
-  using PortalsOrTransport =
-      absl::variant<absl::Span<IpczHandle>, IpczDriverHandle>;
-  Ref<TestNodeController> SpawnTestNodeImpl(
-      IpczHandle from_node,
-      const internal::TestNodeDetails& details,
-      PortalsOrTransport portals_or_transport,
-      IpczConnectNodeFlags flags);
+  // outside of ipcz (e.g. to wait on its termination). `details` describes the
+  // node to be launched, and `our_transport` on output receives a locally owned
+  // transport to the spawned node.
+  Ref<TestNodeController> SpawnTestNodeImpl(const TestNodeDetails& details,
+                                            IpczDriverHandle& our_transport);
 
-  DriverMode driver_mode_ = DriverMode::kSync;
+  TestDriver* test_driver_ = nullptr;
   IpczHandle node_ = IPCZ_INVALID_HANDLE;
   IpczDriverHandle transport_ = IPCZ_INVALID_DRIVER_HANDLE;
   std::vector<Ref<TestNodeController>> spawned_nodes_;
-
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-  TestChildLauncher child_launcher_;
-#endif
 };
 
 // Actual parameterized GTest Test fixture for multinode tests. This or a
-// subclass of it is required for TEST_P() invocations to function as proper
-// multinode tests.
+// subclass of it is required for MULTINODE_TEST() invocations to function as
+// proper multinode tests.
 template <typename TestNodeType = TestNode>
-class MultinodeTest : public TestNodeType,
-                      public ::testing::Test,
-                      public ::testing::WithParamInterface<DriverMode> {
+class MultinodeTest : public TestNodeType, public ::testing::Test {
  public:
   static_assert(internal::IsValidTestNodeType<TestNodeType>,
                 "MultinodeTest<T> requires T to be a subclass of TestNode.");
-  MultinodeTest() {
-    TestNode::Initialize(GetParam(), IPCZ_CREATE_NODE_AS_BROKER);
+};
+
+// TestDriver specifies an IpczDriver implementation to use for multinode tests.
+// It also implements launching and joining of other test nodes. A TestDriver
+// can be registered by statically initializing a corresponding
+// TestDriverRegistration. All multinode tests are run against all registered
+// TestDrivers.
+class TestDriver {
+ public:
+  // A reference to the actual IpczDriver implementation used by this
+  // TestDriver.
+  virtual const IpczDriver& GetIpczDriver() const = 0;
+
+  // A unique name for this test driver.
+  virtual const char* GetName() const = 0;
+
+  // Creates a new pair of transports suitable for connecting a broker node to a
+  // non-broker node. Called by `source`, who will adopt `ours` from the
+  // returned pair and pass `theirs` to some newly spawned test node.
+  virtual TestNode::TransportPair CreateTransports(TestNode& source) const = 0;
+
+  // Spawns a new TestNode instance for a TestNode described by `details`,
+  // passing `their_transport` to the new node so it can establish a connection.
+  // `our_transport` is the local driver transport endpoint that will be used to
+  // connect to the new node.
+  virtual Ref<TestNode::TestNodeController> SpawnTestNode(
+      TestNode& source,
+      const TestNodeDetails& details,
+      IpczDriverHandle our_transport,
+      IpczDriverHandle their_transport) = 0;
+
+  // Returns any extra flags to be provided to ConnectNode() when connecting to
+  // the main test node from a node spawned by the test.
+  virtual IpczConnectNodeFlags GetExtraClientConnectNodeFlags() const = 0;
+
+  // If the test driver launches test nodes in a separate subprocess, this is
+  // called to retrieve the driver transport which the test node should use to
+  // connect to the broker.
+  virtual IpczDriverHandle GetClientTestNodeTransport() = 0;
+};
+
+// Registers a TestDriver globally so that all MULTINODE_TEST() invocations are
+// parameterized over it.
+class TestDriverRegistrationImpl {
+ public:
+  explicit TestDriverRegistrationImpl(TestDriver& driver);
+};
+
+template <typename TestDriverType>
+class TestDriverRegistration {
+ public:
+  template <typename... Args>
+  explicit TestDriverRegistration(Args&&... args)
+      : driver(std::forward<Args>(args)...) {}
+
+  TestDriverType driver;
+  TestDriverRegistrationImpl registration{driver};
+};
+
+void RegisterMultinodeTestNode(std::string_view node_name,
+                               TestNodeFactory factory);
+
+template <typename NodeType>
+class MultinodeTestNodeRegistration {
+ public:
+  MultinodeTestNodeRegistration() {
+    RegisterMultinodeTestNode(NodeType::kDetails.name,
+                              NodeType::kDetails.factory);
   }
 };
 
-}  // namespace ipcz::test
+using MultinodeTestFactory = std::function<testing::Test*(TestDriver*)>;
+void RegisterMultinodeTest(const char* test_suite_name,
+                           const char* test_name,
+                           const char* filename,
+                           int line,
+                           MultinodeTestFactory factory);
 
-#define MULTINODE_TEST_CHILD_MAIN_HELPER(func, node_name) \
-  MULTIPROCESS_TEST_MAIN(func) {                          \
-    node_name node;                                       \
-    return node.RunAsChild();                             \
+// Registers a MULTINODE_TEST() test to be run when all tests are run. This
+// registers a unique instance of the test for each registered test driver.
+template <typename Test>
+class MultinodeTestRegistration {
+ public:
+  MultinodeTestRegistration(const char* test_suite_name,
+                            const char* test_name,
+                            const char* filename,
+                            int line) {
+    RegisterMultinodeTest(test_suite_name, test_name, filename, line,
+                          [](TestDriver* driver) { return new Test(driver); });
   }
+};
 
-#define MULTINODE_TEST_CHILD_MAIN(fixture, node_name) \
-  MULTINODE_TEST_CHILD_MAIN_HELPER(fixture##_##node_name##_Node, node_name)
+// Must be called before RUN_ALL_TESTS() is invoked in order for any defined
+// multinode tests to be run.
+void RegisterMultinodeTests();
+
+}  // namespace ipcz::test
 
 // Defines the main body of a non-broker test node for a multinode test. The
 // named node can be spawned by another node using SpawnTestNode<T> where T is
@@ -306,30 +337,53 @@
                   "ipcz::test::TestNode.");                                \
                                                                            \
    public:                                                                 \
-    static constexpr ::ipcz::test::internal::TestNodeDetails kDetails = {  \
+    static constexpr ::ipcz::test::TestNodeDetails kDetails = {            \
         .name = #fixture "_" #node_name "_Node",                           \
         .factory = &::ipcz::test::internal::MakeTestNode<node_name>,       \
+        .is_broker = false,                                                \
     };                                                                     \
+    const ::ipcz::test::TestNodeDetails& GetDetails() const override {     \
+      return kDetails;                                                     \
+    }                                                                      \
     void NodeBody() override;                                              \
   };                                                                       \
-  MULTINODE_TEST_CHILD_MAIN(fixture, node_name);                           \
+  ::ipcz::test::MultinodeTestNodeRegistration<node_name>                   \
+      kRegister_##node_name;                                               \
   void node_name::NodeBody()
 
-#if BUILDFLAG(ENABLE_IPCZ_MULTIPROCESS_TESTS)
-#define IPCZ_EXTRA_DRIVER_MODES , ipcz::test::DriverMode::kMultiprocess
-#else
-#define IPCZ_EXTRA_DRIVER_MODES
-#endif
+#define MULTINODE_TEST_NAME(name) #name
+#define MULTINODE_TEST_CLASS_NAME(name) name##_Test
+#define MULTINODE_TEST_REGISTRATION_NAME(name) kRegister_##name##_Test
 
-// TODO: Add other DriverMode enumerators here as support is landed.
-#define INSTANTIATE_MULTINODE_TEST_SUITE_P(suite)                        \
-  INSTANTIATE_TEST_SUITE_P(                                              \
-      , suite,                                                           \
-      ::testing::Values(                                                 \
-          ipcz::test::DriverMode::kSync, ipcz::test::DriverMode::kAsync, \
-          ipcz::test::DriverMode::kAsyncDelegatedAlloc,                  \
-          ipcz::test::DriverMode::kAsyncObjectBrokering,                 \
-          ipcz::test::DriverMode::kAsyncObjectBrokeringAndDelegatedAlloc \
-              IPCZ_EXTRA_DRIVER_MODES))
+#define MULTINODE_TEST(fixture, test_name)                                   \
+  class MULTINODE_TEST_CLASS_NAME(test_name) : public fixture {              \
+   public:                                                                   \
+    static constexpr ::ipcz::test::TestNodeDetails kDetails = {              \
+        .name = #fixture "_" #test_name "_Node",                             \
+        .factory = nullptr,                                                  \
+        .is_broker = true,                                                   \
+    };                                                                       \
+    explicit MULTINODE_TEST_CLASS_NAME(test_name)(::ipcz::test::TestDriver * \
+                                                  test_driver) {             \
+      TestNode::Initialize(test_driver);                                     \
+    }                                                                        \
+    ~MULTINODE_TEST_CLASS_NAME(test_name)() override = default;              \
+    MULTINODE_TEST_CLASS_NAME(test_name)                                     \
+    (const MULTINODE_TEST_CLASS_NAME(test_name) &) = delete;                 \
+    void operator=(const MULTINODE_TEST_CLASS_NAME(test_name) &) = delete;   \
+    const ::ipcz::test::TestNodeDetails& GetDetails() const override {       \
+      return kDetails;                                                       \
+    }                                                                        \
+                                                                             \
+   private:                                                                  \
+    void TestBody() override;                                                \
+  };                                                                         \
+  namespace {                                                                \
+  ::ipcz::test::MultinodeTestRegistration<                                   \
+      MULTINODE_TEST_CLASS_NAME(test_name)>                                  \
+      MULTINODE_TEST_REGISTRATION_NAME(test_name){                           \
+          #fixture, MULTINODE_TEST_NAME(test_name), __FILE__, __LINE__};     \
+  }                                                                          \
+  void MULTINODE_TEST_CLASS_NAME(test_name)::TestBody()
 
 #endif  // IPCZ_SRC_TEST_MULTINODE_TEST_H_
diff --git a/third_party/ipcz/src/test/test_base.h b/third_party/ipcz/src/test/test_base.h
index 3b3df98b..458082e 100644
--- a/third_party/ipcz/src/test/test_base.h
+++ b/third_party/ipcz/src/test/test_base.h
@@ -22,7 +22,7 @@
 // use ipcz::test::Test as a base. For multinode tests, use ipcz::test:TestNode
 // as a base for MULTINODE_TEST_NODE() invocations, and use
 // ipcz::test::MultinodeTest<T> (where T is a subclass of TestNode) for
-// TEST_P() invocations for parameterized multinode test bodies.
+// MULTINODE_TEST() invocations for parameterized multinode test bodies.
 class TestBase {
  public:
   using TrapEventHandler = std::function<void(const IpczTrapEvent&)>;
diff --git a/third_party/ipcz/src/test/test_child_launcher.cc b/third_party/ipcz/src/test/test_child_launcher.cc
index b6d9b0e..30d15b9e 100644
--- a/third_party/ipcz/src/test/test_child_launcher.cc
+++ b/third_party/ipcz/src/test/test_child_launcher.cc
@@ -162,7 +162,9 @@
   // Execute the test binary with an extra command-line switch that circumvents
   // the normal test runner path and instead runs the named TestNode's body.
   ArgList child_args = GetArgList();
-  child_args.push_back(MakeSwitch(kTestChildProcess, node_name.data()));
+  std::string test_main_name = absl::StrCat(
+      node_name.data(), "/", internal::kMultiprocessTestDriverName);
+  child_args.push_back(MakeSwitch(kTestChildProcess, test_main_name));
   child_args.push_back(MakeSwitch(kSocketFd, socket.release()));
 
   std::vector<char*> child_argv = MakeExecArgv(child_args);
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 8d59afe..9011d4f 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: d92f1d47573427e6417e29a3e82ea7d4c34fe0b5
+Version: 5b7bb37d41f45635fcb848841524cb18664336dd
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/android/avd/proto/creation/generic_android28.textpb b/tools/android/avd/proto/creation/generic_android28.textpb
index 40f9280..62f1c62 100644
--- a/tools/android/avd/proto/creation/generic_android28.textpb
+++ b/tools/android/avd/proto/creation/generic_android28.textpb
@@ -38,7 +38,7 @@
 min_sdk: 28
 install_privileged_apk_partition: "/system"
 
-privileged_apk {
+additional_apk {
   package_name: "chrome_internal/third_party/google3/apks/gmscore/x86"
   version: "0h92P9pOxl5e5D8ImqTtJydx6qu6qxOhsZxzeKerjCwC"
   dest_path: "generic_android28/gmscore_apks"
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 970f53cf..fdac582 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -136,9 +136,15 @@
   # Try updating the current repo if it exists and has no local diff.
   if os.path.isdir(dir):
     os.chdir(dir)
+    # Force re-clone if we're not using the GoB LLVM mirror since some users
+    # may still have the github repo checked out locally.
+    # TODO: remove this after a while
+    remotes = subprocess.check_output(['git', 'remote', '-v'],
+                                      universal_newlines=True)
     # git diff-index --quiet returns success when there is no diff.
     # Also check that the first commit is reachable.
-    if (RunCommand(['git', 'diff-index', '--quiet', 'HEAD'], fail_hard=False)
+    if ('googlesource' in remotes and RunCommand(
+        ['git', 'diff-index', '--quiet', 'HEAD'], fail_hard=False)
         and RunCommand(['git', 'fetch'], fail_hard=False)
         and RunCommand(['git', 'checkout', commit], fail_hard=False)):
       return
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9f410296..2201eb8 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -10741,6 +10741,7 @@
   <int value="281" label="MSDH_REQUEST_ALL_SCREENS_NOT_ALLOWED_FOR_ORIGIN"/>
   <int value="282" label="RFHI_CREATE_FENCED_FRAME_BAD_FRAME_TOKEN"/>
   <int value="283" label="RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN"/>
+  <int value="284" label="FF_FROZEN_SANDBOX_FLAGS_CHANGED"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -13185,11 +13186,6 @@
   <int value="1" label="Tablet"/>
 </enum>
 
-<enum name="BooleanTabSearchWithSearchQuery">
-  <int value="0" label="Action occurred from an unfiltered item list"/>
-  <int value="1" label="Action occurred from a filtered item list"/>
-</enum>
-
 <enum name="BooleanTerminated">
   <int value="0" label="Not terminated"/>
   <int value="1" label="Terminated"/>
@@ -41137,6 +41133,8 @@
   <int value="4329" label="ReplacedElementPaintedWithLargeOverflow"/>
   <int value="4330" label="FlexboxAbsPosJustifyContent"/>
   <int value="4331" label="MultipleFetchHandlersInServiceWorker"/>
+  <int value="4332"
+      label="StorageAccessAPI_requestStorageAccessForSite_Method"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -51784,6 +51782,14 @@
   </int>
 </enum>
 
+<enum name="IOSLensEntrypoint">
+  <summary>The entrypoint that the user entered Lens by.</summary>
+  <int value="0" label="Context Menu"/>
+  <int value="1" label="Home Screen Widget"/>
+  <int value="2" label="New Tab Page"/>
+  <int value="3" label="Omnibox Keyboard"/>
+</enum>
+
 <enum name="IOSLensSupportStatus">
   <summary>Whether lens is supported and if not the reason why.</summary>
   <int value="0" label="Lens Search Supported"/>
@@ -56111,7 +56117,6 @@
   <int value="-1969625771" label="MessagesForAndroidOfferNotification:enabled"/>
   <int value="-1966445414" label="StylusBatteryStatus:disabled"/>
   <int value="-1965587041" label="omnibox-tab-switch-suggestions"/>
-  <int value="-1965232124" label="TabSearchMediaTabs:disabled"/>
   <int value="-1964730371"
       label="AutofillFillMerchantPromoCodeFields:disabled"/>
   <int value="-1964261747" label="WebVrVsyncAlign:disabled"/>
@@ -60763,7 +60768,6 @@
   <int value="910725730" label="WebRtcHWVP9Encoding:disabled"/>
   <int value="911256399" label="GlobalMediaControlsModernUI:enabled"/>
   <int value="912119426" label="InfiniteSessionRestore:disabled"/>
-  <int value="912279548" label="TabSearchMediaTabs:enabled"/>
   <int value="913138924" label="RecurrentInterstitialFeature:disabled"/>
   <int value="913855453" label="VirtualKeyboardFloatingResizable:disabled"/>
   <int value="914708297" label="CCTResizableAllowResizeByUserGesture:enabled"/>
@@ -61607,6 +61611,7 @@
       label="CellularBypassESimInstallationConnectivityCheck:enabled"/>
   <int value="1446066818" label="WebRtcAnalogAgcClippingControl:enabled"/>
   <int value="1446349255" label="ArcEnableUsap:disabled"/>
+  <int value="1446904662" label="CCTResizableWindowAboveNavbar:disabled"/>
   <int value="1446946673" label="DesktopRestructuredLanguageSettings:disabled"/>
   <int value="1447295459" label="SyncPseudoUSSApps:enabled"/>
   <int value="1448684258" label="TabHoverCardImages:enabled"/>
@@ -62422,6 +62427,7 @@
   <int value="1969860311" label="DynamicColorGamut:enabled"/>
   <int value="1971472561" label="CrostiniImeSupport:disabled"/>
   <int value="1971964569" label="NewEncodeCpuLoadEstimator:disabled"/>
+  <int value="1972178452" label="CCTResizableWindowAboveNavbar:enabled"/>
   <int value="1972232935" label="DisplayMoveWindowAccels:enabled"/>
   <int value="1972720114" label="WebPaymentsJustInTimePaymentApp:enabled"/>
   <int value="1973376634" label="WebXRMultiGpu:enabled"/>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 315d869f..1b0eafd 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1435,7 +1435,7 @@
 </histogram>
 
 <histogram name="Arc.Notifications.ActionEnabled" enum="BooleanEnabled"
-    expires_after="2022-09-27">
+    expires_after="2023-02-15">
   <owner>yaoqq@google.com</owner>
   <owner>arc-framework@google.com</owner>
   <summary>
@@ -1445,7 +1445,7 @@
 </histogram>
 
 <histogram name="Arc.Notifications.ExpandState"
-    enum="ArcNotificationExpandState" expires_after="2022-10-05">
+    enum="ArcNotificationExpandState" expires_after="2023-02-15">
   <owner>yaoqq@google.com</owner>
   <owner>arc-framework@google.com</owner>
   <summary>
@@ -1459,7 +1459,7 @@
 </histogram>
 
 <histogram name="Arc.Notifications.InlineReplyEnabled" enum="BooleanEnabled"
-    expires_after="2022-10-07">
+    expires_after="2023-02-15">
   <owner>yaoqq@google.com</owner>
   <owner>arc-framework@google.com</owner>
   <summary>
@@ -1469,7 +1469,7 @@
 </histogram>
 
 <histogram name="Arc.Notifications.Style" enum="ArcNotificationStyle"
-    expires_after="2022-09-27">
+    expires_after="2023-02-15">
   <owner>yaoqq@google.com</owner>
   <owner>arc-framework@google.com</owner>
   <summary>Records the style of an Arc notification when it's created.</summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 750ac8c..214ddbb 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -1224,6 +1224,16 @@
   </summary>
 </histogram>
 
+<histogram name="Network.PortalSuspectedToOnlineTime" units="ms"
+    expires_after="2023-09-18">
+  <owner>stevenjb@chromium.org</owner>
+  <owner>cros-network-health@google.com</owner>
+  <summary>
+    Captive portal time from a portal-suspected state to an online state on
+    ChromeOS.
+  </summary>
+</histogram>
+
 <histogram name="Network.Radio.PossibleWakeupTrigger.RadioUtilsOverhead"
     units="ms" expires_after="2023-02-05">
   <owner>bashi@chromium.org</owner>
@@ -1309,6 +1319,16 @@
   </summary>
 </histogram>
 
+<histogram name="Network.RedirectFoundToOnlineTime" units="ms"
+    expires_after="2023-09-18">
+  <owner>stevenjb@chromium.org</owner>
+  <owner>cros-network-health@google.com</owner>
+  <summary>
+    Captive portal time from a redirect-found state to an online state on
+    ChromeOS.
+  </summary>
+</histogram>
+
 <histogram name="Network.Shill.Cellular.3GPPRegistrationDelayedDrop"
     enum="NetworkCellular3GPPRegistrationDelayedDrop"
     expires_after="2022-12-30">
diff --git a/tools/metrics/histograms/metadata/phonehub/histograms.xml b/tools/metrics/histograms/metadata/phonehub/histograms.xml
index 570f577..c4c0816 100644
--- a/tools/metrics/histograms/metadata/phonehub/histograms.xml
+++ b/tools/metrics/histograms/metadata/phonehub/histograms.xml
@@ -466,6 +466,7 @@
     request message was sent and a response was received.
   </summary>
   <token key="MessageType">
+    <variant name="FeatureSetup"/>
     <variant name="FetchCameraRollItemData"/>
     <variant name="FetchCameraRollItems"/>
     <variant name="InitiateCameraRollItemTransfer"/>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 7b649ea..5038c241 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -2350,33 +2350,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="Tabs.TabSearch.DistanceOf{Action}FromInitiallySelectedTabIn{State}List"
-    units="tabs" expires_after="2022-09-27">
-  <owner>elainechien@chromium.org</owner>
-  <owner>tluk@chromium.org</owner>
-  <owner>robliao@chromium.org</owner>
-  <owner>yuhengh@chromium.org</owner>
-  <owner>romanarora@chromium.org</owner>
-  <summary>
-    Tab Search is a feature that allows users to efficiently navigate, search,
-    and interact with their open and recently closed tabs.
-
-    This metric is emitted when the user performs a {Action} action on a tab in
-    the Open Tabs or Audio and Video section in a {State} state. This records
-    the absolute distance of the index of the tab from the index of the
-    initially selected tab.
-  </summary>
-  <token key="Action">
-    <variant name="CloseTab"/>
-    <variant name="SwitchTab"/>
-  </token>
-  <token key="State">
-    <variant name="Filtered" summary="Filtered by user search query."/>
-    <variant name="Unfiltered" summary="Unfiltered by user search query."/>
-  </token>
-</histogram>
-
 <histogram name="Tabs.TabSearch.Mojo.SwitchToTab" units="ms"
     expires_after="2022-12-25">
   <owner>kerenzhu@chromium.org</owner>
@@ -2430,21 +2403,6 @@
   </summary>
 </histogram>
 
-<histogram name="Tabs.TabSearch.NumMediaTabsOnOpen" units="tabs"
-    expires_after="2022-12-25">
-  <owner>elainechien@chromium.org</owner>
-  <owner>romanarora@chromium.org</owner>
-  <owner>tluk@chromium.org</owner>
-  <owner>robliao@chromium.org</owner>
-  <owner>yuhengh@chromium.org</owner>
-  <summary>
-    Tab Search is a feature that allows users to efficiently navigate, search,
-    and interact with their open and recently closed tabs. This records the
-    number of tabs using audio or video in the payload Tab Search receives when
-    it is first opened. This metric will be zero if Media Tabs are not enabled.
-  </summary>
-</histogram>
-
 <histogram name="Tabs.TabSearch.NumTabsClosedPerInstance" units="tabs"
     expires_after="2022-12-25">
   <owner>tluk@chromium.org</owner>
@@ -2573,28 +2531,6 @@
   </token>
 </histogram>
 
-<histogram
-    name="Tabs.TabSearch.WebUI.IndexRelativeToOpenTabsSectionOf{Action}InUnfilteredList"
-    units="tabs" expires_after="2022-09-27">
-  <owner>elainechien@chromium.org</owner>
-  <owner>tluk@chromium.org</owner>
-  <owner>robliao@chromium.org</owner>
-  <owner>yuhengh@chromium.org</owner>
-  <owner>romanarora@chromium.org</owner>
-  <summary>
-    Tab Search is a feature that allows users to efficiently navigate, search,
-    and interact with their open and recently closed tabs.
-
-    This records the index relative to the top of the Open Tabs section. This
-    metric is emitted when the user performs a {Action} action on a tab in the
-    Open Tabs section with no search query.
-  </summary>
-  <token key="Action">
-    <variant name="CloseTab"/>
-    <variant name="SwitchTab"/>
-  </token>
-</histogram>
-
 <histogram name="Tabs.TabSearch.WebUI.LoadCompletedTime" units="ms"
     expires_after="2022-12-25">
   <owner>tluk@chromium.org</owner>
@@ -2616,28 +2552,6 @@
   </summary>
 </histogram>
 
-<histogram name="Tabs.TabSearch.WebUI.MediaTab{Action}Action"
-    enum="BooleanTabSearchWithSearchQuery" expires_after="2022-10-26">
-  <owner>elainechien@chromium.org</owner>
-  <owner>romanarora@chromium.org</owner>
-  <owner>tluk@chromium.org</owner>
-  <owner>robliao@chromium.org</owner>
-  <owner>yuhengh@chromium.org</owner>
-  <summary>
-    Tab Search is a feature that allows users to efficiently navigate, search,
-    and interact with their open and recently closed tabs.
-
-    This metric will be emitted when the user uses Tab Search to perform a
-    {Action} action on a tab in the Audio and Video section and will track
-    whether this action was taken from a filtered search results list or the
-    default unfiltered list.
-  </summary>
-  <token key="Action">
-    <variant name="CloseTab"/>
-    <variant name="SwitchTab"/>
-  </token>
-</histogram>
-
 <histogram name="Tabs.TabSearch.WebUI.RecentlyClosed{Item}OpenAction"
     enum="TabSearchRecentlyClosedItemOpenAction" expires_after="2022-10-26">
   <owner>tluk@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 7220043..b5ce17f 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -14,7 +14,7 @@
         },
         "mac": {
             "hash": "65e00b832831bd94638cc93f391882a1539e5080",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/5b39b1050966a57df482206b84e96e31e602f48b/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/f87b4c862dcd8acc8879f7d69aa8e2a968f07bed/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "e1ad4861384b06d911a65f035317914b8cc975c6",
diff --git a/ui/accessibility/ax_tree_manager.cc b/ui/accessibility/ax_tree_manager.cc
index 311c239..5df0bcf3 100644
--- a/ui/accessibility/ax_tree_manager.cc
+++ b/ui/accessibility/ax_tree_manager.cc
@@ -20,7 +20,7 @@
 }
 
 // static
-AXTreeManager* AXTreeManager::FromID(AXTreeID ax_tree_id) {
+AXTreeManager* AXTreeManager::FromID(const AXTreeID& ax_tree_id) {
   return ax_tree_id != AXTreeIDUnknown() ? GetMap().GetManager(ax_tree_id)
                                          : nullptr;
 }
diff --git a/ui/accessibility/ax_tree_manager.h b/ui/accessibility/ax_tree_manager.h
index df43f35..2a2dfac 100644
--- a/ui/accessibility/ax_tree_manager.h
+++ b/ui/accessibility/ax_tree_manager.h
@@ -21,7 +21,7 @@
 // trees).
 class AX_EXPORT AXTreeManager : public AXTreeObserver {
  public:
-  static AXTreeManager* FromID(AXTreeID ax_tree_id);
+  static AXTreeManager* FromID(const AXTreeID& ax_tree_id);
   // If the child of `parent_node` exists in a separate child tree, return the
   // tree manager for that child tree. Otherwise, return nullptr.
   static AXTreeManager* ForChildTree(const AXNode& parent_node);
@@ -35,7 +35,7 @@
   // given |tree_id|. This allows for callers to access nodes outside of their
   // own tree. Returns nullptr if |tree_id| or |node_id| is not found.
   // TODO(kschmi): Remove |tree_id| parameter, as it's unnecessary.
-  virtual AXNode* GetNodeFromTree(const AXTreeID tree_id,
+  virtual AXNode* GetNodeFromTree(const AXTreeID& tree_id,
                                   const AXNodeID node_id) const = 0;
 
   // Returns the AXNode in the current tree that has the given |node_id|.
@@ -68,22 +68,22 @@
   AXEventGenerator& event_generator() { return event_generator_; }
 
   // AXTreeObserver implementation.
-  void OnTreeDataChanged(ui::AXTree* tree,
-                         const ui::AXTreeData& old_data,
-                         const ui::AXTreeData& new_data) override;
-  void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override {}
-  void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override {}
-  void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override {}
-  void OnNodeDeleted(ui::AXTree* tree, int32_t node_id) override {}
-  void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override {}
-  void OnRoleChanged(ui::AXTree* tree,
-                     ui::AXNode* node,
+  void OnTreeDataChanged(AXTree* tree,
+                         const AXTreeData& old_data,
+                         const AXTreeData& new_data) override;
+  void OnNodeWillBeDeleted(AXTree* tree, AXNode* node) override {}
+  void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override {}
+  void OnNodeCreated(AXTree* tree, AXNode* node) override {}
+  void OnNodeDeleted(AXTree* tree, int32_t node_id) override {}
+  void OnNodeReparented(AXTree* tree, AXNode* node) override {}
+  void OnRoleChanged(AXTree* tree,
+                     AXNode* node,
                      ax::mojom::Role old_role,
                      ax::mojom::Role new_role) override {}
   void OnAtomicUpdateFinished(
-      ui::AXTree* tree,
+      AXTree* tree,
       bool root_changed,
-      const std::vector<ui::AXTreeObserver::Change>& changes) override {}
+      const std::vector<AXTreeObserver::Change>& changes) override {}
 
  protected:
   AXTreeManager();
diff --git a/ui/accessibility/test_ax_tree_manager.cc b/ui/accessibility/test_ax_tree_manager.cc
index 392ba91..8e546fb 100644
--- a/ui/accessibility/test_ax_tree_manager.cc
+++ b/ui/accessibility/test_ax_tree_manager.cc
@@ -59,7 +59,7 @@
     GetMap().AddTreeManager(GetTreeID(), this);
 }
 
-AXNode* TestAXTreeManager::GetNodeFromTree(const AXTreeID tree_id,
+AXNode* TestAXTreeManager::GetNodeFromTree(const AXTreeID& tree_id,
                                            const AXNodeID node_id) const {
   return (ax_tree_ && GetTreeID() == tree_id) ? ax_tree_->GetFromId(node_id)
                                               : nullptr;
diff --git a/ui/accessibility/test_ax_tree_manager.h b/ui/accessibility/test_ax_tree_manager.h
index a3299a3..71e3740 100644
--- a/ui/accessibility/test_ax_tree_manager.h
+++ b/ui/accessibility/test_ax_tree_manager.h
@@ -44,7 +44,7 @@
   void SetTree(std::unique_ptr<AXTree> tree);
 
   // AXTreeManager implementation.
-  AXNode* GetNodeFromTree(const AXTreeID tree_id,
+  AXNode* GetNodeFromTree(const AXTreeID& tree_id,
                           const AXNodeID node_id) const override;
   AXNode* GetNodeFromTree(const AXNodeID node_id) const override;
   AXNode* GetParentNodeFromParentTreeAsAXNode() const override;
diff --git a/ui/chromeos/strings/network_element_localized_strings_provider.cc b/ui/chromeos/strings/network_element_localized_strings_provider.cc
index 6a40281..5b64c64e 100644
--- a/ui/chromeos/strings/network_element_localized_strings_provider.cc
+++ b/ui/chromeos/strings/network_element_localized_strings_provider.cc
@@ -431,6 +431,8 @@
       {"networkProxyWpad", IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD},
       {"networkProxyWpadNone", IDS_SETTINGS_INTERNET_NETWORK_PROXY_WPAD_NONE},
       {"remove", IDS_REMOVE},
+      {"controlledSettingPolicy",
+       IDS_SETTINGS_INTERNET_NETWORK_SETTING_MANAGED_BY_ADMIN_TOOLTIP},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/ui/chromeos/ui_chromeos_strings.grd b/ui/chromeos/ui_chromeos_strings.grd
index 720fc77d..b50da92 100644
--- a/ui/chromeos/ui_chromeos_strings.grd
+++ b/ui/chromeos/ui_chromeos_strings.grd
@@ -1728,6 +1728,9 @@
       <message name="IDS_SETTINGS_INTERNET_NETWORK_PROXY_PORT_INPUT_ACCESSIBILITY_LABEL" desc="Settings > Internet > Network details: Accessibility only label for proxy port number input field.">
         <ph name="INPUT_LABEL">$1<ex>HTTP Proxy</ex></ph> - Port
       </message>
+      <message name="IDS_SETTINGS_INTERNET_NETWORK_SETTING_MANAGED_BY_ADMIN_TOOLTIP" desc="Text displayed in the controlled settings bubble when a setting's value is managed by policy.">
+        This setting is managed by your administrator.
+      </message>
 
       <!-- Settings > Internet > SIM lock/unlock dialog -->
       <message name="IDS_SETTINGS_INTERNET_NETWORK_SIM_CHANGE_PIN" desc="Settings > Internet > Network details > Lock/unlock SIM card: Label for buton to change the PIN.">
diff --git a/ui/chromeos/ui_chromeos_strings_grd/IDS_SETTINGS_INTERNET_NETWORK_SETTING_MANAGED_BY_ADMIN_TOOLTIP.png.sha1 b/ui/chromeos/ui_chromeos_strings_grd/IDS_SETTINGS_INTERNET_NETWORK_SETTING_MANAGED_BY_ADMIN_TOOLTIP.png.sha1
new file mode 100644
index 0000000..4bfbef1
--- /dev/null
+++ b/ui/chromeos/ui_chromeos_strings_grd/IDS_SETTINGS_INTERNET_NETWORK_SETTING_MANAGED_BY_ADMIN_TOOLTIP.png.sha1
@@ -0,0 +1 @@
+9675c2df69fe7b2c050d0d200a4e2b83d583044a
\ No newline at end of file
diff --git a/ui/views/accessibility/views_ax_tree_manager.cc b/ui/views/accessibility/views_ax_tree_manager.cc
index 8e3d657..abad8ac 100644
--- a/ui/views/accessibility/views_ax_tree_manager.cc
+++ b/ui/views/accessibility/views_ax_tree_manager.cc
@@ -58,7 +58,7 @@
 }
 
 ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
-    const ui::AXTreeID tree_id,
+    const ui::AXTreeID& tree_id,
     const ui::AXNodeID node_id) const {
   if (!widget_ || !widget_->GetRootView())
     return nullptr;
diff --git a/ui/views/accessibility/views_ax_tree_manager.h b/ui/views/accessibility/views_ax_tree_manager.h
index 651c013..a94de70a 100644
--- a/ui/views/accessibility/views_ax_tree_manager.h
+++ b/ui/views/accessibility/views_ax_tree_manager.h
@@ -79,7 +79,7 @@
   void UnsetGeneratedEventCallbackForTesting();
 
   // AXTreeManager implementation.
-  ui::AXNode* GetNodeFromTree(const ui::AXTreeID tree_id,
+  ui::AXNode* GetNodeFromTree(const ui::AXTreeID& tree_id,
                               const ui::AXNodeID node_id) const override;
   ui::AXNode* GetNodeFromTree(const ui::AXNodeID node_id) const override;
   ui::AXTreeID GetParentTreeID() const override;
diff --git a/ui/views/cocoa/bridged_native_widget_unittest.mm b/ui/views/cocoa/bridged_native_widget_unittest.mm
index c132e64..dd060ee 100644
--- a/ui/views/cocoa/bridged_native_widget_unittest.mm
+++ b/ui/views/cocoa/bridged_native_widget_unittest.mm
@@ -291,6 +291,27 @@
 }
 @end
 
+@interface NativeWidgetMacNSWindowForTesting : NativeWidgetMacNSWindow {
+  BOOL hasShadowForTesting;
+}
+@end
+
+@implementation NativeWidgetMacNSWindowForTesting
+
+// Preserves the value of the hasShadow flag. During testing, -hasShadow will
+// always return NO because shadows are disabled on the bots.
+- (void)setHasShadow:(BOOL)flag {
+  hasShadowForTesting = flag;
+  [super setHasShadow:flag];
+}
+
+// Returns the value of the hasShadow flag during tests.
+- (BOOL)hasShadowForTesting {
+  return hasShadowForTesting;
+}
+
+@end
+
 namespace views {
 namespace test {
 
@@ -311,7 +332,7 @@
     ownership_ = params.ownership;
 
     base::scoped_nsobject<NativeWidgetMacNSWindow> window(
-        [[NativeWidgetMacNSWindow alloc]
+        [[NativeWidgetMacNSWindowForTesting alloc]
             initWithContentRect:ui::kWindowSizeDeterminedLater
                       styleMask:NSWindowStyleMaskBorderless
                         backing:NSBackingStoreBuffered
@@ -406,6 +427,12 @@
     return nil;
   }
 
+  bool BridgeWindowHasShadow() {
+    return
+        [base::mac::ObjCCast<NativeWidgetMacNSWindowForTesting>(bridge_window())
+            hasShadowForTesting];
+  }
+
  protected:
   std::unique_ptr<Widget> widget_;
   raw_ptr<MockNativeWidgetMac> native_widget_mac_;  // Weak. Owned by |widget_|.
@@ -903,9 +930,7 @@
 }
 
 // Tests the shadow type given in InitParams.
-// Disabled because shadows are disabled on the bots - see
-// https://crbug.com/899286.
-TEST_F(BridgedNativeWidgetInitTest, DISABLED_ShadowType) {
+TEST_F(BridgedNativeWidgetInitTest, ShadowType) {
   // Verify Widget::InitParam defaults and arguments added from SetUp().
   EXPECT_EQ(Widget::InitParams::TYPE_WINDOW_FRAMELESS, type_);
   EXPECT_EQ(Widget::InitParams::WindowOpacity::kOpaque, opacity_);
@@ -913,29 +938,27 @@
 
   CreateNewWidgetToInit();
   EXPECT_FALSE(
-      [bridge_window() hasShadow]);  // Default for NSWindowStyleMaskBorderless.
+      BridgeWindowHasShadow());  // Default for NSWindowStyleMaskBorderless.
   PerformInit();
 
   // Borderless is 0, so isn't really a mask. Check that nothing is set.
   EXPECT_EQ(NSWindowStyleMaskBorderless, [bridge_window() styleMask]);
-  EXPECT_TRUE(
-      [bridge_window() hasShadow]);  // ShadowType::kDefault means a shadow.
+  EXPECT_TRUE(BridgeWindowHasShadow());  // ShadowType::kDefault means a shadow.
 
   CreateNewWidgetToInit();
   shadow_type_ = Widget::InitParams::ShadowType::kNone;
   PerformInit();
-  EXPECT_FALSE([bridge_window() hasShadow]);  // Preserves lack of shadow.
+  EXPECT_FALSE(BridgeWindowHasShadow());  // Preserves lack of shadow.
 
   // Default for Widget::InitParams::TYPE_WINDOW.
   CreateNewWidgetToInit();
   PerformInit();
-  EXPECT_FALSE(
-      [bridge_window() hasShadow]);  // ShadowType::kNone removes shadow.
+  EXPECT_FALSE(BridgeWindowHasShadow());  // ShadowType::kNone removes shadow.
 
   shadow_type_ = Widget::InitParams::ShadowType::kDefault;
   CreateNewWidgetToInit();
   PerformInit();
-  EXPECT_TRUE([bridge_window() hasShadow]);  // Preserves shadow.
+  EXPECT_TRUE(BridgeWindowHasShadow());  // Preserves shadow.
 
   widget_.reset();
 }
diff --git a/ui/webui/resources/cr_components/help_bubble/help_bubble.html b/ui/webui/resources/cr_components/help_bubble/help_bubble.html
index 5ec2a385c8..e19004e 100644
--- a/ui/webui/resources/cr_components/help_bubble/help_bubble.html
+++ b/ui/webui/resources/cr_components/help_bubble/help_bubble.html
@@ -1,70 +1,88 @@
 <style include="cr-hidden-style">
   :host {
+    border-radius: 8px;
+    box-shadow: 0 6px 10px 4px rgba(60, 64, 67, 0.15), 0 2px 3px rgba(60, 64, 67, 0.3);
     position: absolute;
     z-index: 1;
   }
 
-  /* HelpBubblePosition.ABOVE */
-  :host([position='0']) {
-    transform: translateY(-100%);
-  }
-
-  /* HelpBubblePosition.BELOW */
-  :host([position='1']) {
-    transform: none;
-  }
-
-  /* HelpBubblePosition.LEFT */
-  :host([position='2']) {
-    transform: translate(-100%, -50%);
-  }
-
-  /* HelpBubblePosition.RIGHT */
-  :host([position='3']) {
-    transform: translateY(-50%);
-  }
-
   #arrow {
-    --help-bubble-arrow-size: 16px;
-    --help-bubble-arrow-offset: calc(var(--help-bubble-arrow-size) / 2);
+    --help-bubble-arrow-size: 11.3px;
+    --help-bubble-arrow-size-half: calc(var(--help-bubble-arrow-size) / 2);
+    --help-bubble-arrow-diameter: 16px; /* approx. */
+    --help-bubble-arrow-radius: calc(var(--help-bubble-arrow-diameter) / 2);
+    --help-bubble-arrow-edge-offset: 22px;
+    --help-bubble-arrow-offset: calc(var(--help-bubble-arrow-edge-offset) + var(--help-bubble-arrow-radius));
+    --help-bubble-arrow-border-radius: 2px;
+    position: absolute;
+  }
+
+  /* #arrow is rotated and positioned in a container to simplify positioning */
+  #inner-arrow {
     background-color: var(--help-bubble-background);
     height: var(--help-bubble-arrow-size);
+    left: calc(0px - var(--help-bubble-arrow-size-half));
     position: absolute;
-    transform: rotate(-45deg);
+    top: calc(0px - var(--help-bubble-arrow-size-half));
+    transform: rotate(45deg);
     width: var(--help-bubble-arrow-size);
     z-index: -1;
   }
 
-  /* Turns the arrow direction downwards, when the bubble is placed above
-   * the anchor element */
-  #arrow.above {
-    border-bottom-left-radius: 2px;
-    bottom: calc(0 - var(--help-bubble-arrow-offset));
-    left: calc(50% - var(--help-bubble-arrow-offset));
+  #arrow.bottom-edge {
+    bottom: 0;
   }
 
-  /* Turns the arrow direction upwards, when the bubble is placed below
-   * the anchor element */
-  #arrow.below {
-    border-top-right-radius: 2px;
-    left: calc(50% - var(--help-bubble-arrow-offset));
-    top: calc(0 - var(--help-bubble-arrow-offset));
+  #arrow.bottom-edge #inner-arrow {
+    border-bottom-right-radius: var(--help-bubble-arrow-border-radius);
   }
 
-  /* Turns the arrow direction to the right, when the bubble is placed to the
-   * left of the anchor element */
-  #arrow.left {
-    border-bottom-right-radius: 2px;
-    right: calc(0 - var(--help-bubble-arrow-offset));
-    top: calc(50% - var(--help-bubble-arrow-offset));
+  #arrow.top-edge {
+    top: 0;
   }
 
-  /* Turns the arrow direction to the left, when the bubble is placed to the
-   * right of the anchor element */
-  #arrow.right {
-    border-top-left-radius: 2px;
-    left: calc(0 - var(--help-bubble-arrow-offset));
-    top: calc(50% - var(--help-bubble-arrow-offset));
+  #arrow.top-edge #inner-arrow {
+    border-top-left-radius: var(--help-bubble-arrow-border-radius);
+  }
+
+  #arrow.right-edge {
+    right: 0;
+  }
+
+  #arrow.right-edge #inner-arrow {
+    border-top-right-radius: var(--help-bubble-arrow-border-radius);
+  }
+
+  #arrow.left-edge {
+    left: 0;
+  }
+
+  #arrow.left-edge #inner-arrow {
+    border-bottom-left-radius: var(--help-bubble-arrow-border-radius);
+  }
+
+  #arrow.top-position {
+    top: var(--help-bubble-arrow-offset);
+  }
+
+  #arrow.vertical-center-position {
+    top: 50%;
+  }
+
+  #arrow.bottom-position {
+    bottom: var(--help-bubble-arrow-offset);
+  }
+
+  #arrow.left-position {
+    left: var(--help-bubble-arrow-offset);
+  }
+
+  #arrow.horizontal-center-position {
+    left: 50%;
+  }
+
+  #arrow.right-position {
+    right: var(--help-bubble-arrow-offset);
   }
 
   #topContainer {
@@ -115,19 +133,19 @@
    * themes, which is why the values below do not change based on theme
    * preference. */
 
-   .help-bubble {
+  .help-bubble {
     --help-bubble-background: var(--google-blue-700);
     --help-bubble-element-spacing: 8px;
     --help-bubble-text-color: var(--google-grey-200);
     background-color: var(--help-bubble-background);
     border-radius: 8px;
-    box-shadow: 0 6px 10px 4px rgba(60, 64, 67, 0.15), 0 2px 3px rgba(60, 64, 67, 0.3);
     color: var(--help-bubble-text-color);
     display: flex;
     flex-direction: column;
     justify-content: space-between;
     padding: 16px 20px;
-    width: 340px;
+    position: relative;
+    width: 300px;
   }
 
   #main {
@@ -210,12 +228,10 @@
       </template>
     </div>
     <div id="infoIcon" hidden$="[[!shouldShowInfoIcon_(progress, infoIcon)]]"></div>
-    <div class="title"
-         hidden$="[[!shouldShowTitleInTopContainer_(progress, titleText)]]">
+    <div class="title" hidden$="[[!shouldShowTitleInTopContainer_(progress, titleText)]]">
       [[titleText]]
     </div>
-    <div class="body"
-         hidden$="[[!shouldShowBodyInTopContainer_(progress, titleText)]]">
+    <div class="body" hidden$="[[!shouldShowBodyInTopContainer_(progress, titleText)]]">
       [[bodyText]]
     </div>
     <cr-icon-button id="close" iron-icon="cr:close"
@@ -229,11 +245,11 @@
   </div>
   <div id="buttons" hidden$="[[!buttons.length]]">
     <template is="dom-repeat" items="[[buttons]]" sort="buttonSortFunc_">
-      <cr-button id$="[[getButtonId_(itemsIndex)]]"
-          tabindex$="[[getButtonTabIndex_(itemsIndex, item.isDefault)]]"
-          class$="[[getButtonClass_(item.isDefault)]]"
-          on-click="onButtonClick_">[[item.text]]</cr-button>
+      <cr-button id$="[[getButtonId_(itemsIndex)]]" tabindex$="[[getButtonTabIndex_(itemsIndex, item.isDefault)]]"
+        class$="[[getButtonClass_(item.isDefault)]]" on-click="onButtonClick_">[[item.text]]</cr-button>
     </template>
   </div>
-  <div id="arrow" class$="[[getArrowClass_(position)]]"></div>
+  <div id="arrow" class$="[[getArrowClass_(position)]]">
+    <div id="inner-arrow"></div>
+  </div>
 </div>
diff --git a/ui/webui/resources/cr_components/help_bubble/help_bubble.mojom b/ui/webui/resources/cr_components/help_bubble/help_bubble.mojom
index 28deb1be..d6c96a5 100644
--- a/ui/webui/resources/cr_components/help_bubble/help_bubble.mojom
+++ b/ui/webui/resources/cr_components/help_bubble/help_bubble.mojom
@@ -15,11 +15,22 @@
 // more detailed usage information, see README.md.
 
 // Where the help bubble floats relative to its anchor.
-enum HelpBubblePosition {
-  ABOVE,
-  BELOW,
-  LEFT,
-  RIGHT
+enum HelpBubbleArrowPosition {
+  TOP_LEFT,
+  TOP_CENTER,
+  TOP_RIGHT,
+
+  BOTTOM_LEFT,
+  BOTTOM_CENTER,
+  BOTTOM_RIGHT,
+
+  LEFT_TOP,
+  LEFT_CENTER,
+  LEFT_BOTTOM,
+
+  RIGHT_TOP,
+  RIGHT_CENTER,
+  RIGHT_BOTTOM,
 };
 
 // Simplified version of user_education::HelpBubbleButtonParams.
@@ -41,7 +52,7 @@
   // element ID.
   string native_identifier;
 
-  HelpBubblePosition position = HelpBubblePosition.BELOW;
+  HelpBubbleArrowPosition position = HelpBubbleArrowPosition.TOP_CENTER;
   string? title_text;
   string body_text;
   string close_button_alt_text;
diff --git a/ui/webui/resources/cr_components/help_bubble/help_bubble.ts b/ui/webui/resources/cr_components/help_bubble/help_bubble.ts
index 07b5920..9a4b4b2 100644
--- a/ui/webui/resources/cr_components/help_bubble/help_bubble.ts
+++ b/ui/webui/resources/cr_components/help_bubble/help_bubble.ts
@@ -20,7 +20,7 @@
 import {DomRepeatEvent, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './help_bubble.html.js';
-import {HelpBubbleButtonParams, HelpBubblePosition, Progress} from './help_bubble.mojom-webui.js';
+import {HelpBubbleButtonParams, HelpBubbleArrowPosition, Progress} from './help_bubble.mojom-webui.js';
 
 const ANCHOR_HIGHLIGHT_CLASS = 'help-anchor-highlight';
 
@@ -65,8 +65,8 @@
       closeText: String,
 
       position: {
-        type: HelpBubblePosition,
-        value: HelpBubblePosition.BELOW,
+        type: HelpBubbleArrowPosition,
+        value: HelpBubbleArrowPosition.TOP_CENTER,
         reflectToAttribute: true,
       },
     };
@@ -76,7 +76,7 @@
   bodyText: string;
   titleText: string;
   closeText: string;
-  position: HelpBubblePosition;
+  position: HelpBubbleArrowPosition;
   buttons: HelpBubbleButtonParams[] = [];
   progress: Progress|null = null;
   infoIcon: string|null = null;
@@ -241,19 +241,67 @@
     return 0;
   }
 
-  private getArrowClass_(position: HelpBubblePosition): string {
+  /**
+   * Determine classes that describe the arrow position relative to the
+   * HelpBubble
+   */
+  private getArrowClass_(position: HelpBubbleArrowPosition): string {
+    let classList = '';
+    // `*-edge` classes move arrow to a HelpBubble edge
     switch (position) {
-      case HelpBubblePosition.ABOVE:
-        return 'above';
-      case HelpBubblePosition.BELOW:
-        return 'below';
-      case HelpBubblePosition.LEFT:
-        return 'left';
-      case HelpBubblePosition.RIGHT:
-        return 'right';
+      case HelpBubbleArrowPosition.TOP_LEFT:
+      case HelpBubbleArrowPosition.TOP_CENTER:
+      case HelpBubbleArrowPosition.TOP_RIGHT:
+        classList = 'top-edge ';
+        break;
+      case HelpBubbleArrowPosition.BOTTOM_LEFT:
+      case HelpBubbleArrowPosition.BOTTOM_CENTER:
+      case HelpBubbleArrowPosition.BOTTOM_RIGHT:
+        classList = 'bottom-edge ';
+        break;
+      case HelpBubbleArrowPosition.LEFT_TOP:
+      case HelpBubbleArrowPosition.LEFT_CENTER:
+      case HelpBubbleArrowPosition.LEFT_BOTTOM:
+        classList = 'left-edge ';
+        break;
+      case HelpBubbleArrowPosition.RIGHT_TOP:
+      case HelpBubbleArrowPosition.RIGHT_CENTER:
+      case HelpBubbleArrowPosition.RIGHT_BOTTOM:
+        classList = 'right-edge ';
+        break;
       default:
         assertNotReached('Unknown help bubble position: ' + position);
     }
+    // `*-position` classes move arrow along the HelpBubble edge
+    switch (position) {
+      case HelpBubbleArrowPosition.TOP_LEFT:
+      case HelpBubbleArrowPosition.BOTTOM_LEFT:
+        classList += 'left-position';
+        break;
+      case HelpBubbleArrowPosition.TOP_CENTER:
+      case HelpBubbleArrowPosition.BOTTOM_CENTER:
+        classList += 'horizontal-center-position';
+        break;
+      case HelpBubbleArrowPosition.TOP_RIGHT:
+      case HelpBubbleArrowPosition.BOTTOM_RIGHT:
+        classList += 'right-position';
+        break;
+      case HelpBubbleArrowPosition.LEFT_TOP:
+      case HelpBubbleArrowPosition.RIGHT_TOP:
+        classList += 'top-position';
+        break;
+      case HelpBubbleArrowPosition.LEFT_CENTER:
+      case HelpBubbleArrowPosition.RIGHT_CENTER:
+        classList += 'vertical-center-position';
+        break;
+      case HelpBubbleArrowPosition.LEFT_BOTTOM:
+      case HelpBubbleArrowPosition.RIGHT_BOTTOM:
+        classList += 'bottom-position';
+        break;
+      default:
+        assertNotReached('Unknown help bubble position: ' + position);
+    }
+    return classList;
   }
 
   /**
@@ -264,52 +312,90 @@
     assert(
         this.anchorElement_, 'Update position: expected valid anchor element.');
 
+    // How far HelpBubble is from anchorElement
+    const ANCHOR_OFFSET = 16;
+    const ARROW_WIDTH = 16;
+    // The nearest an arrow can be to the adjacent HelpBubble edge
+    const ARROW_OFFSET_FROM_EDGE = 22 + (ARROW_WIDTH / 2);
+
     // Inclusive of 8px visible arrow and 8px margin.
-    const parentRect = this.offsetParent!.getBoundingClientRect();
     const anchorRect = this.anchorElement_.getBoundingClientRect();
-    const anchorLeft = anchorRect.left - parentRect.left;
-    const anchorHorizontalCenter = anchorLeft + anchorRect.width / 2;
-    const anchorTop = anchorRect.top - parentRect.top;
-    const ARROW_OFFSET = 16;
-    const LEFT_MARGIN = 8;
-    const HELP_BUBBLE_WIDTH = 362;
+    const helpBubbleRect = this.getBoundingClientRect();
 
-    let helpLeft: string = '';
-    let helpTop: string = '';
-
+    let transform = '';
+    // Move HelpBubble to correct side of the anchorElement
     switch (this.position) {
-      case HelpBubblePosition.ABOVE:
-        // Anchor the help bubble to the top center of the anchor element.
-        helpTop = `${anchorTop - ARROW_OFFSET}px`;
-        helpLeft = `${
-            Math.max(
-                LEFT_MARGIN,
-                anchorHorizontalCenter - HELP_BUBBLE_WIDTH / 2)}px`;
+      case HelpBubbleArrowPosition.TOP_LEFT:
+      case HelpBubbleArrowPosition.TOP_CENTER:
+      case HelpBubbleArrowPosition.TOP_RIGHT:
+        transform += `translateY(${
+          anchorRect.height
+        }px) translateY(${ANCHOR_OFFSET}px) `;
         break;
-      case HelpBubblePosition.BELOW:
-        // Anchor the help bubble to the bottom center of the anchor element.
-        helpTop = `${anchorTop + anchorRect.height + ARROW_OFFSET}px`;
-        helpLeft = `${
-            Math.max(
-                LEFT_MARGIN,
-                anchorHorizontalCenter - HELP_BUBBLE_WIDTH / 2)}px`;
+      case HelpBubbleArrowPosition.BOTTOM_LEFT:
+      case HelpBubbleArrowPosition.BOTTOM_CENTER:
+      case HelpBubbleArrowPosition.BOTTOM_RIGHT:
+        transform += `translateY(-100%) translateY(-${ANCHOR_OFFSET}px) `;
         break;
-      case HelpBubblePosition.LEFT:
-        // Anchor the help bubble to the center left of the anchor element.
-        helpTop = `${anchorTop + anchorRect.height / 2}px`;
-        helpLeft = `${anchorLeft - ARROW_OFFSET}px`;
+      case HelpBubbleArrowPosition.LEFT_TOP:
+      case HelpBubbleArrowPosition.LEFT_CENTER:
+      case HelpBubbleArrowPosition.LEFT_BOTTOM:
+        transform += `translateX(${
+          anchorRect.width
+        }px) translateX(${ANCHOR_OFFSET}px) `;
         break;
-      case HelpBubblePosition.RIGHT:
-        // Anchor the help bubble to the center right of the anchor element.
-        helpTop = `${anchorTop + anchorRect.height / 2}px`;
-        helpLeft = `${anchorLeft + anchorRect.width + ARROW_OFFSET}px`;
+      case HelpBubbleArrowPosition.RIGHT_TOP:
+      case HelpBubbleArrowPosition.RIGHT_CENTER:
+      case HelpBubbleArrowPosition.RIGHT_BOTTOM:
+        transform += `translateX(-100%) translateX(-${ANCHOR_OFFSET}px) `;
         break;
       default:
         assertNotReached();
     }
-
-    this.style.top = helpTop;
-    this.style.left = helpLeft;
+    // Move HelpBubble along the anchorElement edge according to arrow position
+    switch (this.position) {
+      case HelpBubbleArrowPosition.TOP_LEFT:
+      case HelpBubbleArrowPosition.BOTTOM_LEFT:
+        transform += `translateX(${
+          (anchorRect.width / 2) - ARROW_OFFSET_FROM_EDGE
+        }px)`;
+        break;
+      case HelpBubbleArrowPosition.TOP_CENTER:
+      case HelpBubbleArrowPosition.BOTTOM_CENTER:
+        transform += `translateX(${
+          (anchorRect.width / 2) - (helpBubbleRect.width / 2)
+        }px)`;
+        break;
+      case HelpBubbleArrowPosition.TOP_RIGHT:
+      case HelpBubbleArrowPosition.BOTTOM_RIGHT:
+        transform += `translateX(${
+          (anchorRect.width / 2)
+          - (helpBubbleRect.width - ARROW_OFFSET_FROM_EDGE)
+        }px)`;
+        break;
+      case HelpBubbleArrowPosition.LEFT_TOP:
+      case HelpBubbleArrowPosition.RIGHT_TOP:
+        transform += `translateY(${
+          (anchorRect.height / 2) - ARROW_OFFSET_FROM_EDGE
+        }px)`;
+        break;
+      case HelpBubbleArrowPosition.LEFT_CENTER:
+      case HelpBubbleArrowPosition.RIGHT_CENTER:
+        transform += `translateY(${
+          (anchorRect.height / 2) - (helpBubbleRect.height / 2)
+        }px)`;
+        break;
+      case HelpBubbleArrowPosition.LEFT_BOTTOM:
+      case HelpBubbleArrowPosition.RIGHT_BOTTOM:
+        transform += `translateY(${
+          (anchorRect.height / 2)
+          - (helpBubbleRect.height - ARROW_OFFSET_FROM_EDGE)
+        }px)`;
+        break;
+      default:
+        assertNotReached();
+    }
+    this.style.transform = transform;
   }
 
   /**
diff --git a/ui/webui/resources/mojo/BUILD.gn b/ui/webui/resources/mojo/BUILD.gn
index dcd7487..7ff9d04 100644
--- a/ui/webui/resources/mojo/BUILD.gn
+++ b/ui/webui/resources/mojo/BUILD.gn
@@ -16,6 +16,7 @@
   in_files = [
     "mojo/public/mojom/base/big_buffer.mojom-webui.js",
     "mojo/public/mojom/base/file_path.mojom-webui.js",
+    "mojo/public/mojom/base/int128.mojom-webui.js",
     "mojo/public/mojom/base/process_id.mojom-webui.js",
     "mojo/public/mojom/base/safe_base_name.mojom-webui.js",
     "mojo/public/mojom/base/string16.mojom-webui.js",