diff --git a/DEPS b/DEPS
index 5a0f408b..738651c 100644
--- a/DEPS
+++ b/DEPS
@@ -304,7 +304,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': '9dfd1bdccb678e544b23440524ccf7ae7d5266a1',
+  'skia_revision': '89742d768c973ea0e07676713825a105039b9a5d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -312,15 +312,15 @@
   # 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': '645a37537f7b5e087fed63dacb2f0825d31d8304',
+  'angle_revision': '7d8cbb600e5ef9bc63d039bd422af8e5c3e2a281',
   # 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': 'bcbc10b1bed498c81d9f7fc801e14635e821cc7c',
+  'swiftshader_revision': 'd0aa9ad9447025a42f17df1b93bd71183e9b2d1f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'a72813f8d47fbee57aa04531dab7dd2322155212',
+  'pdfium_revision': '355229b748b7843e23dcfb24d80f7bc16b159f33',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -331,7 +331,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:11.20221221.3.1',
+  'fuchsia_version': 'version:11.20221222.0.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.
@@ -375,7 +375,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': '2863c8e12d8bfb01108f785fddd92c94a2357829',
+  'catapult_revision': 'b10b1305cef856fefe0827de2a1ba20d52c0580a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -383,7 +383,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': 'e7c9643691efe717cd2fd680856e58e02b5f8a2c',
+  'devtools_frontend_revision': '37d35582c97bf3017430fb6e93e167911e64316e',
   # 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.
@@ -419,7 +419,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'a1f5334e72bfd13bc2ad04a80deac2bae540aea0',
+  'dawn_revision': '906fc9df206d668191e9660a16688e27eb3d97ce',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -776,12 +776,12 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '90a1af0a3e8734263fa979dc1d9ce29371378ad1',
+    '3e0d5ccc185f18759008e2f588f33e31cdf52104',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'a21816a490b5ed39b53b42b9ac3b60711cc7955d',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'c924b5f5698e14acc886597b5a53d4b44f4daf9d',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -875,7 +875,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': '83J3_x65NJaReq3LxP1CYYLLz4srstFe_B45yvUJOMkC',
+          'version': 'IFpn8u0Ljf32cafxbLxFH0fquMy0UIuilH4Lmue_jW8C',
         },
       ],
       'dep_type': 'cipd',
@@ -886,7 +886,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'PoA0W8Yg7Qu5D7PFntq-bL3o0aEleoQpOpdbtbIqEroC',
+          'version': 'pAOncX-hjOcTBopPDGrahps7xWbo3Hazwv_AunHS83YC',
         },
       ],
       'dep_type': 'cipd',
@@ -897,7 +897,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'dhmAQzV1Vclxd-eirwEzMitzu3VMtF0eLvj9DRfdISkC',
+          'version': '_QEkP2xIpyusNbBkiOOePdTsM49L-MIzYeM-d8QDvwMC',
         },
       ],
       'dep_type': 'cipd',
@@ -965,7 +965,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '1CRXG22riAUf6hHMXngSF6stzqJ7eNsJvCWfVXpC0EQC',
+          'version': 'XNQCcCxsa8Vznu546BS4QiWwlsCp1_1rV1J_5rSghKwC',
       },
     ],
     'condition': 'checkout_android',
@@ -1182,7 +1182,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9fd66e7fbbb6eb79324e57df2c8b950a81d2e677',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '1702ac7f02e50fac5e5cf0a5bc388420023ab03e',
       'condition': 'checkout_chromeos',
   },
 
@@ -1216,7 +1216,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '301cc781b9ace83fe8c98fe178e224cadfde93d1',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'aefadfcacebbc1d64d8fd81f07a7dbf4bf12cec2',
     'condition': 'checkout_src_internal',
   },
 
@@ -1428,7 +1428,7 @@
     Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git' + '@' +  Var('libfuzzer_revision'),
 
   'src/third_party/libaddressinput/src':
-    Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'df35d6c42da4fa2759e4cfb592afe33817993b89',
+    Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'e8712e415627f22d0b00ebee8db99547077f39bd',
 
   'src/third_party/libaom/source/libaom':
     Var('aomedia_git') + '/aom.git' + '@' +  'a84503456d4276348da3e80de7569adb1b389a60',
@@ -1645,7 +1645,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b177a9b77d364faf675687204c9e48b8fd4d813c',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2e920f3a489071ad7c98f6c84eba83f259f107a2',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1790,7 +1790,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3a028e4c1f4a85437451406b23ed4d416aa0b92d',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@048d14e9ffed1f6d841a03a17d7c0406a87736df',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1830,7 +1830,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '5f2708ddda33a293c6a0339029c44e144d7f8f0a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '6f5c314f07724469071fd5f347ac5479d138a033',
+    Var('webrtc_git') + '/src.git' + '@' + 'd29b12f90ca101e3bece49a6a67ea6da4a168111',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1900,7 +1900,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b2f943a50d07e32f0c5da0bd600935c6a8e403ee',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0eb5bc35c0153313afefab2a3b3e1b4196c6640a',
     'condition': 'checkout_src_internal',
   },
 
@@ -1930,7 +1930,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'Yp4Y5WdgXCymktb8BtHnW0KiCkx3yNSnzAZtSIg1VAUC',
+        'version': '0CNuEKi70Ay8wLTnPEfahWZpa2N9aLoQbUL2gIpeYQ8C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1941,7 +1941,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'vceV-IT5iWElElMojwOfHxwBy69Y2M7ZqmpbCF1YWRwC',
+        'version': 'nizpzYofOoqUn01F6h98ktdDl-LpP7GciWj1F2a5CZ0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 80ba73c..e5d93a18 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1080,12 +1080,17 @@
     "style/style_viewer/system_ui_components_grid_view_factories.h",
     "style/style_viewer/system_ui_components_style_viewer_view.cc",
     "style/style_viewer/system_ui_components_style_viewer_view.h",
+    "style/style_viewer/tab_slider_instances_grid_view_factory.cc",
     "style/system_shadow.cc",
     "style/system_shadow.h",
     "style/system_shadow_on_nine_patch_layer.cc",
     "style/system_shadow_on_nine_patch_layer.h",
     "style/system_shadow_on_texture_layer.cc",
     "style/system_shadow_on_texture_layer.h",
+    "style/system_textfield.cc",
+    "style/system_textfield.h",
+    "style/system_textfield_controller.cc",
+    "style/system_textfield_controller.h",
     "style/system_toast_style.cc",
     "style/system_toast_style.h",
     "style/tab_slider.cc",
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index bcbc99c..1dcd245 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -1559,9 +1559,9 @@
 
   // Tests that |side_volume_button_location_| is read correctly if the location
   // file exists.
-  base::DictionaryValue location;
-  location.SetString(kVolumeButtonRegion, kVolumeButtonRegionScreen);
-  location.SetString(kVolumeButtonSide, kVolumeButtonSideLeft);
+  base::Value::Dict location;
+  location.Set(kVolumeButtonRegion, kVolumeButtonRegionScreen);
+  location.Set(kVolumeButtonSide, kVolumeButtonSideLeft);
   std::string json_location;
   base::JSONWriter::Write(location, &json_location);
   base::ScopedTempDir file_tmp_dir;
diff --git a/ash/accessibility/magnifier/fullscreen_magnifier_controller.cc b/ash/accessibility/magnifier/fullscreen_magnifier_controller.cc
index 64148b3..4245ce21 100644
--- a/ash/accessibility/magnifier/fullscreen_magnifier_controller.cc
+++ b/ash/accessibility/magnifier/fullscreen_magnifier_controller.cc
@@ -67,11 +67,6 @@
 // MoveMagnifierWindowFollowPoint() when |reduce_bottom_margin| is true.
 constexpr int kKeyboardBottomPanningMargin = 10;
 
-void MoveCursorTo(aura::WindowTreeHost* host, const gfx::Point& root_location) {
-  host->MoveCursorToLocationInPixels(gfx::ToCeiledPoint(
-      host->GetRootTransform().MapPoint(gfx::PointF(root_location))));
-}
-
 }  // namespace
 
 class FullscreenMagnifierController::GestureProviderClient
@@ -273,7 +268,7 @@
 
 void FullscreenMagnifierController::OnImplicitAnimationsCompleted() {
   if (move_cursor_after_animation_) {
-    MoveCursorTo(root_window_->GetHost(), position_after_animation_);
+    MoveCursorTo(position_after_animation_);
     move_cursor_after_animation_ = false;
 
     aura::client::CursorClient* cursor_client =
@@ -524,7 +519,9 @@
     y = max_y;
 
   // Does nothing if both the origin and the scale are not changed.
-  if (origin_.x() == x && origin_.y() == y && scale == scale_) {
+  // Cast origin points back to int, as viewport can only be integer values.
+  if (static_cast<int>(origin_.x()) == static_cast<int>(x) &&
+      static_cast<int>(origin_.y()) == static_cast<int>(y) && scale == scale_) {
     return false;
   }
 
@@ -892,7 +889,7 @@
       // good already).
       if ((x_diff != 0 || y_diff != 0) &&
           mouse_following_mode_ != MagnifierMouseFollowingMode::kContinuous) {
-        MoveCursorTo(root_window_->GetHost(), point);
+        MoveCursorTo(point);
       }
     }
   }
@@ -967,4 +964,15 @@
   }
 }
 
+void FullscreenMagnifierController::MoveCursorTo(
+    const gfx::Point& root_location) {
+  aura::WindowTreeHost* host = root_window_->GetHost();
+  host->MoveCursorToLocationInPixels(gfx::ToCeiledPoint(
+      host->GetRootTransform().MapPoint(gfx::PointF(root_location))));
+
+  if (cursor_moved_callback_for_testing_) {
+    cursor_moved_callback_for_testing_.Run(root_location);
+  }
+}
+
 }  // namespace ash
diff --git a/ash/accessibility/magnifier/fullscreen_magnifier_controller.h b/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
index 65ab4ae..f0fe62c9 100644
--- a/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
+++ b/ash/accessibility/magnifier/fullscreen_magnifier_controller.h
@@ -10,6 +10,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
+#include "base/functional/callback.h"
 #include "ui/aura/window_observer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/events/event_handler.h"
@@ -142,6 +143,11 @@
   // Returns the current number of touch points.
   int32_t GetTouchPointsForTesting() const { return touch_points_; }
 
+  void set_cursor_moved_callback_for_testing(
+      base::RepeatingCallback<void(const gfx::Point&)> callback) {
+    cursor_moved_callback_for_testing_ = std::move(callback);
+  }
+
  private:
   class GestureProviderClient;
 
@@ -244,6 +250,9 @@
   // to center the |rect| in that dimension.
   void MoveMagnifierWindowFollowRect(const gfx::Rect& rect);
 
+  // Moves the cursor to the given location in the root window.
+  void MoveCursorTo(const gfx::Point& root_location);
+
   // Target root window. This must not be NULL.
   aura::Window* root_window_;
 
@@ -306,6 +315,10 @@
   // Flag to draw a preview box around magnifier viewport area instead of
   // magnifying the screen for debugging.
   bool magnifier_debug_draw_rect_ = false;
+
+  // Called every time MoveCursorTo is called, when set in tests.
+  base::RepeatingCallback<void(const gfx::Point&)>
+      cursor_moved_callback_for_testing_;
 };
 
 }  // namespace ash
diff --git a/ash/accessibility/magnifier/fullscreen_magnifier_controller_unittest.cc b/ash/accessibility/magnifier/fullscreen_magnifier_controller_unittest.cc
index 08623a2..3d9a2d9 100644
--- a/ash/accessibility/magnifier/fullscreen_magnifier_controller_unittest.cc
+++ b/ash/accessibility/magnifier/fullscreen_magnifier_controller_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
 #include "ui/aura/env.h"
 #include "ui/aura/test/aura_test_utils.h"
 #include "ui/aura/window_tree_host.h"
@@ -1080,4 +1081,36 @@
   }
 }
 
+TEST_F(FullscreenMagnifierControllerTest, DoesNotRedrawIfViewportIsNotChanged) {
+  auto* magnifier = GetFullscreenMagnifierController();
+  // Picking a floating point scale makes the float/int conversion
+  // more likely to fail (which is what this test guards against).
+  magnifier->SetEnabled(true);
+  magnifier->set_mouse_following_mode(MagnifierMouseFollowingMode::kEdge);
+  magnifier->SetScale(10.345, /*animate=*/false);
+  int num_cursor_moves = 0;
+  magnifier->set_cursor_moved_callback_for_testing(base::BindLambdaForTesting(
+      [&num_cursor_moves](const gfx::Point& point) { num_cursor_moves++; }));
+
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+
+  // Move until viewport is in bottom right corner, asymetrically.
+  gfx::Point bottom_right(kRootWidth - 70, kRootHeight - 53);
+  while (GetViewport().ToString() != "722,542 78x58") {
+    event_generator->MoveMouseToInHost(bottom_right);
+  }
+
+  // The cursor has been moved.
+  EXPECT_GT(num_cursor_moves, 1);
+
+  num_cursor_moves = 0;
+
+  // Moving around further doesn't try to move the cursor position and
+  // doesn't change the viewport.
+  // If this were to happen we could end up in an infinite cursor-moving loop.
+  event_generator->MoveMouseToInHost(bottom_right);
+  EXPECT_EQ(GetViewport().ToString(), "722,542 78x58");
+  EXPECT_EQ(0, num_cursor_moves);
+}
+
 }  // namespace ash
diff --git a/ash/app_list/app_list_color_provider_impl.cc b/ash/app_list/app_list_color_provider_impl.cc
index 6b40cd6..ef832b0 100644
--- a/ash/app_list/app_list_color_provider_impl.cc
+++ b/ash/app_list/app_list_color_provider_impl.cc
@@ -40,14 +40,6 @@
       kColorAshButtonIconColor);
 }
 
-SkColor AppListColorProviderImpl::GetFolderBackgroundColor(
-    const views::Widget* app_list_widget) const {
-  DCHECK(app_list_widget);
-
-  return app_list_widget->GetColorProvider()->GetColor(
-      kColorAshShieldAndBase80);
-}
-
 SkColor AppListColorProviderImpl::GetFolderNotificationBadgeColor(
     const views::Widget* app_list_widget) const {
   DCHECK(app_list_widget);
diff --git a/ash/app_list/app_list_color_provider_impl.h b/ash/app_list/app_list_color_provider_impl.h
index a36b6d0..321c78dc 100644
--- a/ash/app_list/app_list_color_provider_impl.h
+++ b/ash/app_list/app_list_color_provider_impl.h
@@ -16,8 +16,6 @@
   // AppListColorProvider:
   SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const override;
-  SkColor GetFolderBackgroundColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetFolderNotificationBadgeColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetGridBackgroundCardActiveColor(
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index d470d75..9268a92 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -78,6 +78,8 @@
 
 constexpr int kScrollViewGradientSize = 16;
 
+constexpr int kFolderBackgroundRadius = 12;
+
 // Insets for the vertical scroll bar. The top is pushed down slightly to align
 // with the icons, which keeps the scroll bar out of the rounded corner area.
 constexpr auto kVerticalScrollInsets =
@@ -88,13 +90,6 @@
 // for flying in or out the folder.
 constexpr base::TimeDelta kFolderTransitionDuration = base::Milliseconds(250);
 
-// A utility function for `background_view` to update its background color.
-void SetBackgroundViewColor(views::View* background_view, SkColor color) {
-  background_view->SetBackground(color == SK_ColorTRANSPARENT
-                                     ? nullptr
-                                     : views::CreateSolidBackground(color));
-}
-
 // Returns true if ChromeVox (spoken feedback) is enabled.
 bool IsSpokenFeedbackEnabled() {
   return Shell::HasInstance() &&  // May be null in tests.
@@ -110,22 +105,18 @@
  public:
   BackgroundAnimation(bool show,
                       AppListFolderView* folder_view,
-                      views::View* background_view)
+                      views::View* animating_view)
       : show_(show),
         folder_view_(folder_view),
-        background_view_(background_view),
+        animating_view_(animating_view),
         shadow_(folder_view->shadow()) {
-    background_view_observer_.Observe(background_view_);
-    shadow_->GetLayer()->SetVisible(true);
+    background_view_observer_.Observe(animating_view_);
   }
 
   BackgroundAnimation(const BackgroundAnimation&) = delete;
   BackgroundAnimation& operator=(const BackgroundAnimation&) = delete;
 
-  ~BackgroundAnimation() override {
-    if (!show_)
-      shadow_->GetLayer()->SetVisible(false);
-  }
+  ~BackgroundAnimation() override = default;
 
  private:
   // AppListFolderView::Animation:
@@ -136,37 +127,37 @@
     // Calculate the source and target states.
     const int icon_radius =
         folder_view_->GetAppListConfig()->folder_icon_radius();
-    const int folder_radius =
-        folder_view_->GetAppListConfig()->folder_background_radius();
-    const int from_radius = show_ ? icon_radius : folder_radius;
-    const int to_radius = show_ ? folder_radius : icon_radius;
+    const int from_radius = show_ ? icon_radius : kFolderBackgroundRadius;
+    const int to_radius = show_ ? kFolderBackgroundRadius : icon_radius;
     gfx::Rect from_rect = show_ ? folder_view_->folder_item_icon_bounds()
-                                : background_view_->bounds();
-    from_rect -= background_view_->bounds().OffsetFromOrigin();
-    gfx::Rect to_rect = show_ ? background_view_->bounds()
+                                : animating_view_->bounds();
+    from_rect -= animating_view_->bounds().OffsetFromOrigin();
+    gfx::Rect to_rect = show_ ? animating_view_->bounds()
                               : folder_view_->folder_item_icon_bounds();
-    to_rect -= background_view_->bounds().OffsetFromOrigin();
+    to_rect -= animating_view_->bounds().OffsetFromOrigin();
     const views::Widget* app_list_widget = folder_view_->GetWidget();
     const SkColor background_color =
-        AppListColorProvider::Get()->GetFolderBackgroundColor(app_list_widget);
+        app_list_widget->GetColorProvider()->GetColor(kColorAshShieldAndBase80);
     const SkColor bubble_color = app_list_widget->GetColorProvider()->GetColor(
         kColorAshControlBackgroundColorInactive);
     const SkColor from_color = show_ ? bubble_color : background_color;
     const SkColor to_color = show_ ? background_color : bubble_color;
 
-    SetBackgroundViewColor(background_view_, from_color);
-    background_view_->layer()->SetClipRect(from_rect);
-    background_view_->layer()->SetRoundedCornerRadius(
+    animating_view_->layer()->SetColor(from_color);
+    animating_view_->layer()->SetClipRect(from_rect);
+    animating_view_->layer()->SetRoundedCornerRadius(
         gfx::RoundedCornersF(from_radius));
 
+    AlignShadowWithAnimatingBackground();
+
     ui::ScopedLayerAnimationSettings settings(
-        background_view_->layer()->GetAnimator());
+        animating_view_->layer()->GetAnimator());
     settings.SetTransitionDuration(kFolderTransitionDuration);
     settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
     settings.AddObserver(this);
-    SetBackgroundViewColor(background_view_, to_color);
-    background_view_->layer()->SetClipRect(to_rect);
-    background_view_->layer()->SetRoundedCornerRadius(
+    animating_view_->layer()->SetColor(to_color);
+    animating_view_->layer()->SetClipRect(to_rect);
+    animating_view_->layer()->SetRoundedCornerRadius(
         gfx::RoundedCornersF(to_radius));
     is_animating_ = true;
   }
@@ -174,19 +165,9 @@
   bool IsAnimationRunning() override { return is_animating_; }
 
   // ui::ImplicitAnimationObserver:
-  void OnImplicitAnimationsScheduled() override {
-    // Remove the highlight border at the start of the closing animation.
-    if (!show_)
-      folder_view_->UpdateHighlightBorder(false);
-  }
-
-  // ui::ImplicitAnimationObserver:
   void OnImplicitAnimationsCompleted() override {
-    // Add the highlight border when the showing animation is completed.
-    if (show_)
-      folder_view_->UpdateHighlightBorder(true);
-
     is_animating_ = false;
+
     folder_view_->RecordAnimationSmoothness();
 
     if (completion_callback_)
@@ -200,11 +181,15 @@
     // attributes. We need to use the intermediate clip rect shape from
     // background animation to update shadow's contents bounds and corner
     // radius.
-    DCHECK_EQ(observed_view, background_view_);
+    DCHECK_EQ(observed_view, animating_view_);
 
+    AlignShadowWithAnimatingBackground();
+  }
+
+  void AlignShadowWithAnimatingBackground() {
     // If layer clip rect is not empty, we use the clip rect to update the
     // shadow's contents bounds. Otherwise, we use the layer bounds.
-    const auto* background_layer = background_view_->layer();
+    const auto* background_layer = animating_view_->layer();
     const gfx::Rect& background_bounds = background_layer->bounds();
     const gfx::Rect& clip_rect = background_layer->clip_rect();
     const gfx::Rect& content_bounds =
@@ -219,9 +204,9 @@
   const bool show_;
   bool is_animating_ = false;
 
-  AppListFolderView* const folder_view_;  // Not owned.
-  views::View* const background_view_;    // Not owned.
-  SystemShadow* const shadow_;            // Not owned.
+  AppListFolderView* const folder_view_;
+  views::View* const animating_view_;
+  SystemShadow* const shadow_;
 
   // Observes the rect clip change of background view.
   base::ScopedObservation<views::View, views::ViewObserver>
@@ -362,7 +347,8 @@
       // Add the transitional views into child views, and set its bounds to the
       // same location of the item in the folder list view.
       top_icon_views_.push_back(
-          folder_view_->background_view()->AddChildView(std::move(icon_view)));
+          folder_view_->animating_background()->AddChildView(
+              std::move(icon_view)));
       icon_view_ptr->SetBoundsRect(first_page_item_views_bounds[i]);
       icon_view_ptr->TransformView(kFolderTransitionDuration);
     }
@@ -531,10 +517,6 @@
       layer->SetTransform(transform);
     layer->SetOpacity(show_ ? 0.0f : 1.0f);
 
-    // The folder should be set visible only after it is scaled down and
-    // transparent to prevent the flash of the view right before the animation.
-    folder_view_->SetVisible(true);
-
     ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
     animation.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
     animation.AddObserver(this);
@@ -643,6 +625,24 @@
       ColorProvider::kBackgroundBlurSigma);
   background_view_->layer()->SetBackdropFilterQuality(
       ColorProvider::kBackgroundBlurQuality);
+  background_view_->layer()->SetRoundedCornerRadius(
+      gfx::RoundedCornersF(kFolderBackgroundRadius));
+  background_view_->layer()->SetIsFastRoundedCorner(true);
+  background_view_->SetBorder(std::make_unique<views::HighlightBorder>(
+      kFolderBackgroundRadius, views::HighlightBorder::Type::kHighlightBorder1,
+      /*use_light_colors=*/!features::IsDarkLightModeEnabled()));
+  background_view_->SetBackground(
+      views::CreateThemedSolidBackground(kColorAshShieldAndBase80));
+  background_view_->SetVisible(false);
+
+  animating_background_ = AddChildView(std::make_unique<views::View>());
+  animating_background_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
+  animating_background_->layer()->SetBackgroundBlur(
+      ColorProvider::kBackgroundBlurSigma);
+  animating_background_->layer()->SetBackdropFilterQuality(
+      ColorProvider::kBackgroundBlurQuality);
+  animating_background_->layer()->SetFillsBoundsOpaquely(false);
+  animating_background_->SetVisible(false);
 
   contents_container_ = AddChildView(std::make_unique<views::View>());
   contents_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
@@ -653,7 +653,6 @@
   shadow_ = SystemShadow::CreateShadowOnNinePatchLayer(
       SystemShadow::Type::kElevation8);
   background_view_->AddLayerBeneathView(shadow_->GetLayer());
-  shadow_->GetLayer()->SetVisible(false);
 
   AppListModelProvider::Get()->AddObserver(this);
 }
@@ -779,7 +778,7 @@
 
   // Animate the background corner radius, opacity and bounds.
   folder_visibility_animations_.push_back(
-      std::make_unique<BackgroundAnimation>(show, this, background_view_));
+      std::make_unique<BackgroundAnimation>(show, this, animating_background_));
 
   // Animate the folder item's title's opacity.
   views::View* const folder_title = folder_item_view_->title();
@@ -811,6 +810,12 @@
                        weak_ptr_factory_.GetWeakPtr()));
   }
 
+  SetVisible(true);
+
+  background_view_->SetVisible(false);
+  animating_background_->SetVisible(true);
+  shadow_->GetLayer()->SetVisible(true);
+
   for (auto& animation : folder_visibility_animations_)
     animation->ScheduleAnimation(animation_completion_callback);
 }
@@ -896,7 +901,8 @@
 
   // Transition all the states immediately to the end of folder closing
   // animation.
-  SetBackgroundViewColor(background_view_, SK_ColorTRANSPARENT);
+  background_view_->SetVisible(false);
+
   if (restore_folder_item_view_state && folder_item_view_) {
     folder_item_view_->SetIconVisible(true);
     folder_item_view_->title()->DestroyLayer();
@@ -910,11 +916,17 @@
 }
 
 void AppListFolderView::OnShowAnimationDone() {
+  animating_background_->SetVisible(false);
+  background_view_->SetVisible(true);
+
   if (animation_done_test_callback_)
     std::move(animation_done_test_callback_).Run();
 }
 
 void AppListFolderView::OnHideAnimationDone(bool hide_for_reparent) {
+  animating_background_->SetVisible(false);
+  shadow_->GetLayer()->SetVisible(false);
+
   a11y_announcer_->AnnounceFolderClosed();
 
   // If the folder view is hiding for folder closure, reset the
@@ -937,18 +949,6 @@
     std::move(animation_done_test_callback_).Run();
 }
 
-void AppListFolderView::UpdateHighlightBorder(bool show) {
-  if (!show) {
-    background_view_->SetBorder(nullptr);
-    return;
-  }
-
-  background_view_->SetBorder(std::make_unique<views::HighlightBorder>(
-      GetAppListConfig()->folder_background_radius(),
-      views::HighlightBorder::Type::kHighlightBorder1,
-      /*use_light_colors=*/!features::IsDarkLightModeEnabled()));
-}
-
 void AppListFolderView::UpdatePreferredBounds() {
   if (!folder_item_view_)
     return;
diff --git a/ash/app_list/views/app_list_folder_view.h b/ash/app_list/views/app_list_folder_view.h
index 9d252901..eeb74b1 100644
--- a/ash/app_list/views/app_list_folder_view.h
+++ b/ash/app_list/views/app_list_folder_view.h
@@ -140,10 +140,6 @@
   // to be in the parent view's coordinate system.
   void SetBoundingBox(const gfx::Rect& bounding_box);
 
-  // Updates the highlight border of the folder view according to the folder
-  // animation.
-  void UpdateHighlightBorder(bool show);
-
   // Sets the callback that runs when the folder animation ends.
   void SetAnimationDoneTestCallback(base::OnceClosure animation_done_callback);
 
@@ -151,7 +147,7 @@
 
   FolderHeaderView* folder_header_view() { return folder_header_view_; }
 
-  views::View* background_view() { return background_view_; }
+  views::View* animating_background() { return animating_background_; }
 
   views::View* contents_container() { return contents_container_; }
 
@@ -236,7 +232,8 @@
   AppListA11yAnnouncer* const a11y_announcer_;
 
   // The view is used to draw a background with corner radius.
-  views::View* background_view_;  // Owned by views hierarchy.
+  views::View* background_view_;
+  views::View* animating_background_;
 
   // The view is used as a container for all following views.
   views::View* contents_container_;  // Owned by views hierarchy.
diff --git a/ash/assistant/assistant_web_view_delegate_impl.cc b/ash/assistant/assistant_web_view_delegate_impl.cc
index 0d43831..1c7779e 100644
--- a/ash/assistant/assistant_web_view_delegate_impl.cc
+++ b/ash/assistant/assistant_web_view_delegate_impl.cc
@@ -37,7 +37,6 @@
       case views::CAPTION_BUTTON_ICON_BACK:
         return back_button_visibility_;
 
-      case views::CAPTION_BUTTON_ICON_FLOAT:
       case views::CAPTION_BUTTON_ICON_MINIMIZE:
       case views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
       case views::CAPTION_BUTTON_ICON_LEFT_TOP_SNAPPED:
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 9402108..1d70c01a4 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -756,11 +756,6 @@
              "EnableLogControllerForDiagnosticsApp",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// If enabled, the networking cards will be shown in the diagnostics app.
-BASE_FEATURE(kEnableNetworkingInDiagnosticsApp,
-             "EnableNetworkingInDiagnosticsApp",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables OAuth support when printing via the IPP protocol.
 BASE_FEATURE(kEnableOAuthIpp,
              "EnableOAuthIpp",
@@ -919,12 +914,6 @@
              "FastPairSoftwareScanning",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables the "Subsequent Pairing" Fast Pair scenario in Bluetooth Settings
-// and Quick Settings.
-BASE_FEATURE(kFastPairSubsequentPairingUX,
-             "FastPairSubsequentPairingUX",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Enables the "Saved Devices" Fast Pair page in scenario in Bluetooth Settings.
 BASE_FEATURE(kFastPairSavedDevices,
              "FastPairSavedDevices",
@@ -1504,16 +1493,14 @@
              "OobeHidDetectionRevamp",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables OOBE Jelly features.
+BASE_FEATURE(kOobeJelly, "OobeJelly", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables the Oobe quick start flow.
 BASE_FEATURE(kOobeQuickStart,
              "OobeQuickStart",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables OOBE Material Next features.
-BASE_FEATURE(kOobeMaterialNext,
-             "OobeMaterialNext",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Removes "Shut down" button from OOBE, except first login screen and
 // successful enrollment step.
 BASE_FEATURE(kOobeRemoveShutdownButton,
@@ -2315,10 +2302,6 @@
   return base::FeatureList::IsEnabled(kArcInputOverlayAlphaV2);
 }
 
-bool IsArcNetworkDiagnosticsButtonEnabled() {
-  return IsNetworkingInDiagnosticsAppEnabled();
-}
-
 bool IsAssistantNativeIconsEnabled() {
   return base::FeatureList::IsEnabled(kAssistantNativeIcons);
 }
@@ -2554,10 +2537,6 @@
   return base::FeatureList::IsEnabled(kFastPairSoftwareScanning);
 }
 
-bool IsFastPairSubsequentPairingUXEnabled() {
-  return base::FeatureList::IsEnabled(kFastPairSubsequentPairingUX);
-}
-
 bool IsFastPairSavedDevicesEnabled() {
   return base::FeatureList::IsEnabled(kFastPairSavedDevices);
 }
@@ -2809,10 +2788,6 @@
   return base::FeatureList::IsEnabled(kNearbyKeepAliveFix);
 }
 
-bool IsNetworkingInDiagnosticsAppEnabled() {
-  return base::FeatureList::IsEnabled(kEnableNetworkingInDiagnosticsApp);
-}
-
 bool IsOAuthIppEnabled() {
   return base::FeatureList::IsEnabled(kEnableOAuthIpp);
 }
@@ -2857,8 +2832,8 @@
   return base::FeatureList::IsEnabled(kEnableKioskLoginScreen);
 }
 
-bool IsOobeMaterialNextEnabled() {
-  return IsJellyEnabled() && base::FeatureList::IsEnabled(kOobeMaterialNext);
+bool IsOobeJellyEnabled() {
+  return IsJellyEnabled() && base::FeatureList::IsEnabled(kOobeJelly);
 }
 
 bool IsOobeNetworkScreenSkipEnabled() {
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 7e0c696..c850257 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -226,8 +226,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kEnableLocalSearchService);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kEnableLogControllerForDiagnosticsApp);
-COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kEnableNetworkingInDiagnosticsApp);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kEnableOAuthIpp);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kEnableOobeChromeVoxHint);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -271,8 +269,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::FeatureParam<double> kFastPairLowPowerInactiveSeconds;
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFastPairSoftwareScanning);
-COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kFastPairSubsequentPairingUX);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFastPairSavedDevices);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kFastPairSavedDevicesStrictOptIn);
@@ -428,8 +424,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeChoobe);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeConsolidatedConsent);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeHidDetectionRevamp);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeJelly);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeQuickStart);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeMaterialNext);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeNewRecommendApps);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeRemoveShutdownButton);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -636,7 +632,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsArcFuseBoxFileSharingEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsArcInputOverlayAlphaV2Enabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsArcInputOverlayBetaEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsArcNetworkDiagnosticsButtonEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAssistantNativeIconsEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAssistiveMultiWordEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAudioSettingsPageEnabled();
@@ -699,7 +694,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsFastPairPreventNotificationsForRecentlyLostDeviceEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSoftwareScanningEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSubsequentPairingUXEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSavedDevicesEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSavedDevicesStrictOptInEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFederatedServiceEnabled();
@@ -761,7 +755,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsMicMuteNotificationsEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsMinimumChromeVersionEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNearbyKeepAliveFixEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNetworkingInDiagnosticsAppEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEducationEnrollmentOobeFlowEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNewLockScreenReauthLayoutEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNotificationExpansionAnimationEnabled();
@@ -774,9 +767,9 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeChoobeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeChromeVoxHintEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeHidDetectionRevampEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeJellyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsKioskEnrollmentInOobeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsKioskLoginScreenEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeMaterialNextEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeNetworkScreenSkipEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeConsolidatedConsentEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeQuickStartEnabled();
diff --git a/ash/frame/caption_buttons/frame_caption_button_container_view_unittest.cc b/ash/frame/caption_buttons/frame_caption_button_container_view_unittest.cc
index fb2ed4a8..6193ec4 100644
--- a/ash/frame/caption_buttons/frame_caption_button_container_view_unittest.cc
+++ b/ash/frame/caption_buttons/frame_caption_button_container_view_unittest.cc
@@ -342,58 +342,24 @@
   EXPECT_TRUE(window_state->IsSnapped());
 }
 
-// Test float button requires kFloatWindow feature to be enabled during setup.
-class WindowFloatButtonTest : public FrameCaptionButtonContainerViewTest {
+// Test float button requires `kFloatWindow` feature to be enabled during setup.
+class FrameCaptionButtonContainerViewWithFloatTest
+    : public FrameCaptionButtonContainerViewTest {
  public:
-  WindowFloatButtonTest()
+  FrameCaptionButtonContainerViewWithFloatTest()
       : scoped_feature_list_(chromeos::wm::features::kFloatWindow) {}
-  WindowFloatButtonTest(const WindowFloatButtonTest&) = delete;
-  WindowFloatButtonTest& operator=(const WindowFloatButtonTest&) = delete;
-  ~WindowFloatButtonTest() override = default;
-
-  void ClickFloatButton(FrameCaptionButtonContainerView::TestApi* test_api) {
-    ui::test::EventGenerator* generator = GetEventGenerator();
-    auto* float_button = test_api->float_button();
-    generator->MoveMouseTo(float_button->GetBoundsInScreen().CenterPoint());
-    generator->ClickLeftButton();
-  }
+  FrameCaptionButtonContainerViewWithFloatTest(
+      const FrameCaptionButtonContainerViewWithFloatTest&) = delete;
+  FrameCaptionButtonContainerViewWithFloatTest& operator=(
+      const FrameCaptionButtonContainerViewWithFloatTest&) = delete;
+  ~FrameCaptionButtonContainerViewWithFloatTest() override = default;
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(WindowFloatButtonTest, TestFloatButtonBehavior) {
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      switches::kAshDeveloperShortcuts);
-
-  auto* widget = CreateTestWidget(MAXIMIZE_ALLOWED, MINIMIZE_ALLOWED,
-                                  CLOSE_BUTTON_VISIBLE);
-  auto* window = widget->GetNativeWindow();
-  window->SetProperty(aura::client::kAppType,
-                      static_cast<int>(ash::AppType::BROWSER));
-  widget->Show();
-
-  FrameCaptionButtonContainerView container(widget);
-  InitContainer(&container);
-  widget->GetContentsView()->AddChildView(&container);
-  views::test::RunScheduledLayout(&container);
-  FrameCaptionButtonContainerView::TestApi testApi(&container);
-
-  ClickFloatButton(&testApi);
-  auto* window_state = WindowState::Get(window);
-  // Check if window is floated.
-  EXPECT_TRUE(window_state->IsFloated());
-  EXPECT_EQ(window->GetProperty(chromeos::kWindowStateTypeKey),
-            chromeos::WindowStateType::kFloated);
-
-  ClickFloatButton(&testApi);
-  // Check if window is unfloated.
-  EXPECT_FALSE(window_state->IsFloated());
-  EXPECT_EQ(window->GetProperty(chromeos::kWindowStateTypeKey),
-            chromeos::WindowStateType::kNormal);
-}
-
-TEST_F(WindowFloatButtonTest, TabletSizeButtonVisibility) {
+TEST_F(FrameCaptionButtonContainerViewWithFloatTest,
+       TabletSizeButtonVisibility) {
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
 
   // Create a window in tablet mode. It should be maximized and the size button
diff --git a/ash/public/cpp/app_list/app_list_color_provider.h b/ash/public/cpp/app_list/app_list_color_provider.h
index 630a9518..693d6c0 100644
--- a/ash/public/cpp/app_list/app_list_color_provider.h
+++ b/ash/public/cpp/app_list/app_list_color_provider.h
@@ -20,8 +20,6 @@
 
   virtual SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const = 0;
-  virtual SkColor GetFolderBackgroundColor(
-      const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetFolderNotificationBadgeColor(
       const views::Widget* app_list_widget) const = 0;
   virtual SkColor GetGridBackgroundCardActiveColor(
diff --git a/ash/public/cpp/app_list/app_list_config.cc b/ash/public/cpp/app_list/app_list_config.cc
index e37acc5c..d2584b3 100644
--- a/ash/public/cpp/app_list/app_list_config.cc
+++ b/ash/public/cpp/app_list/app_list_config.cc
@@ -175,7 +175,6 @@
       folder_unclipped_icon_dimension_(
           FolderUnclippedIconDimensionForType(type)),
       folder_icon_radius_(FolderClippedIconDimensionForType(type) / 2),
-      folder_background_radius_(12),
       item_icon_in_folder_icon_dimension_(
           ItemIconInFolderIconDimensionForType(type)),
       item_icon_in_folder_icon_margin_(4),
@@ -203,8 +202,6 @@
       folder_unclipped_icon_dimension_(
           Scale(base_config.folder_unclipped_icon_dimension_, scale_x)),
       folder_icon_radius_(Scale(base_config.folder_icon_radius_, scale_x)),
-      folder_background_radius_(
-          Scale(base_config.folder_background_radius_, scale_x)),
       item_icon_in_folder_icon_dimension_(
           Scale(base_config.item_icon_in_folder_icon_dimension_, scale_x)),
       item_icon_in_folder_icon_margin_(
diff --git a/ash/public/cpp/app_list/app_list_config.h b/ash/public/cpp/app_list/app_list_config.h
index 9da6206..6d35ab8 100644
--- a/ash/public/cpp/app_list/app_list_config.h
+++ b/ash/public/cpp/app_list/app_list_config.h
@@ -189,7 +189,6 @@
     return folder_unclipped_icon_dimension_;
   }
   int folder_icon_radius() const { return folder_icon_radius_; }
-  int folder_background_radius() const { return folder_background_radius_; }
   int item_icon_in_folder_icon_dimension() const {
     return item_icon_in_folder_icon_dimension_;
   }
@@ -280,9 +279,6 @@
   // The corner radius of folder icon.
   const int folder_icon_radius_;
 
-  // The corner radius of folder background.
-  const int folder_background_radius_;
-
   // The dimension of the item icon in folder icon.
   const int item_icon_in_folder_icon_dimension_;
 
diff --git a/ash/public/cpp/test/test_app_list_color_provider.cc b/ash/public/cpp/test/test_app_list_color_provider.cc
index 0a32ca9..4e2cbe2 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.cc
+++ b/ash/public/cpp/test/test_app_list_color_provider.cc
@@ -9,11 +9,6 @@
 
 namespace ash {
 
-SkColor TestAppListColorProvider::GetFolderBackgroundColor(
-    const views::Widget* app_list_widget) const {
-  return gfx::kGoogleGrey900;
-}
-
 SkColor TestAppListColorProvider::GetPageSwitcherButtonColor(
     const views::Widget* app_list_widget) const {
   return gfx::kGoogleGrey700;
diff --git a/ash/public/cpp/test/test_app_list_color_provider.h b/ash/public/cpp/test/test_app_list_color_provider.h
index 2866de2..b931ca57 100644
--- a/ash/public/cpp/test/test_app_list_color_provider.h
+++ b/ash/public/cpp/test/test_app_list_color_provider.h
@@ -19,8 +19,6 @@
   // AppListColorProvider:
   SkColor GetPageSwitcherButtonColor(
       const views::Widget* app_list_widget) const override;
-  SkColor GetFolderBackgroundColor(
-      const views::Widget* app_list_widget) const override;
   SkColor GetFolderNotificationBadgeColor(
       const views::Widget* app_list_widget) const override;
   SkColor GetGridBackgroundCardActiveColor(
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
index bafd3c99e..222feda 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
@@ -34,6 +34,16 @@
 // Model IDs associated with the same device (for example, each Pixel Bud Pros
 // have different Model IDs for each different color) so we append '_*' to the
 // naming for subsequent Model IDs after the first one.
+const char kPopularPeripheral_BoatRockerz255Pro_ModelId[] = "CFF121";
+const char kPopularPeripheral_BoatRockerz255Pro_Name[] = "BoatRockerz255Pro";
+
+const char kPopularPeripheral_BoseQuietComfort35II_ModelId[] = "0100F0";
+const char kPopularPeripheral_BoseQuietComfort35II_Name[] =
+    "BoseQuietComfort35II";
+const char kPopularPeripheral_BoseQuietComfort35II_1_ModelId[] = "0000F0";
+const char kPopularPeripheral_BoseQuietComfort35II_1_Name[] =
+    "BoseQuietComfort35II_1";
+
 const char kPopularPeripheral_JBLLIVEPROTWS_ModelId[] = "461BB8";
 const char kPopularPeripheral_JBLLIVEPROTWS_Name[] = "JBLLIVEPROTWS";
 const char kPopularPeripheral_JBLLIVEPROTWS_1_ModelId[] = "C6936A";
@@ -80,6 +90,15 @@
 const char kPopularPeripheral_JBLTUNE125TWS_5_ModelId[] = "BD193B";
 const char kPopularPeripheral_JBLTUNE125TWS_5_Name[] = "JBLTUNE125TWS_5";
 
+const char kPopularPeripheral_JBLTUNE130NCTWS_ModelId[] = "BDB433";
+const char kPopularPeripheral_JBLTUNE130NCTWS_Name[] = "JBLTUNE130NCTWS";
+const char kPopularPeripheral_JBLTUNE130NCTWS_1_ModelId[] = "1115E7";
+const char kPopularPeripheral_JBLTUNE130NCTWS_1_Name[] = "JBLTUNE130NCTWS_1";
+const char kPopularPeripheral_JBLTUNE130NCTWS_2_ModelId[] = "436FD1";
+const char kPopularPeripheral_JBLTUNE130NCTWS_2_Name[] = "JBLTUNE130NCTWS_2";
+const char kPopularPeripheral_JBLTUNE130NCTWS_3_ModelId[] = "B73DBA";
+const char kPopularPeripheral_JBLTUNE130NCTWS_3_Name[] = "JBLTUNE130NCTWS_3";
+
 const char kPopularPeripheral_JBLTUNE225TWS_ModelId[] = "5C0C84";
 const char kPopularPeripheral_JBLTUNE225TWS_Name[] = "JBLTUNE225TWS";
 const char kPopularPeripheral_JBLTUNE225TWS_1_ModelId[] = "FAA6C3";
@@ -93,20 +112,27 @@
 const char kPopularPeripheral_JBLTUNE225TWS_5_ModelId[] = "9C98DB";
 const char kPopularPeripheral_JBLTUNE225TWS_5_Name[] = "JBLTUNE225TWS_5";
 
-const char kPopularPeripheral_JBLTUNE130NCTWS_ModelId[] = "BDB433";
-const char kPopularPeripheral_JBLTUNE130NCTWS_Name[] = "JBLTUNE130NCTWS";
-const char kPopularPeripheral_JBLTUNE130NCTWS_1_ModelId[] = "1115E7";
-const char kPopularPeripheral_JBLTUNE130NCTWS_1_Name[] = "JBLTUNE130NCTWS_1";
-const char kPopularPeripheral_JBLTUNE130NCTWS_2_ModelId[] = "436FD1";
-const char kPopularPeripheral_JBLTUNE130NCTWS_2_Name[] = "JBLTUNE130NCTWS_2";
-const char kPopularPeripheral_JBLTUNE130NCTWS_3_ModelId[] = "B73DBA";
-const char kPopularPeripheral_JBLTUNE130NCTWS_3_Name[] = "JBLTUNE130NCTWS_3";
+const char kPopularPeripheral_JBLTUNE230NCTWS_ModelId[] = "96C12E";
+const char kPopularPeripheral_JBLTUNE230NCTWS_Name[] = "JBLTUNE230NCTWS";
+const char kPopularPeripheral_JBLTUNE230NCTWS_1_ModelId[] = "71F20A";
+const char kPopularPeripheral_JBLTUNE230NCTWS_1_Name[] = "JBLTUNE230NCTWS_1";
+const char kPopularPeripheral_JBLTUNE230NCTWS_2_ModelId[] = "EB01C0";
+const char kPopularPeripheral_JBLTUNE230NCTWS_2_Name[] = "JBLTUNE230NCTWS_2";
+const char kPopularPeripheral_JBLTUNE230NCTWS_3_ModelId[] = "A9394A";
+const char kPopularPeripheral_JBLTUNE230NCTWS_3_Name[] = "JBLTUNE230NCTWS_3";
 
 const char kPopularPeripheral_NothingEar1_ModelId[] = "31D53D";
 const char kPopularPeripheral_NothingEar1_Name[] = "NOTHINGEAR1";
 const char kPopularPeripheral_NothingEar1_1_ModelId[] = "624011";
 const char kPopularPeripheral_NothingEar1_1_Name[] = "NOTHINGEAR1_1";
 
+const char kPopularPeripheral_OnePlusBuds_ModelId[] = "5F5806";
+const char kPopularPeripheral_OnePlusBuds_Name[] = "OnePlusBuds";
+const char kPopularPeripheral_OnePlusBuds_1_ModelId[] = "81B915";
+const char kPopularPeripheral_OnePlusBuds_1_Name[] = "OnePlusBuds_1";
+const char kPopularPeripheral_OnePlusBuds_2_ModelId[] = "6C73F1";
+const char kPopularPeripheral_OnePlusBuds_2_Name[] = "OnePlusBuds_2";
+
 const char kPopularPeripheral_OnePlusBudsZ_ModelId[] = "A41C91";
 const char kPopularPeripheral_OnePlusBudsZ_Name[] = "OnePlusBudsZ";
 const char kPopularPeripheral_OnePlusBudsZ_1_ModelId[] = "1393DE";
@@ -148,6 +174,19 @@
 const char kPopularPeripheral_RealMeBudsAir2Neo_ModelId[] = "0B5374";
 const char kPopularPeripheral_RealMeBudsAir2Neo_Name[] = "RealMeBudsAir2Neo";
 
+const char kPopularPeripheral_RealMeBudsQ2TWS_ModelId[] = "72C415";
+const char kPopularPeripheral_RealMeBudsQ2TWS_Name[] = "RealMeBudsQ2TWS";
+
+const char kPopularPeripheral_RealMeTechLifeBudsT100_ModelId[] = "29C992";
+const char kPopularPeripheral_RealMeTechLifeBudsT100_Name[] =
+    "RealMeTechLifeBudsT100";
+const char kPopularPeripheral_RealMeTechLifeBudsT100_1_ModelId[] = "D5C6CE";
+const char kPopularPeripheral_RealMeTechLifeBudsT100_1_Name[] =
+    "RealMeTechLifeBudsT100_1";
+const char kPopularPeripheral_RealMeTechLifeBudsT100_2_ModelId[] = "62E69F";
+const char kPopularPeripheral_RealMeTechLifeBudsT100_2_Name[] =
+    "RealMeTechLifeBudsT100_2";
+
 const char kPopularPeripheral_SonyWF1000XM3_ModelId[] = "38C95C";
 const char kPopularPeripheral_SonyWF1000XM3_Name[] = "SonyWF1000XM3";
 const char kPopularPeripheral_SonyWF1000XM3_1_ModelId[] = "9C98DB";
@@ -170,6 +209,19 @@
 const char kPopularPeripheral_SonyWH1000XM3_1_ModelId[] = "AC95C";
 const char kPopularPeripheral_SonyWH1000XM3_1_Name[] = "SonyWH1000XM3_1";
 
+const char kPopularPeripheral_SRSXB13_ModelId[] = "741594";
+const char kPopularPeripheral_SRSXB13_Name[] = "SRSXB13";
+const char kPopularPeripheral_SRSXB13_1_ModelId[] = "DF4B02";
+const char kPopularPeripheral_SRSXB13_1_Name[] = "SRSXB13_1";
+const char kPopularPeripheral_SRSXB13_2_ModelId[] = "F5CEC7";
+const char kPopularPeripheral_SRSXB13_2_Name[] = "SRSXB13_2";
+const char kPopularPeripheral_SRSXB13_3_ModelId[] = "FFB35B";
+const char kPopularPeripheral_SRSXB13_3_Name[] = "SRSXB13_3";
+const char kPopularPeripheral_SRSXB13_4_ModelId[] = "36EFA5";
+const char kPopularPeripheral_SRSXB13_4_Name[] = "SRSXB13_4";
+const char kPopularPeripheral_SRSXB13_5_ModelId[] = "3E6B5B";
+const char kPopularPeripheral_SRSXB13_5_Name[] = "SRSXB13_5";
+
 const char kPopularPeripheral_SRSXB23_ModelId[] = "30D222";
 const char kPopularPeripheral_SRSXB23_Name[] = "SRSXB23";
 const char kPopularPeripheral_SRSXB23_1_ModelId[] = "438188";
@@ -193,161 +245,297 @@
 const char kPopularPeripheral_Other_Name[] = "Other";
 
 const std::string GetFastPairTrackedModelId(const std::string& model_id) {
-  if (model_id == kPopularPeripheral_JBLLIVE300TWS_ModelId)
-    return kPopularPeripheral_JBLLIVE300TWS_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE300TWS_1_ModelId)
-    return kPopularPeripheral_JBLLIVE300TWS_1_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE300TWS_2_ModelId)
-    return kPopularPeripheral_JBLLIVE300TWS_2_Name;
+  if (model_id == kPopularPeripheral_BoatRockerz255Pro_ModelId) {
+    return kPopularPeripheral_BoatRockerz255Pro_Name;
+  }
 
-  if (model_id == kPopularPeripheral_JBLLIVE400BT_ModelId)
-    return kPopularPeripheral_JBLLIVE400BT_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE400BT_1_ModelId)
-    return kPopularPeripheral_JBLLIVE400BT_1_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE400BT_2_ModelId)
-    return kPopularPeripheral_JBLLIVE400BT_2_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE400BT_3_ModelId)
-    return kPopularPeripheral_JBLLIVE400BT_3_Name;
-  if (model_id == kPopularPeripheral_JBLLIVE400BT_4_ModelId)
-    return kPopularPeripheral_JBLLIVE400BT_4_Name;
+  if (model_id == kPopularPeripheral_BoseQuietComfort35II_ModelId) {
+    return kPopularPeripheral_BoseQuietComfort35II_Name;
+  }
+  if (model_id == kPopularPeripheral_BoseQuietComfort35II_1_ModelId) {
+    return kPopularPeripheral_BoseQuietComfort35II_1_Name;
+  }
 
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_1_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_1_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_2_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_2_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_3_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_3_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_4_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_4_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE125TWS_5_ModelId)
-    return kPopularPeripheral_JBLTUNE125TWS_5_Name;
-
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_1_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_1_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_2_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_2_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_3_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_3_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_4_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_4_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE225TWS_5_ModelId)
-    return kPopularPeripheral_JBLTUNE225TWS_5_Name;
-
-  if (model_id == kPopularPeripheral_OnePlusBudsZ_ModelId)
-    return kPopularPeripheral_OnePlusBudsZ_Name;
-  if (model_id == kPopularPeripheral_OnePlusBudsZ_1_ModelId)
-    return kPopularPeripheral_OnePlusBudsZ_1_Name;
-  if (model_id == kPopularPeripheral_OnePlusBudsZ_2_ModelId)
-    return kPopularPeripheral_OnePlusBudsZ_2_Name;
-
-  if (model_id == kPopularPeripheral_PixelBuds_ModelId)
-    return kPopularPeripheral_PixelBuds_Name;
-
-  if (model_id == kPopularPeripheral_PixelBudsASeries_ModelId)
-    return kPopularPeripheral_PixelBudsASeries_Name;
-  if (model_id == kPopularPeripheral_PixelBudsASeries_1_ModelId)
-    return kPopularPeripheral_PixelBudsASeries_1_Name;
-  if (model_id == kPopularPeripheral_PixelBudsASeries_2_ModelId)
-    return kPopularPeripheral_PixelBudsASeries_2_Name;
-
-  if (model_id == kPopularPeripheral_PixelBudsPro_ModelId)
-    return kPopularPeripheral_PixelBudsPro_Name;
-  if (model_id == kPopularPeripheral_PixelBudsPro_1_ModelId)
-    return kPopularPeripheral_PixelBudsPro_1_Name;
-  if (model_id == kPopularPeripheral_PixelBudsPro_2_ModelId)
-    return kPopularPeripheral_PixelBudsPro_2_Name;
-  if (model_id == kPopularPeripheral_PixelBudsPro_3_ModelId)
-    return kPopularPeripheral_PixelBudsPro_3_Name;
-  if (model_id == kPopularPeripheral_PixelBudsPro_4_ModelId)
-    return kPopularPeripheral_PixelBudsPro_4_Name;
-
-  if (model_id == kPopularPeripheral_RealMeBudsAir2_ModelId)
-    return kPopularPeripheral_RealMeBudsAir2_Name;
-
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_1_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_1_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_2_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_2_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_3_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_3_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_4_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_4_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_5_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_5_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_6_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_6_Name;
-  if (model_id == kPopularPeripheral_SonyWF1000XM3_7_ModelId)
-    return kPopularPeripheral_SonyWF1000XM3_7_Name;
-
-  if (model_id == kPopularPeripheral_NothingEar1_ModelId)
-    return kPopularPeripheral_NothingEar1_Name;
-  if (model_id == kPopularPeripheral_NothingEar1_1_ModelId)
-    return kPopularPeripheral_NothingEar1_1_Name;
-
-  if (model_id == kPopularPeripheral_RealMeBudsAir2Neo_ModelId)
-    return kPopularPeripheral_RealMeBudsAir2Neo_Name;
-
-  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_ModelId)
-    return kPopularPeripheral_JBLTUNE130NCTWS_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_1_ModelId)
-    return kPopularPeripheral_JBLTUNE130NCTWS_1_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_2_ModelId)
-    return kPopularPeripheral_JBLTUNE130NCTWS_2_Name;
-  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_3_ModelId)
-    return kPopularPeripheral_JBLTUNE130NCTWS_3_Name;
-
-  if (model_id == kPopularPeripheral_SonyWH1000XM3_ModelId)
-    return kPopularPeripheral_SonyWH1000XM3_Name;
-  if (model_id == kPopularPeripheral_SonyWH1000XM3_1_ModelId)
-    return kPopularPeripheral_SonyWH1000XM3_1_Name;
-
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_ModelId)
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_1_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_1_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_1_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_2_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_2_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_2_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_3_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_3_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_3_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_4_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_4_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_4_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_5_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_5_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_5_Name;
-  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_6_ModelId)
+  }
+  if (model_id == kPopularPeripheral_JBLLIVEPROTWS_6_ModelId) {
     return kPopularPeripheral_JBLLIVEPROTWS_6_Name;
+  }
 
-  if (model_id == kPopularPeripheral_RealMeBudsAirPro_ModelId)
+  if (model_id == kPopularPeripheral_JBLLIVE300TWS_ModelId) {
+    return kPopularPeripheral_JBLLIVE300TWS_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE300TWS_1_ModelId) {
+    return kPopularPeripheral_JBLLIVE300TWS_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE300TWS_2_ModelId) {
+    return kPopularPeripheral_JBLLIVE300TWS_2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_JBLLIVE400BT_ModelId) {
+    return kPopularPeripheral_JBLLIVE400BT_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE400BT_1_ModelId) {
+    return kPopularPeripheral_JBLLIVE400BT_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE400BT_2_ModelId) {
+    return kPopularPeripheral_JBLLIVE400BT_2_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE400BT_3_ModelId) {
+    return kPopularPeripheral_JBLLIVE400BT_3_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLLIVE400BT_4_ModelId) {
+    return kPopularPeripheral_JBLLIVE400BT_4_Name;
+  }
+
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_1_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_2_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_2_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_3_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_3_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_4_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_4_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE125TWS_5_ModelId) {
+    return kPopularPeripheral_JBLTUNE125TWS_5_Name;
+  }
+
+  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_ModelId) {
+    return kPopularPeripheral_JBLTUNE130NCTWS_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_1_ModelId) {
+    return kPopularPeripheral_JBLTUNE130NCTWS_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_2_ModelId) {
+    return kPopularPeripheral_JBLTUNE130NCTWS_2_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE130NCTWS_3_ModelId) {
+    return kPopularPeripheral_JBLTUNE130NCTWS_3_Name;
+  }
+
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_1_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_2_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_2_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_3_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_3_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_4_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_4_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE225TWS_5_ModelId) {
+    return kPopularPeripheral_JBLTUNE225TWS_5_Name;
+  }
+
+  if (model_id == kPopularPeripheral_JBLTUNE230NCTWS_ModelId) {
+    return kPopularPeripheral_JBLTUNE230NCTWS_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE230NCTWS_1_ModelId) {
+    return kPopularPeripheral_JBLTUNE230NCTWS_1_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE230NCTWS_2_ModelId) {
+    return kPopularPeripheral_JBLTUNE230NCTWS_2_Name;
+  }
+  if (model_id == kPopularPeripheral_JBLTUNE230NCTWS_3_ModelId) {
+    return kPopularPeripheral_JBLTUNE230NCTWS_3_Name;
+  }
+
+  if (model_id == kPopularPeripheral_NothingEar1_ModelId) {
+    return kPopularPeripheral_NothingEar1_Name;
+  }
+  if (model_id == kPopularPeripheral_NothingEar1_1_ModelId) {
+    return kPopularPeripheral_NothingEar1_1_Name;
+  }
+
+  if (model_id == kPopularPeripheral_OnePlusBuds_ModelId) {
+    return kPopularPeripheral_OnePlusBuds_Name;
+  }
+  if (model_id == kPopularPeripheral_OnePlusBuds_1_ModelId) {
+    return kPopularPeripheral_OnePlusBuds_1_Name;
+  }
+  if (model_id == kPopularPeripheral_OnePlusBuds_2_ModelId) {
+    return kPopularPeripheral_OnePlusBuds_2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_OnePlusBudsZ_ModelId) {
+    return kPopularPeripheral_OnePlusBudsZ_Name;
+  }
+  if (model_id == kPopularPeripheral_OnePlusBudsZ_1_ModelId) {
+    return kPopularPeripheral_OnePlusBudsZ_1_Name;
+  }
+  if (model_id == kPopularPeripheral_OnePlusBudsZ_2_ModelId) {
+    return kPopularPeripheral_OnePlusBudsZ_2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_PixelBuds_ModelId) {
+    return kPopularPeripheral_PixelBuds_Name;
+  }
+
+  if (model_id == kPopularPeripheral_PixelBudsASeries_ModelId) {
+    return kPopularPeripheral_PixelBudsASeries_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsASeries_1_ModelId) {
+    return kPopularPeripheral_PixelBudsASeries_1_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsASeries_2_ModelId) {
+    return kPopularPeripheral_PixelBudsASeries_2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_PixelBudsPro_ModelId) {
+    return kPopularPeripheral_PixelBudsPro_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsPro_1_ModelId) {
+    return kPopularPeripheral_PixelBudsPro_1_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsPro_2_ModelId) {
+    return kPopularPeripheral_PixelBudsPro_2_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsPro_3_ModelId) {
+    return kPopularPeripheral_PixelBudsPro_3_Name;
+  }
+  if (model_id == kPopularPeripheral_PixelBudsPro_4_ModelId) {
+    return kPopularPeripheral_PixelBudsPro_4_Name;
+  }
+
+  if (model_id == kPopularPeripheral_RealMeBudsAir2_ModelId) {
+    return kPopularPeripheral_RealMeBudsAir2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_RealMeBudsAir2Neo_ModelId) {
+    return kPopularPeripheral_RealMeBudsAir2Neo_Name;
+  }
+
+  if (model_id == kPopularPeripheral_RealMeBudsAirPro_ModelId) {
     return kPopularPeripheral_RealMeBudsAirPro_Name;
-  if (model_id == kPopularPeripheral_RealMeBudsAirPro_1_ModelId)
+  }
+  if (model_id == kPopularPeripheral_RealMeBudsAirPro_1_ModelId) {
     return kPopularPeripheral_RealMeBudsAirPro_1_Name;
-  if (model_id == kPopularPeripheral_RealMeBudsAirPro_2_ModelId)
+  }
+  if (model_id == kPopularPeripheral_RealMeBudsAirPro_2_ModelId) {
     return kPopularPeripheral_RealMeBudsAirPro_2_Name;
+  }
 
-  if (model_id == kPopularPeripheral_SRSXB33_ModelId)
+  if (model_id == kPopularPeripheral_RealMeBudsQ2TWS_ModelId) {
+    return kPopularPeripheral_RealMeBudsQ2TWS_Name;
+  }
+
+  if (model_id == kPopularPeripheral_RealMeTechLifeBudsT100_ModelId) {
+    return kPopularPeripheral_RealMeTechLifeBudsT100_Name;
+  }
+  if (model_id == kPopularPeripheral_RealMeTechLifeBudsT100_1_ModelId) {
+    return kPopularPeripheral_RealMeTechLifeBudsT100_1_Name;
+  }
+  if (model_id == kPopularPeripheral_RealMeTechLifeBudsT100_2_ModelId) {
+    return kPopularPeripheral_RealMeTechLifeBudsT100_2_Name;
+  }
+
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_1_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_1_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_2_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_2_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_3_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_3_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_4_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_4_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_5_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_5_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_6_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_6_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWF1000XM3_7_ModelId) {
+    return kPopularPeripheral_SonyWF1000XM3_7_Name;
+  }
+
+  if (model_id == kPopularPeripheral_SonyWH1000XM3_ModelId) {
+    return kPopularPeripheral_SonyWH1000XM3_Name;
+  }
+  if (model_id == kPopularPeripheral_SonyWH1000XM3_1_ModelId) {
+    return kPopularPeripheral_SonyWH1000XM3_1_Name;
+  }
+
+  if (model_id == kPopularPeripheral_SRSXB33_ModelId) {
     return kPopularPeripheral_SRSXB33_Name;
-  if (model_id == kPopularPeripheral_SRSXB33_1_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB33_1_ModelId) {
     return kPopularPeripheral_SRSXB33_1_Name;
-  if (model_id == kPopularPeripheral_SRSXB33_2_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB33_2_ModelId) {
     return kPopularPeripheral_SRSXB33_2_Name;
-  if (model_id == kPopularPeripheral_SRSXB33_3_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB33_3_ModelId) {
     return kPopularPeripheral_SRSXB33_3_Name;
+  }
 
-  if (model_id == kPopularPeripheral_SRSXB23_ModelId)
+  if (model_id == kPopularPeripheral_SRSXB23_ModelId) {
     return kPopularPeripheral_SRSXB23_Name;
-  if (model_id == kPopularPeripheral_SRSXB23_1_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB23_1_ModelId) {
     return kPopularPeripheral_SRSXB23_1_Name;
-  if (model_id == kPopularPeripheral_SRSXB23_2_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB23_2_ModelId) {
     return kPopularPeripheral_SRSXB23_2_Name;
-  if (model_id == kPopularPeripheral_SRSXB23_3_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB23_3_ModelId) {
     return kPopularPeripheral_SRSXB23_3_Name;
-  if (model_id == kPopularPeripheral_SRSXB23_4_ModelId)
+  }
+  if (model_id == kPopularPeripheral_SRSXB23_4_ModelId) {
     return kPopularPeripheral_SRSXB23_4_Name;
+  }
+
+  if (model_id == kPopularPeripheral_SRSXB13_ModelId) {
+    return kPopularPeripheral_SRSXB13_Name;
+  }
+  if (model_id == kPopularPeripheral_SRSXB13_1_ModelId) {
+    return kPopularPeripheral_SRSXB13_1_Name;
+  }
+  if (model_id == kPopularPeripheral_SRSXB13_2_ModelId) {
+    return kPopularPeripheral_SRSXB13_2_Name;
+  }
+  if (model_id == kPopularPeripheral_SRSXB13_3_ModelId) {
+    return kPopularPeripheral_SRSXB13_3_Name;
+  }
+  if (model_id == kPopularPeripheral_SRSXB13_4_ModelId) {
+    return kPopularPeripheral_SRSXB13_4_Name;
+  }
+  if (model_id == kPopularPeripheral_SRSXB13_5_ModelId) {
+    return kPopularPeripheral_SRSXB13_5_Name;
+  }
 
   return kPopularPeripheral_Other_Name;
 }
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index 746312d..2c74212a 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -79,7 +79,6 @@
 std::unique_ptr<FastPairPairer> FastPairPairerImpl::Factory::Create(
     scoped_refptr<device::BluetoothAdapter> adapter,
     scoped_refptr<Device> device,
-    base::OnceCallback<void(scoped_refptr<Device>)> handshake_complete_callback,
     base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
     base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
         pair_failed_callback,
@@ -89,16 +88,14 @@
         pairing_procedure_complete) {
   if (g_test_factory_) {
     return g_test_factory_->CreateInstance(
-        std::move(adapter), std::move(device),
-        std::move(handshake_complete_callback), std::move(paired_callback),
+        std::move(adapter), std::move(device), std::move(paired_callback),
         std::move(pair_failed_callback),
         std::move(account_key_failure_callback),
         std::move(pairing_procedure_complete));
   }
 
   return base::WrapUnique(new FastPairPairerImpl(
-      std::move(adapter), std::move(device),
-      std::move(handshake_complete_callback), std::move(paired_callback),
+      std::move(adapter), std::move(device), std::move(paired_callback),
       std::move(pair_failed_callback), std::move(account_key_failure_callback),
       std::move(pairing_procedure_complete)));
 }
@@ -114,7 +111,6 @@
 FastPairPairerImpl::FastPairPairerImpl(
     scoped_refptr<device::BluetoothAdapter> adapter,
     scoped_refptr<Device> device,
-    base::OnceCallback<void(scoped_refptr<Device>)> handshake_complete_callback,
     base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
     base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
         pair_failed_callback,
@@ -123,7 +119,6 @@
     base::OnceCallback<void(scoped_refptr<Device>)> pairing_procedure_complete)
     : adapter_(std::move(adapter)),
       device_(std::move(device)),
-      handshake_complete_callback_(std::move(handshake_complete_callback)),
       paired_callback_(std::move(paired_callback)),
       pair_failed_callback_(std::move(pair_failed_callback)),
       account_key_failure_callback_(std::move(account_key_failure_callback)),
@@ -149,66 +144,9 @@
 
   fast_pair_handshake_ = FastPairHandshakeLookup::GetInstance()->Get(device_);
 
-  if (fast_pair_handshake_) {
-    // Handle cases where we are retrying pair after a non-handshake related
-    // error occurs.
-    if (fast_pair_handshake_->completed_successfully()) {
-      QP_LOG(VERBOSE) << __func__
-                      << ": Reusing handshake for retried pair attempt.";
-      RecordFastPairInitializePairingProcessEvent(
-          *device_, FastPairInitializePairingProcessEvent::kHandshakeReused);
-      OnHandshakeComplete(device_, /*failure=*/absl::nullopt);
-      return;
-    }
-
-    // Handles cases where we are retrying pair after an error occurred when
-    // creating the handshake.
-    QP_LOG(VERBOSE) << __func__
-                    << ": Clearing failed handshake for retried pair attempt.";
-    FastPairHandshakeLookup::GetInstance()->Erase(device_);
-    fast_pair_handshake_ = nullptr;
-  }
-
-  QP_LOG(VERBOSE) << __func__ << ": Creating new handshake for pair attempt.";
-  FastPairHandshakeLookup::GetInstance()->Create(
-      adapter_, device_,
-      base::BindOnce(&FastPairPairerImpl::OnHandshakeComplete,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void FastPairPairerImpl::OnHandshakeComplete(
-    scoped_refptr<Device> device,
-    absl::optional<PairFailure> failure) {
-  // TODO(b/259429032) : Log with `RecordInitializationRetriesBeforeSuccess`
-  // the number of handshake retries occurred before success. Log with
-  // `FastPairInitializePairingProcessEvent` if we have exhausted the retries.
-
-  if (failure.has_value()) {
-    QP_LOG(WARNING) << __func__ << ": Handshake failed with " << device
-                    << " because: " << failure.value();
-    RecordInitializationFailureReason(*device, failure.value());
-    std::move(pair_failed_callback_).Run(device_, failure.value());
-    // |this| may be destroyed after this line.
-    return;
-  }
-
-  // During handshake, the device address can be set to null.
-  if (!device_->classic_address()) {
-    QP_LOG(WARNING) << __func__ << ": Device lost during handshake.";
-    RecordInitializationFailureReason(*device, PairFailure::kPairingDeviceLost);
-    std::move(pair_failed_callback_)
-        .Run(device_, PairFailure::kPairingDeviceLost);
-    // |this| may be destroyed after this line.
-    return;
-  }
-
-  fast_pair_handshake_ = FastPairHandshakeLookup::GetInstance()->Get(device_);
-
   DCHECK(fast_pair_handshake_);
   DCHECK(fast_pair_handshake_->completed_successfully());
 
-  std::move(handshake_complete_callback_).Run(device_);
-
   fast_pair_gatt_service_client_ =
       fast_pair_handshake_->fast_pair_gatt_service_client();
 
@@ -225,7 +163,6 @@
 void FastPairPairerImpl::StartPairing() {
   RecordProtocolPairingStep(FastPairProtocolPairingSteps::kPairingStarted,
                             *device_);
-
   std::string device_address = device_->classic_address().value();
   device::BluetoothDevice* bt_device = adapter_->GetDevice(device_address);
   switch (device_->protocol) {
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h
index 6bbc0f4..9db4d0ed 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h
@@ -44,8 +44,6 @@
     static std::unique_ptr<FastPairPairer> Create(
         scoped_refptr<device::BluetoothAdapter> adapter,
         scoped_refptr<Device> device,
-        base::OnceCallback<void(scoped_refptr<Device>)>
-            handshake_complete_callback,
         base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
         base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
             pair_failed_callback,
@@ -62,8 +60,6 @@
     virtual std::unique_ptr<FastPairPairer> CreateInstance(
         scoped_refptr<device::BluetoothAdapter> adapter,
         scoped_refptr<Device> device,
-        base::OnceCallback<void(scoped_refptr<Device>)>
-            handshake_complete_callback,
         base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
         base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
             pair_failed_callback,
@@ -79,8 +75,6 @@
   FastPairPairerImpl(
       scoped_refptr<device::BluetoothAdapter> adapter,
       scoped_refptr<Device> device,
-      base::OnceCallback<void(scoped_refptr<Device>)>
-          handshake_complete_callback,
       base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
       base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
           pair_failed_callback,
@@ -176,7 +170,6 @@
   scoped_refptr<Device> device_;
   FastPairGattServiceClient* fast_pair_gatt_service_client_;
   std::string pairing_device_address_;
-  base::OnceCallback<void(scoped_refptr<Device>)> handshake_complete_callback_;
   base::OnceCallback<void(scoped_refptr<Device>)> paired_callback_;
   base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
       pair_failed_callback_;
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
index 5804b6ee..441c426b 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
@@ -110,16 +110,6 @@
     "FastPair.SubsequentPairing.Pairing";
 constexpr char kInitializePairingProcessInitial[] =
     "FastPair.InitialPairing.Initialization";
-constexpr char kInitializePairingProcessSubsequent[] =
-    "FastPair.SubsequentPairing.Initialization";
-constexpr char kInitializePairingProcessRetroactive[] =
-    "FastPair.RetroactivePairing.Initialization";
-constexpr char kInitializePairingProcessFailureReasonInitial[] =
-    "FastPair.InitialPairing.Initialization.FailureReason";
-constexpr char kInitializePairingProcessFailureReasonSubsequent[] =
-    "FastPair.SubsequentPairing.Initialization.FailureReason";
-constexpr char kInitializePairingProcessFailureReasonRetroactive[] =
-    "FastPair.RetroactivePairing.Initialization.FailureReason";
 
 class FakeBluetoothAdapter
     : public testing::NiceMock<device::MockBluetoothAdapter> {
@@ -296,8 +286,6 @@
     AshTestBase::SetUp();
     FastPairGattServiceClientImpl::Factory::SetFactoryForTesting(
         &fast_pair_gatt_service_factory_);
-    FastPairHandshakeLookup::SetCreateFunctionForTesting(base::BindRepeating(
-        &FastPairPairerImplTest::CreateHandshake, base::Unretained(this)));
 
     adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
 
@@ -339,7 +327,16 @@
     FastPairHandshakeLookup::GetInstance()->Erase(device_);
   }
 
-  std::unique_ptr<FastPairHandshake> CreateHandshake(
+  void AddConnectedHandshake() {
+    FastPairHandshakeLookup::SetCreateFunctionForTesting(
+        base::BindRepeating(&FastPairPairerImplTest::CreateConnectedHandshake,
+                            base::Unretained(this)));
+
+    FastPairHandshakeLookup::GetInstance()->Create(adapter_, device_,
+                                                   base::DoNothing());
+  }
+
+  std::unique_ptr<FastPairHandshake> CreateConnectedHandshake(
       scoped_refptr<Device> device,
       FastPairHandshake::OnCompleteCallback callback) {
     // This is the only place where data_encryptor_unique_ is used. We assume
@@ -453,8 +450,7 @@
   // This is done on-demand to enable setting up mock expectations first.
   void CreatePairer() {
     pairer_ = std::make_unique<FastPairPairerImpl>(
-        adapter_, device_, handshake_complete_callback_.Get(),
-        paired_callback_.Get(),
+        adapter_, device_, paired_callback_.Get(),
         base::BindOnce(&FastPairPairerImplTest::PairFailedCallback,
                        weak_ptr_factory_.GetWeakPtr()),
         account_key_failure_callback_.Get(), pairing_procedure_complete_.Get());
@@ -462,8 +458,7 @@
 
   void CreatePairerAsFactory() {
     pairer_ = FastPairPairerImpl::Factory::Create(
-        adapter_, device_, handshake_complete_callback_.Get(),
-        paired_callback_.Get(),
+        adapter_, device_, paired_callback_.Get(),
         base::BindOnce(&FastPairPairerImplTest::PairFailedCallback,
                        weak_ptr_factory_.GetWeakPtr()),
         account_key_failure_callback_.Get(), pairing_procedure_complete_.Get());
@@ -472,25 +467,24 @@
   void CreateDevice(DeviceFastPairVersion version) {
     CreateMockDevice(version,
                      /*protocol=*/Protocol::kFastPairInitial);
+
+    // Adds a connected handshake that has completed successfully in
+    // 'FastPairHandshakeLookup' for the mock device.
+    SetGetDeviceNullptr();
+    AddConnectedHandshake();
+    fake_fast_pair_handshake_->InvokeCallback();
     CreatePairer();
     if (version == DeviceFastPairVersion::kHigherThanV1) {
       SetPublicKey();
-      // When pairing starts, if the classic address can't be resolved to
-      // a device then we pair via address.
-      SetGetDeviceNullptr();
-      fake_fast_pair_handshake_->InvokeCallback();
-      base::RunLoop().RunUntilIdle();
       EXPECT_EQ(GetPairFailure(), absl::nullopt);
       EXPECT_CALL(paired_callback_, Run);
       SetDecryptPasskeyForSuccess();
       NotifyConfirmPasskey();
-      base::RunLoop().RunUntilIdle();
     }
   }
 
   void PerformAndCheckSuccessfulPairingCallbacks() {
     RunWritePasskeyCallback(kResponseBytes);
-    base::RunLoop().RunUntilIdle();
     EXPECT_CALL(pairing_procedure_complete_, Run).Times(1);
     EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
     adapter_->DevicePairedChanged(fake_bluetooth_device_ptr_, true);
@@ -523,84 +517,37 @@
   base::WeakPtrFactory<FastPairPairerImplTest> weak_ptr_factory_{this};
 };
 
-TEST_F(FastPairPairerImplTest, NoPairingIfHandshakeFailed_Initial) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
-  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(GetPairFailure(), PairFailure::kCreateGattConnection);
-  EXPECT_EQ(histogram_tester_.GetBucketCount(
-                kInitializePairingProcessFailureReasonInitial,
-                PairFailure::kCreateGattConnection),
-            1);
-}
-
-TEST_F(FastPairPairerImplTest, NoPairingIfHandshakeFailed_Subsequent) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairSubsequent);
-  CreatePairer();
-  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(GetPairFailure(), PairFailure::kCreateGattConnection);
-  EXPECT_EQ(histogram_tester_.GetBucketCount(
-                kInitializePairingProcessFailureReasonSubsequent,
-                PairFailure::kCreateGattConnection),
-            1);
-}
-
-TEST_F(FastPairPairerImplTest, NoPairingIfHandshakeFailed_Retroactive) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairRetroactive);
-  CreatePairer();
-  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(GetPairFailure(), PairFailure::kCreateGattConnection);
-  EXPECT_EQ(histogram_tester_.GetBucketCount(
-                kInitializePairingProcessFailureReasonRetroactive,
-                PairFailure::kCreateGattConnection),
-            1);
-}
-
 TEST_F(FastPairPairerImplTest, NoCallbackIsInvokedOnGattSuccess_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
 }
 
 TEST_F(FastPairPairerImplTest, NoCallbackIsInvokedOnGattSuccess_Retroactive) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
 }
 
 TEST_F(FastPairPairerImplTest, NoCallbackIsInvokedOnGattSuccess_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
 }
 
@@ -608,16 +555,15 @@
 // most other tests in this file.
 TEST_F(FastPairPairerImplTest, PairByDeviceFailure_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPairDeviceResult, 0);
   histogram_tester().ExpectTotalCount(kPairDeviceErrorReason, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPairFailure();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), PairFailure::kPairingConnect);
   histogram_tester().ExpectTotalCount(kPairDeviceResult, 1);
   histogram_tester().ExpectTotalCount(kPairDeviceErrorReason, 1);
@@ -625,17 +571,16 @@
 
 TEST_F(FastPairPairerImplTest, PairByDeviceFailure_Initial_CancelsPairing) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPairFailure();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
   CreatePairer();
 
   // Mock that the device was paired unsuccessfully.
   EXPECT_CALL(*fake_bluetooth_device_ptr_, IsPaired()).WillOnce(Return(false));
-  fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
 
   // Check to make sure that, when pairing fails, we call CancelPairing.
   EXPECT_CALL(*fake_bluetooth_device_ptr_, CancelPairing()).Times(1);
@@ -643,16 +588,15 @@
 
 TEST_F(FastPairPairerImplTest, PairByDeviceFailure_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPairDeviceResult, 0);
   histogram_tester().ExpectTotalCount(kPairDeviceErrorReason, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
   SetPairFailure();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), PairFailure::kPairingConnect);
   histogram_tester().ExpectTotalCount(kPairDeviceResult, 1);
   histogram_tester().ExpectTotalCount(kPairDeviceErrorReason, 1);
@@ -662,13 +606,12 @@
 
 TEST_F(FastPairPairerImplTest, PairByDeviceSuccess_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   ExpectStepMetrics(kProtocolPairingStepInitial,
                     {FastPairProtocolPairingSteps::kPairingStarted,
@@ -678,17 +621,17 @@
 TEST_F(FastPairPairerImplTest,
        PairByDeviceSuccess_Initial_AlreadyClassicPaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+
   // Mock that the device is already paired.
   EXPECT_CALL(*fake_bluetooth_device_ptr_, IsBonded()).WillOnce(Return(true));
-
   EXPECT_CALL(paired_callback_, Run);
-  fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
+
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
 
   // For an already classic paired device, we skip right to Account Key writing.
@@ -703,11 +646,12 @@
 
 TEST_F(FastPairPairerImplTest, PairByDeviceSuccess_Initial_AlreadyFastPaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+
   // Mock that the device is already fast paired (and saved to Footprints).
   fast_pair_repository_.SaveMacAddressToAccount(kBluetoothCanonicalizedAddress);
   EXPECT_CALL(*fake_bluetooth_device_ptr_, IsBonded()).WillOnce(Return(true));
@@ -715,8 +659,8 @@
   // For an already fast paired device, we skip the Account Key writing.
   EXPECT_CALL(paired_callback_, Run);
   EXPECT_CALL(pairing_procedure_complete_, Run);
-  fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
+
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_EQ(
       histogram_tester().GetBucketCount(
@@ -732,17 +676,17 @@
 TEST_F(FastPairPairerImplTest,
        PairByDeviceSuccess_Subsequent_AlreadyClassicPaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
-  CreatePairer();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+
   // Mock that the device is already paired.
   EXPECT_CALL(*fake_bluetooth_device_ptr_, IsBonded()).WillOnce(Return(true));
 
   EXPECT_CALL(paired_callback_, Run);
-  fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   ExpectStepMetrics(kProtocolPairingStepSubsequent,
                     {FastPairProtocolPairingSteps::kPairingStarted,
@@ -753,11 +697,12 @@
 TEST_F(FastPairPairerImplTest,
        PairByDeviceSuccess_Subsequent_AlreadyFastPaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
-  CreatePairer();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+
   // Mock that the device is already fast paired (and saved to Footprints).
   fast_pair_repository_.SaveMacAddressToAccount(kBluetoothCanonicalizedAddress);
   EXPECT_CALL(*fake_bluetooth_device_ptr_, IsBonded()).WillOnce(Return(true));
@@ -765,8 +710,7 @@
   // For an already fast paired device, we skip the Account Key writing.
   EXPECT_CALL(paired_callback_, Run);
   EXPECT_CALL(pairing_procedure_complete_, Run);
-  fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   ExpectStepMetrics(kProtocolPairingStepSubsequent,
                     {FastPairProtocolPairingSteps::kPairingStarted,
@@ -776,13 +720,12 @@
 
 TEST_F(FastPairPairerImplTest, PairByDeviceSuccess_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   ExpectStepMetrics(kProtocolPairingStepSubsequent,
                     {FastPairProtocolPairingSteps::kPairingStarted,
@@ -791,18 +734,19 @@
 
 TEST_F(FastPairPairerImplTest, ConnectFailure_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConnectDeviceResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetConnectFailure();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
 
   EXPECT_EQ(GetPairFailure(), PairFailure::kAddressConnect);
   histogram_tester().ExpectTotalCount(kConnectDeviceResult, 1);
@@ -812,18 +756,19 @@
 
 TEST_F(FastPairPairerImplTest, ConnectFailure_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConnectDeviceResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
   SetConnectFailure();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), PairFailure::kAddressConnect);
   histogram_tester().ExpectTotalCount(kConnectDeviceResult, 1);
   ExpectStepMetrics(kProtocolPairingStepSubsequent,
@@ -832,7 +777,6 @@
 
 TEST_F(FastPairPairerImplTest, ConnectSuccess_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -840,12 +784,14 @@
       kWritePasskeyCharacteristicPairFailureMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -858,7 +804,6 @@
 
 TEST_F(FastPairPairerImplTest, ConnectSuccess_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -866,12 +811,14 @@
       kWritePasskeyCharacteristicPairFailureMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -884,7 +831,6 @@
 
 TEST_F(FastPairPairerImplTest, ParseDecryptedPasskeyFailure_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -894,15 +840,16 @@
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
 
   RunWritePasskeyCallback({}, PairFailure::kPasskeyPairingCharacteristicWrite);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPasskeyPairingCharacteristicWrite);
@@ -921,7 +868,6 @@
 
 TEST_F(FastPairPairerImplTest, ParseDecryptedPasskeyFailure_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
                                       0);
@@ -931,15 +877,16 @@
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback({}, PairFailure::kPasskeyPairingCharacteristicWrite);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPasskeyPairingCharacteristicWrite);
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicResultMetric,
@@ -958,23 +905,23 @@
 TEST_F(FastPairPairerImplTest,
        ParseDecryptedPasskeyIncorrectMessageType_Initial_SeekersPasskey) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForIncorrectMessageType(
       FastPairMessageType::kSeekersPasskey);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kIncorrectPasskeyResponseType);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -990,23 +937,23 @@
     FastPairPairerImplTest,
     ParseDecryptedPasskeyIncorrectMessageType_Initial_KeyBasedPairingRequest) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForIncorrectMessageType(
       FastPairMessageType::kKeyBasedPairingRequest);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kIncorrectPasskeyResponseType);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1022,23 +969,23 @@
     FastPairPairerImplTest,
     ParseDecryptedPasskeyIncorrectMessageType_Initial_KeyBasedPairingResponse) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForIncorrectMessageType(
       FastPairMessageType::kKeyBasedPairingResponse);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kIncorrectPasskeyResponseType);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1052,22 +999,22 @@
 
 TEST_F(FastPairPairerImplTest, ParseDecryptedPasskeyNoPasskey) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForNoPasskey();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPasskeyDecryptFailure);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1082,23 +1029,23 @@
 TEST_F(FastPairPairerImplTest,
        ParseDecryptedPasskeyIncorrectMessageType_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForIncorrectMessageType(
       FastPairMessageType::kKeyBasedPairingResponse);
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kIncorrectPasskeyResponseType);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1112,22 +1059,22 @@
 
 TEST_F(FastPairPairerImplTest, ParseDecryptedPasskeyMismatch_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForPasskeyMismatch();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPasskeyMismatch);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1142,22 +1089,22 @@
 
 TEST_F(FastPairPairerImplTest, ParseDecryptedPasskeyMismatch_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForPasskeyMismatch();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPasskeyMismatch);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
@@ -1172,18 +1119,19 @@
 
 TEST_F(FastPairPairerImplTest, PairedDeviceLost_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForSuccess();
 
@@ -1191,7 +1139,6 @@
   // Passkey exchange.
   SetGetDeviceNullptr();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPairingDeviceLost);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 1);
@@ -1207,18 +1154,19 @@
 
 TEST_F(FastPairPairerImplTest, PairedDeviceLost_Subsequent) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForSuccess();
 
@@ -1226,7 +1174,6 @@
   // Passkey exchange.
   SetGetDeviceNullptr();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), PairFailure::kPairingDeviceLost);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 1);
@@ -1242,24 +1189,24 @@
 
 TEST_F(FastPairPairerImplTest, PairSuccess_Initial) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConfirmPasskeyAskTime, 0);
   histogram_tester().ExpectTotalCount(kConfirmPasskeyConfirmTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
@@ -1281,16 +1228,17 @@
 
 TEST_F(FastPairPairerImplTest, BleDeviceLostMidPair) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   SetDecryptPasskeyForSuccess();
 
@@ -1298,7 +1246,6 @@
   EraseHandshake();
 
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(PairFailure::kBleDeviceLostMidPair, GetPairFailure());
   EXPECT_FALSE(IsDevicePaired());
@@ -1306,24 +1253,24 @@
 
 TEST_F(FastPairPairerImplTest, PairSuccess_Initial_FactoryCreate) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConfirmPasskeyAskTime, 0);
   histogram_tester().ExpectTotalCount(kConfirmPasskeyConfirmTime, 0);
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptTime, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairerAsFactory();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairerAsFactory();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
@@ -1344,7 +1291,6 @@
       /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConfirmPasskeyAskTime, 0);
   histogram_tester().ExpectTotalCount(kConfirmPasskeyConfirmTime, 0);
@@ -1352,17 +1298,18 @@
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -1392,7 +1339,6 @@
                              features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConfirmPasskeyAskTime, 0);
   histogram_tester().ExpectTotalCount(kConfirmPasskeyConfirmTime, 0);
@@ -1400,17 +1346,18 @@
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -1431,7 +1378,6 @@
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kConfirmPasskeyAskTime, 0);
   histogram_tester().ExpectTotalCount(kConfirmPasskeyConfirmTime, 0);
@@ -1439,17 +1385,18 @@
   histogram_tester().ExpectTotalCount(kPasskeyCharacteristicDecryptResult, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -1471,29 +1418,30 @@
       /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_CALL(pairing_procedure_complete_, Run);
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1513,29 +1461,30 @@
                              features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_CALL(pairing_procedure_complete_, Run);
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1554,29 +1503,30 @@
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_CALL(pairing_procedure_complete_, Run);
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1589,24 +1539,24 @@
 
 TEST_F(FastPairPairerImplTest, WriteAccountKey_Initial_GuestLoggedIn) {
   Login(user_manager::UserType::USER_TYPE_GUEST);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -1623,24 +1573,24 @@
 
 TEST_F(FastPairPairerImplTest, WriteAccountKey_Initial_KioskAppLoggedIn) {
   Login(user_manager::UserType::USER_TYPE_KIOSK_APP);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -1657,17 +1607,18 @@
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
@@ -1684,17 +1635,18 @@
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
@@ -1713,26 +1665,27 @@
       /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1754,26 +1707,27 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1794,26 +1748,27 @@
   feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kFastPairSavedDevices},
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+
   // Ensure that the account key is not written to the peripheral until the
   // peripheral is successfully paired.
   EXPECT_FALSE(IsAccountKeySavedToFootprints());
@@ -1835,18 +1790,19 @@
       /*enabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn},
       /*disabled_features=*/{});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
@@ -1862,18 +1818,19 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
@@ -1888,18 +1845,19 @@
   feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kFastPairSavedDevices},
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
@@ -1915,7 +1873,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -1941,7 +1898,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -1967,7 +1923,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -1993,7 +1948,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2019,7 +1973,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2045,7 +1998,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2071,7 +2023,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2097,7 +2048,6 @@
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2122,7 +2072,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   CreateDevice(DeviceFastPairVersion::kHigherThanV1);
   RunWritePasskeyCallback(kResponseBytes);
@@ -2142,7 +2091,6 @@
 
 TEST_F(FastPairPairerImplTest, FastPairVersionOne_DevicePaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateDevice(DeviceFastPairVersion::kV1);
   EXPECT_EQ(GetSystemTrayClient()->show_bluetooth_pairing_dialog_count(), 1);
@@ -2158,7 +2106,6 @@
 
 TEST_F(FastPairPairerImplTest, FastPairVersionOne_DeviceUnpaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
 
   CreateDevice(DeviceFastPairVersion::kV1);
   EXPECT_EQ(GetSystemTrayClient()->show_bluetooth_pairing_dialog_count(), 1);
@@ -2181,7 +2128,6 @@
       /*enabled_features=*/{features::kFastPairSavedDevices,
                             features::kFastPairSavedDevicesStrictOptIn},
       /*disabled_features=*/{});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2192,7 +2138,6 @@
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   RunWritePasskeyCallback(kResponseBytes);
-  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_OptedIn_FlagDisabled) {
@@ -2204,13 +2149,11 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateDevice(DeviceFastPairVersion::kHigherThanV1);
   RunWritePasskeyCallback(kResponseBytes);
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run).Times(1);
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
   adapter_->DevicePairedChanged(fake_bluetooth_device_ptr_, true);
@@ -2235,7 +2178,6 @@
   feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kFastPairSavedDevices},
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2262,7 +2204,6 @@
       /*enabled_features=*/{},
       /*disabled_features=*/{features::kFastPairSavedDevices,
                              features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2288,7 +2229,6 @@
   feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kFastPairSavedDevices},
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2315,7 +2255,6 @@
       /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_UNKNOWN);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2327,7 +2266,6 @@
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   RunWritePasskeyCallback(kResponseBytes);
-  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_StatusUnknown_FlagDisabled) {
@@ -2339,7 +2277,6 @@
                              features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_UNKNOWN);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
@@ -2365,26 +2302,25 @@
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_UNKNOWN);
-  base::RunLoop().RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run).Times(1);
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
   adapter_->DevicePairedChanged(fake_bluetooth_device_ptr_, true);
@@ -2417,7 +2353,6 @@
   histogram_tester().ExpectBucketCount(
       kSavedDeviceUpdateOptInStatusInitialResult,
       /*success=*/false, 0);
-  base::RunLoop().RunUntilIdle();
 
   // Pair the device via Initial Pairing protocol.
   histogram_tester().ExpectTotalCount(
@@ -2425,17 +2360,18 @@
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   SetPublicKey();
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_TRUE(IsDevicePaired());
@@ -2472,23 +2408,24 @@
   feature_list.InitWithFeatures(
       /*enabled_features=*/{features::kFastPairSavedDevices},
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
-  base::RunLoop().RunUntilIdle();
 
   // Retroactive pair
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWriteAccountKeyCallback();
 
-  // Expect that the user is now opted in
+  // Expect that the user is now opted in.
   EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_IN,
             fast_pair_repository_.GetOptInStatus());
   histogram_tester().ExpectBucketCount(
@@ -2519,17 +2456,18 @@
   // Subsequent pair
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
-  base::RunLoop().RunUntilIdle();
+  CreatePairer();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
   EXPECT_CALL(paired_callback_, Run);
   SetDecryptPasskeyForSuccess();
   NotifyConfirmPasskey();
-  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWritePasskeyCallback(kResponseBytes);
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
@@ -2537,7 +2475,7 @@
   EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
   adapter_->DevicePairedChanged(fake_bluetooth_device_ptr_, true);
 
-  // Expect that the user is opted in now
+  // Expect that the user is opted in now.
   EXPECT_EQ(nearby::fastpair::OptInStatus::STATUS_OPTED_IN,
             fast_pair_repository_.GetOptInStatus());
   histogram_tester().ExpectBucketCount(
@@ -2557,9 +2495,10 @@
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
-  SetPairTimeout();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
+  SetPairTimeout();
+  CreatePairer();
   task_environment()->FastForwardBy(kCreateBondTimeout);
   EXPECT_EQ(GetPairFailure(), PairFailure::kCreateBondTimeout);
 }
@@ -2578,9 +2517,10 @@
   // This call mocks the scenario in which |adapter_| does not know |device_|'s
   // address.
   SetGetDeviceNullptr();
-  CreatePairer();
-  SetConnectDeviceTimeout();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
+  SetConnectDeviceTimeout();
+  CreatePairer();
   task_environment()->FastForwardBy(kCreateBondTimeout);
   EXPECT_EQ(GetPairFailure(), PairFailure::kCreateBondTimeout);
 }
@@ -2595,11 +2535,14 @@
       /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
+
   // When pairing starts, if the classic address can't be resolved to
-  // a device then we pair via address.
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
+  CreatePairer();
   EXPECT_CALL(pairing_procedure_complete_, Run);
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
@@ -2624,8 +2567,9 @@
 
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
+  CreatePairer();
   task_environment()->FastForwardBy(kConfirmPasskeyTimeout);
   EXPECT_EQ(GetPairFailure(), PairFailure::kConfirmPasskeyTimeout);
 }
@@ -2637,68 +2581,16 @@
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
 
-  // This call mocks the scenario in which |adapter_| does not know |device_|'s
-  // address.
+  // When pairing starts, if the classic address can't be resolved to
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
   SetGetDeviceNullptr();
-  CreatePairer();
+  AddConnectedHandshake();
   fake_fast_pair_handshake_->InvokeCallback();
+  CreatePairer();
   task_environment()->FastForwardBy(kConfirmPasskeyTimeout);
   EXPECT_EQ(GetPairFailure(), PairFailure::kConfirmPasskeyTimeout);
 }
 
-TEST_F(FastPairPairerImplTest, HandshakeReused_Initial) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairInitial);
-
-  // Simulate Handshake already created before attempt
-  SetReuseHandshake();
-  SetGetDeviceNullptr();
-  CreatePairer();
-
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                kInitializePairingProcessInitial,
-                FastPairInitializePairingProcessEvent::kHandshakeReused),
-            1);
-}
-
-TEST_F(FastPairPairerImplTest, HandshakeReused_Subsequent) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairSubsequent);
-
-  // Simulate Handshake already created before attempt
-  SetReuseHandshake();
-  SetGetDeviceNullptr();
-  CreatePairer();
-
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                kInitializePairingProcessSubsequent,
-                FastPairInitializePairingProcessEvent::kHandshakeReused),
-            1);
-}
-
-TEST_F(FastPairPairerImplTest, HandshakeReused_Retroactive) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  base::RunLoop().RunUntilIdle();
-
-  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
-                   /*protocol=*/Protocol::kFastPairRetroactive);
-
-  // Simulate Handshake already created before attempt
-  SetReuseHandshake();
-  SetGetDeviceNullptr();
-  CreatePairer();
-
-  EXPECT_EQ(histogram_tester().GetBucketCount(
-                kInitializePairingProcessRetroactive,
-                FastPairInitializePairingProcessEvent::kHandshakeReused),
-            1);
-}
-
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.cc b/ash/quick_pair/pairing/pairer_broker_impl.cc
index 50f698e..b22802f 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl.cc
@@ -12,6 +12,7 @@
 #include "ash/quick_pair/common/logging.h"
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake.h"
 #include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake_lookup.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h"
@@ -62,6 +63,8 @@
     case Protocol::kFastPairInitial:
     case Protocol::kFastPairRetroactive:
     case Protocol::kFastPairSubsequent:
+      did_handshake_previously_complete_successfully_map_.insert_or_assign(
+          device->ble_address, false);
       PairFastPairDevice(std::move(device));
       break;
   }
@@ -74,6 +77,8 @@
   pair_failure_counts_.erase(device->ble_address);
   fast_pair_pairers_.erase(device->ble_address);
   FastPairHandshakeLookup::GetInstance()->Erase(device);
+  did_handshake_previously_complete_successfully_map_.insert_or_assign(
+      device->ble_address, false);
 }
 
 bool PairerBrokerImpl::IsPairing() {
@@ -95,6 +100,88 @@
     return;
   }
 
+  // If this is a v1 pairing, we don't have to make a handshake before bonding
+  // because we will pass off pairing to the classic Bluetooth pairing dialog in
+  // 'FastPairPairer', so skip straight to 'StartBondingAttempt'.
+  if (device->version().value() == DeviceFastPairVersion::kV1) {
+    StartBondingAttempt(device);
+    return;
+  }
+
+  // Otherwise, try to create a handshake.
+  CreateHandshake(std::move(device));
+}
+
+void PairerBrokerImpl::CreateHandshake(scoped_refptr<Device> device) {
+  // TODO(b/259429032): Add 3 retries for this handshake.
+  auto* fast_pair_handshake =
+      FastPairHandshakeLookup::GetInstance()->Get(device);
+  if (fast_pair_handshake && fast_pair_handshake->completed_successfully()) {
+    QP_LOG(INFO) << __func__
+                 << ": Reusing existing handshake for pair attempt.";
+    RecordFastPairInitializePairingProcessEvent(
+        *device, FastPairInitializePairingProcessEvent::kHandshakeReused);
+    StartBondingAttempt(device);
+    return;
+  }
+
+  QP_LOG(INFO) << __func__ << ": Creating new handshake for pair attempt.";
+  FastPairHandshakeLookup::GetInstance()->Create(
+      adapter_, device,
+      base::BindOnce(&PairerBrokerImpl::OnHandshakeComplete,
+                     weak_pointer_factory_.GetWeakPtr()));
+}
+
+void PairerBrokerImpl::OnHandshakeComplete(
+    scoped_refptr<Device> device,
+    absl::optional<PairFailure> failure) {
+  if (failure.has_value()) {
+    QP_LOG(WARNING) << __func__ << ": Handshake failed with " << device
+                    << " because: " << failure.value();
+    OnHandshakeFailure(device, failure.value());
+    return;
+  }
+
+  // During handshake, the device address can be set to null.
+  if (!device->classic_address()) {
+    QP_LOG(WARNING) << __func__ << ": Device lost during handshake.";
+    OnHandshakeFailure(device, PairFailure::kPairingDeviceLost);
+    return;
+  }
+
+  if (!did_handshake_previously_complete_successfully_map_[device
+                                                               ->ble_address]) {
+    // Currently the only observer is |QuickPairMetricsLogger|. To keep metrics
+    // consistent we should only call the observer the first time a handshake
+    // completes. If another observer is added, this logic should be
+    // reevaluated.
+    int num_observers = 0;
+    for (auto& observer : observers_) {
+      num_observers++;
+      observer.OnHandshakeComplete(device);
+    }
+    DCHECK(num_observers == 1);
+    did_handshake_previously_complete_successfully_map_.insert_or_assign(
+        device->ble_address, true);
+  }
+
+  StartBondingAttempt(device);
+}
+
+void PairerBrokerImpl::OnHandshakeFailure(scoped_refptr<Device> device,
+                                          PairFailure failure) {
+  QP_LOG(INFO) << __func__
+               << ": Handshake failed to be created. Notifying observers.";
+  RecordInitializationFailureReason(*device, failure);
+  for (auto& observer : observers_) {
+    observer.OnPairFailure(device, failure);
+  }
+
+  FastPairHandshakeLookup::GetInstance()->Erase(device);
+  return;
+}
+
+void PairerBrokerImpl::StartBondingAttempt(scoped_refptr<Device> device) {
   if (!base::Contains(pair_failure_counts_, device->ble_address))
     pair_failure_counts_[device->ble_address] = 0;
 
@@ -107,11 +194,9 @@
   DCHECK(adapter_);
   fast_pair_pairers_[device->ble_address] = FastPairPairerImpl::Factory::Create(
       adapter_, device,
-      base::BindOnce(&PairerBrokerImpl::OnFastPairHandshakeComplete,
+      base::BindOnce(&PairerBrokerImpl::OnFastPairDeviceBonded,
                      weak_pointer_factory_.GetWeakPtr()),
-      base::BindOnce(&PairerBrokerImpl::OnFastPairDevicePaired,
-                     weak_pointer_factory_.GetWeakPtr()),
-      base::BindOnce(&PairerBrokerImpl::OnFastPairPairingFailure,
+      base::BindOnce(&PairerBrokerImpl::OnFastPairBondingFailure,
                      weak_pointer_factory_.GetWeakPtr()),
       base::BindOnce(&PairerBrokerImpl::OnAccountKeyFailure,
                      weak_pointer_factory_.GetWeakPtr()),
@@ -119,15 +204,7 @@
                      weak_pointer_factory_.GetWeakPtr()));
 }
 
-void PairerBrokerImpl::OnFastPairHandshakeComplete(
-    scoped_refptr<Device> device) {
-  QP_LOG(INFO) << __func__ << ": Device=" << device;
-  for (auto& observer : observers_) {
-    observer.OnHandshakeComplete(device);
-  }
-}
-
-void PairerBrokerImpl::OnFastPairDevicePaired(scoped_refptr<Device> device) {
+void PairerBrokerImpl::OnFastPairDeviceBonded(scoped_refptr<Device> device) {
   QP_LOG(INFO) << __func__ << ": Device=" << device;
 
   for (auto& observer : observers_) {
@@ -139,7 +216,7 @@
   pair_failure_counts_.erase(device->ble_address);
 }
 
-void PairerBrokerImpl::OnFastPairPairingFailure(scoped_refptr<Device> device,
+void PairerBrokerImpl::OnFastPairBondingFailure(scoped_refptr<Device> device,
                                                 PairFailure failure) {
   ++pair_failure_counts_[device->ble_address];
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Failure=" << failure
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.h b/ash/quick_pair/pairing/pairer_broker_impl.h
index f53644d..0a645117 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.h
+++ b/ash/quick_pair/pairing/pairer_broker_impl.h
@@ -49,13 +49,17 @@
 
  private:
   void PairFastPairDevice(scoped_refptr<Device> device);
-  void OnFastPairHandshakeComplete(scoped_refptr<Device> device);
-  void OnFastPairDevicePaired(scoped_refptr<Device> device);
-  void OnFastPairPairingFailure(scoped_refptr<Device> device,
+  void OnFastPairDeviceBonded(scoped_refptr<Device> device);
+  void OnFastPairBondingFailure(scoped_refptr<Device> device,
                                 PairFailure failure);
   void OnAccountKeyFailure(scoped_refptr<Device> device,
                            AccountKeyFailure failure);
   void OnFastPairProcedureComplete(scoped_refptr<Device> device);
+  void CreateHandshake(scoped_refptr<Device> device);
+  void OnHandshakeComplete(scoped_refptr<Device> device,
+                           absl::optional<PairFailure> failure);
+  void OnHandshakeFailure(scoped_refptr<Device> device, PairFailure failure);
+  void StartBondingAttempt(scoped_refptr<Device> device);
 
   // Internal method called by BluetoothAdapterFactory to provide the adapter
   // object.
@@ -63,9 +67,13 @@
 
   void EraseHandshakeAndFromPairers(scoped_refptr<Device> device);
 
+  // The key for all the following maps is a device BLE address.
   base::flat_map<std::string, std::unique_ptr<FastPairPairer>>
       fast_pair_pairers_;
   base::flat_map<std::string, int> pair_failure_counts_;
+  base::flat_map<std::string, bool>
+      did_handshake_previously_complete_successfully_map_;
+
   scoped_refptr<device::BluetoothAdapter> adapter_;
   std::unique_ptr<FastPairUnpairHandler> fast_pair_unpair_handler_;
   base::ObserverList<Observer> observers_;
diff --git a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
index 28f496d8..849c4e7 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
@@ -3,12 +3,19 @@
 // found in the LICENSE file.
 
 #include "ash/quick_pair/pairing/pairer_broker_impl.h"
-
 #include "ash/quick_pair/common/account_key_failure.h"
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.h"
+#include "ash/quick_pair/fast_pair_handshake/fake_fast_pair_gatt_service_client.h"
+#include "ash/quick_pair/fast_pair_handshake/fake_fast_pair_handshake.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.h"
+#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake_lookup.h"
 #include "ash/quick_pair/feature_status_tracker/fake_bluetooth_adapter.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h"
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h"
@@ -40,6 +47,12 @@
     "FastPair.SubsequentPairing.Initialization";
 constexpr char kInitializePairingProcessRetroactive[] =
     "FastPair.RetroactivePairing.Initialization";
+constexpr char kInitializePairingProcessFailureReasonInitial[] =
+    "FastPair.InitialPairing.Initialization.FailureReason";
+constexpr char kInitializePairingProcessFailureReasonSubsequent[] =
+    "FastPair.SubsequentPairing.Initialization.FailureReason";
+constexpr char kInitializePairingProcessFailureReasonRetroactive[] =
+    "FastPair.RetroactivePairing.Initialization.FailureReason";
 
 constexpr char kProtocolPairingStepInitial[] =
     "FastPair.InitialPairing.Pairing";
@@ -52,8 +65,6 @@
       scoped_refptr<device::BluetoothAdapter> adapter,
       scoped_refptr<ash::quick_pair::Device> device,
       base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
-          handshake_complete_callback,
-      base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
           paired_callback,
       base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>,
                               ash::quick_pair::PairFailure)>
@@ -65,7 +76,6 @@
           pairing_procedure_complete)
       : adapter_(adapter),
         device_(device),
-        handshake_complete_callback_(std::move(handshake_complete_callback)),
         paired_callback_(std::move(paired_callback)),
         pair_failed_callback_(std::move(pair_failed_callback)),
         account_key_failure_callback_(std::move(account_key_failure_callback)),
@@ -73,11 +83,6 @@
 
   ~FakeFastPairPairer() override = default;
 
-  void TriggerHandshakeCompleteCallback() {
-    EXPECT_TRUE(handshake_complete_callback_);
-    std::move(handshake_complete_callback_).Run(device_);
-  }
-
   void TriggerPairedCallback() {
     EXPECT_TRUE(paired_callback_);
     std::move(paired_callback_).Run(device_);
@@ -103,8 +108,6 @@
   scoped_refptr<device::BluetoothAdapter> adapter_;
   scoped_refptr<ash::quick_pair::Device> device_;
   base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
-      handshake_complete_callback_;
-  base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
       paired_callback_;
   base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>,
                           ash::quick_pair::PairFailure)>
@@ -123,8 +126,6 @@
       scoped_refptr<device::BluetoothAdapter> adapter,
       scoped_refptr<ash::quick_pair::Device> device,
       base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
-          handshake_complete_callback,
-      base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
           paired_callback,
       base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>,
                               ash::quick_pair::PairFailure)>
@@ -135,8 +136,7 @@
       base::OnceCallback<void(scoped_refptr<ash::quick_pair::Device>)>
           pairing_procedure_complete) override {
     auto fake_fast_pair_pairer = std::make_unique<FakeFastPairPairer>(
-        std::move(adapter), std::move(device),
-        std::move(handshake_complete_callback), std::move(paired_callback),
+        std::move(adapter), std::move(device), std::move(paired_callback),
         std::move(pair_failed_callback),
         std::move(account_key_failure_callback),
         std::move(pairing_procedure_complete));
@@ -152,6 +152,35 @@
   FakeFastPairPairer* fake_fast_pair_pairer_ = nullptr;
 };
 
+class FakeFastPairGattServiceClientImplFactory
+    : public ash::quick_pair::FastPairGattServiceClientImpl::Factory {
+ public:
+  ~FakeFastPairGattServiceClientImplFactory() override = default;
+
+  ash::quick_pair::FakeFastPairGattServiceClient*
+  fake_fast_pair_gatt_service_client() {
+    return fake_fast_pair_gatt_service_client_;
+  }
+
+ private:
+  // FastPairGattServiceClientImpl::Factory:
+  std::unique_ptr<ash::quick_pair::FastPairGattServiceClient> CreateInstance(
+      device::BluetoothDevice* device,
+      scoped_refptr<device::BluetoothAdapter> adapter,
+      base::OnceCallback<void(absl::optional<ash::quick_pair::PairFailure>)>
+          on_initialized_callback) override {
+    auto fake_fast_pair_gatt_service_client =
+        std::make_unique<ash::quick_pair::FakeFastPairGattServiceClient>(
+            device, adapter, std::move(on_initialized_callback));
+    fake_fast_pair_gatt_service_client_ =
+        fake_fast_pair_gatt_service_client.get();
+    return fake_fast_pair_gatt_service_client;
+  }
+
+  ash::quick_pair::FakeFastPairGattServiceClient*
+      fake_fast_pair_gatt_service_client_ = nullptr;
+};
+
 }  // namespace
 
 namespace ash {
@@ -173,8 +202,60 @@
     FastPairPairerImpl::Factory::SetFactoryForTesting(
         fast_pair_pairer_factory_.get());
 
+    FastPairGattServiceClientImpl::Factory::SetFactoryForTesting(
+        &fast_pair_gatt_service_factory_);
+    FastPairHandshakeLookup::SetCreateFunctionForTesting(base::BindRepeating(
+        &PairerBrokerImplTest::CreateHandshake, base::Unretained(this)));
+
     pairer_broker_ = std::make_unique<PairerBrokerImpl>();
     pairer_broker_->AddObserver(this);
+
+    gatt_service_client_ = FastPairGattServiceClientImpl::Factory::Create(
+        mock_bluetooth_device_ptr_, adapter_.get(), base::DoNothing());
+
+    // We have to pass in a unique_ptr when we create a Handshake, however
+    // we also want to be able to set fake responses on the encryptor. Thus
+    // we maintain 2 pointers. We won't touch fake_fast_pair_data_encryptor_
+    // aside from CreateHandshake.
+    fake_fast_pair_data_encryptor_ =
+        std::make_unique<FakeFastPairDataEncryptor>();
+  }
+
+  void CreateMockDevice(DeviceFastPairVersion version, Protocol protocol) {
+    device_ = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
+                                           protocol);
+    device_->set_classic_address(kBluetoothCanonicalizedAddress);
+
+    device_->set_version(version);
+
+    // Add a matching mock device to the bluetooth adapter with the
+    // same address to mock the relationship between Device and
+    // device::BluetoothDevice.
+    mock_bluetooth_device_ =
+        std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
+            adapter_.get(), /*bluetooth_class=*/0, kDeviceName,
+            kBluetoothCanonicalizedAddress,
+            /*initially_paired=*/true, /*connected=*/false);
+    mock_bluetooth_device_ptr_ = mock_bluetooth_device_.get();
+    adapter_->AddMockDevice(std::move(mock_bluetooth_device_));
+  }
+
+  void EraseHandshake() {
+    FastPairHandshakeLookup::GetInstance()->Erase(device_);
+  }
+
+  std::unique_ptr<FastPairHandshake> CreateHandshake(
+      scoped_refptr<Device> device,
+      FastPairHandshake::OnCompleteCallback callback) {
+    // This is the only place where fake_fast_pair_data_encryptor_ is used. We
+    // assume that CreateHandshake is only called once.
+    auto fake = std::make_unique<FakeFastPairHandshake>(
+        adapter_, device, std::move(callback),
+        std::move(fake_fast_pair_data_encryptor_),
+        std::move(gatt_service_client_));
+
+    fake_fast_pair_handshake_ = fake.get();
+    return fake;
   }
 
   void TearDown() override {
@@ -223,13 +304,23 @@
   std::unique_ptr<FakeFastPairPairerFactory> fast_pair_pairer_factory_;
 
   std::unique_ptr<PairerBroker> pairer_broker_;
+  FakeFastPairHandshake* fake_fast_pair_handshake_ = nullptr;
+  std::unique_ptr<FastPairGattServiceClient> gatt_service_client_;
+  FakeFastPairGattServiceClientImplFactory fast_pair_gatt_service_factory_;
+  std::unique_ptr<FakeFastPairDataEncryptor> fake_fast_pair_data_encryptor_;
+  std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+      mock_bluetooth_device_ = nullptr;
+  scoped_refptr<Device> device_;
 };
 
 TEST_F(PairerBrokerImplTest, PairDevice_Initial) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
-  pairer_broker_->PairDevice(device);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
+
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
@@ -245,17 +336,14 @@
 
 TEST_F(PairerBrokerImplTest, PairDevice_Subsequent) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairSubsequent);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairing_started_);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
-  fast_pair_pairer_factory_->fake_fast_pair_pairer()
-      ->TriggerHandshakeCompleteCallback();
-  EXPECT_TRUE(handshake_complete_);
-
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
 
   EXPECT_TRUE(pairer_broker_->IsPairing());
@@ -270,10 +358,11 @@
 
 TEST_F(PairerBrokerImplTest, PairDevice_Retroactive) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairRetroactive);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
@@ -289,11 +378,11 @@
 
 TEST_F(PairerBrokerImplTest, AlreadyPairingDevice_Initial) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
-
-  pairer_broker_->PairDevice(device);
-  pairer_broker_->PairDevice(device);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
+  pairer_broker_->PairDevice(device_);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
@@ -309,12 +398,13 @@
 
 TEST_F(PairerBrokerImplTest, AlreadyPairingDevice_Subsequent) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairSubsequent);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
-  pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
+  pairer_broker_->PairDevice(device_);
+
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
@@ -330,12 +420,13 @@
 
 TEST_F(PairerBrokerImplTest, AlreadyPairingDevice_Retroactive) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairRetroactive);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
-  pairer_broker_->PairDevice(device);
-  base::RunLoop().RunUntilIdle();
+  pairer_broker_->PairDevice(device_);
+
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
@@ -352,22 +443,11 @@
 
 TEST_F(PairerBrokerImplTest, PairAfterCancelPairing) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
-  device->set_classic_address(kBluetoothCanonicalizedAddress);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
 
-  // Add a matching mock device to the bluetooth adapter with the
-  // same address to mock the relationship between Device and
-  // device::BluetoothDevice.
-  std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
-      mock_bluetooth_device_ =
-          std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
-              adapter_.get(), 0, kDeviceName, kBluetoothCanonicalizedAddress,
-              true, false);
-  mock_bluetooth_device_ptr_ = mock_bluetooth_device_.get();
-  adapter_->AddMockDevice(std::move(mock_bluetooth_device_));
-
-  pairer_broker_->PairDevice(device);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
   EXPECT_TRUE(pairer_broker_->IsPairing());
   EXPECT_CALL(*mock_bluetooth_device_ptr_, IsPaired())
       .WillOnce(testing::Return(false));
@@ -390,10 +470,11 @@
 
 TEST_F(PairerBrokerImplTest, PairDeviceFailureMax_Initial) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -414,10 +495,11 @@
 
 TEST_F(PairerBrokerImplTest, PairDeviceFailureMax_Subsequent) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairSubsequent);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
@@ -437,10 +519,11 @@
 
 TEST_F(PairerBrokerImplTest, PairDeviceFailureMax_Retroactive) {
   histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairRetroactive);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
       ->TriggerPairFailureCallback(
@@ -458,10 +541,11 @@
 }
 
 TEST_F(PairerBrokerImplTest, AccountKeyFailure_Initial) {
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -473,10 +557,10 @@
 }
 
 TEST_F(PairerBrokerImplTest, AccountKeyFailure_Subsequent) {
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairSubsequent);
-
-  pairer_broker_->PairDevice(device);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -488,10 +572,11 @@
 }
 
 TEST_F(PairerBrokerImplTest, AccountKeyFailure_Retroactive) {
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairRetroactive);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   fast_pair_pairer_factory_->fake_fast_pair_pairer()
@@ -503,10 +588,11 @@
 }
 
 TEST_F(PairerBrokerImplTest, StopPairing) {
-  auto device = base::MakeRefCounted<Device>(kValidModelId, kTestDeviceAddress,
-                                             Protocol::kFastPairInitial);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
 
-  pairer_broker_->PairDevice(device);
   EXPECT_TRUE(pairer_broker_->IsPairing());
 
   // Stop Pairing mid pair.
@@ -519,5 +605,144 @@
   EXPECT_FALSE(pairer_broker_->IsPairing());
 }
 
+TEST_F(PairerBrokerImplTest, ReuseHandshake_Initial) {
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+
+  // Create a Handshake that to mimic a Handshake already existing.
+  FastPairHandshakeLookup::GetInstance()->Create(adapter_, device_,
+                                                 base::DoNothing());
+  fake_fast_pair_handshake_->set_completed_successfully(
+      /*completed_successfully=*/true);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
+
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+  EXPECT_EQ(device_paired_count_, 1);
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessInitial,
+                FastPairInitializePairingProcessEvent::kHandshakeReused),
+            1);
+}
+
+TEST_F(PairerBrokerImplTest, ReuseHandshake_Subsequent) {
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+
+  // Create a Handshake that to mimic a Handshake already existing.
+  FastPairHandshakeLookup::GetInstance()->Create(adapter_, device_,
+                                                 base::DoNothing());
+  fake_fast_pair_handshake_->set_completed_successfully(
+      /*completed_successfully=*/true);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
+
+  EXPECT_TRUE(pairing_started_);
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+  EXPECT_EQ(device_paired_count_, 1);
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+  EXPECT_TRUE(device_pair_complete_);
+
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessSubsequent,
+                FastPairInitializePairingProcessEvent::kHandshakeReused),
+            1);
+}
+
+TEST_F(PairerBrokerImplTest, ReuseHandshake_Retroactive) {
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 0);
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+
+  // Create a Handshake that to mimic a Handshake already existing.
+  FastPairHandshakeLookup::GetInstance()->Create(adapter_, device_,
+                                                 base::DoNothing());
+  fake_fast_pair_handshake_->set_completed_successfully(
+      /*completed_successfully=*/true);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback();
+
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+  EXPECT_EQ(device_paired_count_, 1);
+  histogram_tester_.ExpectTotalCount(kFastPairRetryCountMetricName, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessRetroactive,
+                FastPairInitializePairingProcessEvent::kHandshakeReused),
+            1);
+}
+
+TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Initial) {
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+
+  EXPECT_EQ(device_paired_count_, 0);
+  EXPECT_EQ(pair_failure_count_, 1);
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessFailureReasonInitial,
+                PairFailure::kCreateGattConnection),
+            1);
+}
+
+TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Subsequent) {
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+
+  EXPECT_EQ(device_paired_count_, 0);
+  EXPECT_EQ(pair_failure_count_, 1);
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessFailureReasonSubsequent,
+                PairFailure::kCreateGattConnection),
+            1);
+}
+
+TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Retroactive) {
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+
+  EXPECT_EQ(device_paired_count_, 0);
+  EXPECT_EQ(pair_failure_count_, 1);
+  EXPECT_EQ(histogram_tester_.GetBucketCount(
+                kInitializePairingProcessFailureReasonRetroactive,
+                PairFailure::kCreateGattConnection),
+            1);
+}
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
index 5c53335..1f8ea07 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
@@ -42,6 +42,16 @@
   }
 }
 
+// Enforce a 60 second timeout to discover a device for the retroactive pairing
+// scenario to align with Android implementation and adhere to the Fast Pair
+// spec where providers can only allow account keys to be written within the
+// 60 seconds following classic Bluetooth pairing:
+// https://developers.google.com/nearby/fast-pair/specifications/extensions/retroactiveacctkey#RetroactivelyWritingAccountKey
+// The device is expected to enforce this requirement, however as mentioned
+// above, to align with Android, ChromeOS will include consideration for the
+// 60 seconds expected to retroactively write the account key.
+constexpr base::TimeDelta kRetroactiveDevicePairingTimeout = base::Seconds(60);
+
 }  // namespace
 
 namespace ash {
@@ -173,6 +183,7 @@
   // |potential_retroactive_addresses_|.
   const std::string& classic_address = device->GetAddress();
   potential_retroactive_addresses_.insert(classic_address);
+  AddDevicePairingInformation(classic_address);
 
   // In order to confirm that this device is a retroactive pairing, we need to
   // first check if it has already been saved to the user's account. If it has
@@ -220,11 +231,29 @@
   GetModelIdAndAddressFromMessageStream(device_address, message_stream);
 }
 
+void RetroactivePairingDetectorImpl::AddDevicePairingInformation(
+    const std::string& device_address) {
+  QP_LOG(VERBOSE) << __func__;
+  DCHECK(device_pairing_information_.find(device_address) ==
+         device_pairing_information_.end());
+  RetroactivePairingInformation info;
+  device_pairing_information_[device_address] = info;
+  device_pairing_information_[device_address].expiry_timestamp =
+      base::Time::Now() + kRetroactiveDevicePairingTimeout;
+
+  // Anytime |device_pairing_information_| is updated, parse list to remove
+  // expired devices.
+  RemoveExpiredDevicesFromStoredDeviceData();
+}
+
 void RetroactivePairingDetectorImpl::GetModelIdAndAddressFromMessageStream(
     const std::string& device_address,
     MessageStream* message_stream) {
   DCHECK(message_stream);
-  DCHECK(device_pairing_information_.find(device_address) ==
+
+  // The device at |device_address| is expected to be added in
+  // `AddDevicePairingInformation` once discovered.
+  DCHECK(device_pairing_information_.find(device_address) !=
          device_pairing_information_.end());
 
   // If the MessageStream is immediately available and |DevicePairedChanged|
@@ -234,9 +263,6 @@
   if (!base::Contains(potential_retroactive_addresses_, device_address))
     return;
 
-  RetroactivePairingInformation info;
-  device_pairing_information_[device_address] = info;
-
   // Iterate over messages for ble address and model id, which is what we
   // need for retroactive pairing.
   for (auto& message : message_stream->messages()) {
@@ -270,11 +296,33 @@
     return;
   }
 
+  // At this point, we have both the model id and BLE address for the device,
+  // but we check if it has reached its expiry timeout. If so, we do not
+  // notify of the scenario being detected. `CheckAndRemoveIfDeviceExpired`
+  // will remove corresponding device information if it has expired.
+  if (CheckAndRemoveIfDeviceExpired(device_address)) {
+    return;
+  }
+
   NotifyDeviceFound(device_pairing_information_[device_address].model_id,
                     device_pairing_information_[device_address].ble_address,
                     device_address);
 }
 
+bool RetroactivePairingDetectorImpl::CheckAndRemoveIfDeviceExpired(
+    const std::string& device_address) {
+  if (base::Time::Now() >=
+      device_pairing_information_[device_address].expiry_timestamp) {
+    QP_LOG(VERBOSE) << __func__ << ": device at " << device_address
+                    << " has exceeded the time allotted for detecting "
+                       "retroactive scenario. Removing device information.";
+    RemoveDeviceInformation(device_address);
+    return true;
+  }
+
+  return false;
+}
+
 void RetroactivePairingDetectorImpl::OnModelIdMessage(
     const std::string& device_address,
     const std::string& model_id) {
@@ -295,15 +343,21 @@
 
 void RetroactivePairingDetectorImpl::CheckPairingInformation(
     const std::string& device_address) {
+  // The device at |device_address| is expected to be added in
+  // `AddDevicePairingInformation` once discovered.
   DCHECK(device_pairing_information_.find(device_address) !=
          device_pairing_information_.end());
 
   // If the MessageStream is immediately available and |DevicePairedChanged|
   // fires before FastPair's |OnDevicePaired|, it might be possible for us to
   // find a false positive for a retroactive pairing scenario which we mitigate
-  // here.
-  if (!base::Contains(potential_retroactive_addresses_, device_address))
+  // here. Also check if the device has expired for detecting scenario, if so
+  // do not continue. `CheckAndRemoveIfDeviceExpired` will remove device
+  // information if it has expired.
+  if (!base::Contains(potential_retroactive_addresses_, device_address) ||
+      CheckAndRemoveIfDeviceExpired(device_address)) {
     return;
+  }
 
   if (device_pairing_information_[device_address].model_id.empty() ||
       device_pairing_information_[device_address].ble_address.empty()) {
@@ -413,6 +467,15 @@
 void RetroactivePairingDetectorImpl::RemoveDeviceInformation(
     const std::string& device_address) {
   QP_LOG(VERBOSE) << __func__ << ": device = " << device_address;
+  RemoveDeviceInformationHelper(device_address);
+
+  // Anytime |device_pairing_information_| is updated, parse list to remove
+  // expired devices.
+  RemoveExpiredDevicesFromStoredDeviceData();
+}
+
+void RetroactivePairingDetectorImpl::RemoveDeviceInformationHelper(
+    const std::string& device_address) {
   potential_retroactive_addresses_.erase(device_address);
   device_pairing_information_.erase(device_address);
 
@@ -420,11 +483,29 @@
   // before the MessageStreams are observed, connected, and/or added to our
   // list here if we get a false positive instance of a potential retroactive
   // pairing device.
-  if (!base::Contains(message_streams_, device_address))
-    return;
+  if (base::Contains(message_streams_, device_address)) {
+    message_streams_[device_address]->RemoveObserver(this);
+    message_streams_.erase(device_address);
+  }
+}
 
-  message_streams_[device_address]->RemoveObserver(this);
-  message_streams_.erase(device_address);
+void RetroactivePairingDetectorImpl::
+    RemoveExpiredDevicesFromStoredDeviceData() {
+  // If the RetroactivePairingDetector never receives the model id or
+  // BLE address from the MessageStream, it will not be removed in
+  // `CheckPairingInformation` if it has exceeded the allotted time for
+  // detecting the scenario (kDetectRetroactiveScenarioTimeout). We clean up
+  // these devices here.
+  for (auto it = device_pairing_information_.begin();
+       it != device_pairing_information_.end(); ++it) {
+    if (base::Time::Now() >= it->second.expiry_timestamp) {
+      const std::string device_address = it->first;
+      QP_LOG(VERBOSE) << __func__ << "Removing device at " << device_address
+                      << "that has exceeded the time allotted for detecting "
+                         "retroactive scenario.";
+      RemoveDeviceInformationHelper(it->first);
+    }
+  }
 }
 
 void RetroactivePairingDetectorImpl::OnPairFailure(scoped_refptr<Device> device,
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
index 67b038a..6187d02 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
@@ -24,6 +24,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -70,6 +71,7 @@
   struct RetroactivePairingInformation {
     std::string model_id;
     std::string ble_address;
+    base::Time expiry_timestamp;
   };
 
   // SessionObserver:
@@ -103,15 +105,29 @@
   // object.
   void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
 
-  // Parses MessageStream messages for model id and ble address, and
-  // notifies observers if they exist.
+  // Parses MessageStream messages for model id and BLE address, and
+  // notifies observers if they exist within the |expiry_timeout| time period.
   void GetModelIdAndAddressFromMessageStream(const std::string& device_address,
                                              MessageStream* message_stream);
 
-  // Checks |device_pairing_information_| for a ble address and model id
-  // needed for retroactive pairing, and notifies observers.
+  // Checks |device_pairing_information_| for a BLE address and model id
+  // needed for retroactive pairing, and notifies observers if within the
+  // |expiry_timeout| time period.
   void CheckPairingInformation(const std::string& device_address);
 
+  // Adds |device_pairing_information_| entry for a device at |device_address|
+  // with the |expiry_timeout| field. BLE address and model id are added once
+  // the `MessageStream` is connected.
+  void AddDevicePairingInformation(const std::string& device_address);
+
+  // Checks if the |device_pairing_information_| at |device_address| has
+  // exceeded its expiry timeout. If so, removes all references to device in
+  // |device_pairing_information_|, |potential_retroactive_addresses_|, and
+  // removes an observer for a corresponding MessageStream and from
+  // |message_streams_| if a MessageStream exists for the device, and returns
+  // `true`. Otherwise if the device has not expired, returns `false`.
+  bool CheckAndRemoveIfDeviceExpired(const std::string& device_address);
+
   // FastPairRepository::IsDeviceSavedToAccount callback
   void AttemptRetroactivePairing(const std::string& classic_address,
                                  bool is_device_saved_to_account);
@@ -132,6 +148,14 @@
                          const std::string& classic_address);
 
   void RemoveDeviceInformation(const std::string& device_address);
+  void RemoveDeviceInformationHelper(const std::string& device_address);
+
+  // Iterates over |device_pairing_information_| and if a device's
+  // |expiry_timestamp| has been reached, removes devices from
+  // |device_pairing_information_|, |potential_retroactive_addresses_|, and
+  // removes an observer for a corresponding MessageStream and from
+  // |message_streams_| if a MessageStream exists for the device.
+  void RemoveExpiredDevicesFromStoredDeviceData();
 
   // The classic pairing addresses of potential Retroactive Pair supported
   // devices that are found in the adapter. We have to store them and wait for a
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
index 2b2ff53..b8683b1 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
@@ -41,7 +41,9 @@
 
 namespace {
 
+constexpr base::TimeDelta kRetroactiveDevicePairingTimeout = base::Seconds(60);
 constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16";
+constexpr char kTestDeviceAddress2[] = "11:12:13:14:15:17";
 constexpr char kTestBleDeviceName[] = "Test Device Name";
 constexpr char kValidModelId[] = "718c17";
 const std::string kUserEmail = "test@test.test";
@@ -123,6 +125,9 @@
     : public AshTestBase,
       public RetroactivePairingDetector::Observer {
  public:
+  RetroactivePairingDetectorTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
   void SetUp() override {
     AshTestBase::SetUp();
     adapter_ =
@@ -1779,5 +1784,138 @@
   EXPECT_FALSE(retroactive_pair_found_);
 }
 
+// There are two ways to get to `CheckAndRemoveIfDeviceExpired `. One is by
+// `GetModelIdAndAddressFromMessageStream` which is triggered when the
+// MessageStream already has the model id and BLE address messages on
+// connection. The second way is through `CheckPairingInformation` which is
+// triggered when the MessageStream does not have the model id and BLE
+// address on connection, and the model id and BLE address are observed later
+// on.
+TEST_F(RetroactivePairingDetectorTest,
+       DontNotify_ExpiryTimeoutReached_GetModelIdAndAddressFromMessageStream) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  // Pair a device with classic Bluetooth pairing and set the MessageStream
+  // with model id and BLE address bytes to successfully detect the scenario.
+  // At this point, the device is in the `device_pairing_information_` map with
+  // an expiry timestamp.
+  SetMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
+  // that the device's |expiry_timestamp| has been reached. Because the
+  // timeout has been reached, we expect that the retroactive pairing
+  // scenario to not be triggered.
+  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);
+  fake_socket_->TriggerReceiveCallback();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       DontNotify_ExpiryTimeoutReached_CheckPairingInformation) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  // Pair a device with classic Bluetooth pairing and set the MessageStream
+  // with no bytes to successfully detect the scenario.
+  // At this point, the device is in the `device_pairing_information_` map with
+  // an expiry timestamp. Because there are no BLE address bytes or model id
+  // bytes in the connected MessageStream, the RetroactivePairingDetector adds
+  // itself as an observer to wait for these messages.
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+
+  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
+  // that the device's |expiry_timestamp| has been reached. Because the
+  // timeout has been reached, we expect that the retroactive pairing
+  // scenario to not be triggered.
+  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);
+
+  // Set up the socket with the model id bytes and BLE address bytes to
+  // successfully detect the scenario, and trigger the bytes being received
+  // after the timeout to trigger the check in `CheckPairingInformation`
+  // which happens in the overridden observed red functions for
+  // `OnModelIdMessage` and `OnBleAddressUpdateMessage`.
+  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
+
+  // TODO(b/263391358): Refactor `TriggerReceiveCallback` to take a
+  // base::RunLoop parameter and remove `base::RunLoop().RunUntilIdle()`.
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       DontNotify_ExpiryTimeoutReached_DifferentDeviceTriggerRemoval) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  // Simulate a first device being found with classic Bluetooth pairing.
+  // At this point, the device is in the `device_pairing_information_` map with
+  // an expiry timestamp. This device does not have a MessageStream associated,
+  // so `CheckPairingInformation` will never be fired because it doesn't have
+  // a model id or BLE event, and thus alone, its expiry event will never be
+  // triggered.
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  // Fast forward by |kRetroactiveDevicePairingTimeout| in order to simulate
+  // that the device's |expiry_timestamp| has been reached.
+  task_environment()->FastForwardBy(kRetroactiveDevicePairingTimeout);
+
+  // Simulate another device being found for retroactive pairing. This device
+  // will also be added to `device_pairing_information_` map with an expiry
+  // timeout, and its addition will trigger
+  // `RemoveExpiredDevicesFromStoredDeviceData`, which will remove the
+  // first device since it has expired.
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress2);
+
+  // Trigger a Message Stream event for the first device with the model id and
+  // BLE address. Although there is a check in `CheckPairingInformation`,
+  // the device was already removed in
+  // `RemoveExpiredDevicesFromStoredDeviceData`.
+  SetMessageStream(kModelIdBleAddressBytes);
+  fake_socket_->TriggerReceiveCallback();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/style/style_viewer/system_ui_components_grid_view_factories.h b/ash/style/style_viewer/system_ui_components_grid_view_factories.h
index acf6203..e20e5fd6 100644
--- a/ash/style/style_viewer/system_ui_components_grid_view_factories.h
+++ b/ash/style/style_viewer/system_ui_components_grid_view_factories.h
@@ -24,6 +24,7 @@
 std::unique_ptr<SystemUIComponentsGridView>
 CreateRadioButtonGroupInstancesGridView();
 std::unique_ptr<SystemUIComponentsGridView> CreateKnobSwitchInstancesGridView();
+std::unique_ptr<SystemUIComponentsGridView> CreateTabSliderInstancesGridView();
 
 }  // namespace ash
 
diff --git a/ash/style/style_viewer/system_ui_components_style_viewer_view.cc b/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
index 5a46ed25..0efd46e 100644
--- a/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
+++ b/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
@@ -167,6 +167,9 @@
       base::BindRepeating(&CreateRadioButtonGroupInstancesGridView));
   viewer_view->AddComponent(
       u"KnobSwitch", base::BindRepeating(&CreateKnobSwitchInstancesGridView));
+  viewer_view->AddComponent(
+      u"TabSlider", base::BindRepeating(&CreateTabSliderInstancesGridView));
+
   // Show PillButton on start.
   viewer_view->ShowComponentInstances(u"PillButton");
 
diff --git a/ash/style/style_viewer/tab_slider_instances_grid_view_factory.cc b/ash/style/style_viewer/tab_slider_instances_grid_view_factory.cc
new file mode 100644
index 0000000..0514028
--- /dev/null
+++ b/ash/style/style_viewer/tab_slider_instances_grid_view_factory.cc
@@ -0,0 +1,131 @@
+// 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.
+
+#include "ash/style/style_viewer/system_ui_components_grid_view_factories.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/style/style_viewer/system_ui_components_grid_view.h"
+#include "ash/style/tab_slider.h"
+#include "ash/style/tab_slider_button.h"
+
+namespace ash {
+
+namespace {
+
+// Configurations of grid view for `TabSlider` instances.
+constexpr size_t kGridViewRowNum = 6;
+constexpr size_t kGridViewColNum = 1;
+constexpr size_t kGridViewRowGroupSize = 3;
+constexpr size_t kGirdViewColGroupSize = 1;
+
+}  // namespace
+
+std::unique_ptr<SystemUIComponentsGridView> CreateTabSliderInstancesGridView() {
+  auto grid_view = std::make_unique<SystemUIComponentsGridView>(
+      kGridViewRowNum, kGridViewColNum, kGridViewRowGroupSize,
+      kGirdViewColGroupSize);
+
+  // Create an instance of icon slider with two buttons, a slider background,
+  // the recommended layout, and a selector animation.
+  auto icon_slider_two = std::make_unique<TabSlider>();
+  // Add the buttons with `IconSliderButton` unique pointer.
+  auto* image_button =
+      icon_slider_two->AddButton(std::make_unique<IconSliderButton>(
+          IconSliderButton::PressedCallback(), &kCaptureModeImageIcon,
+          u"image mode"));
+  image_button->SetSelected(true);
+  icon_slider_two->AddButton(std::make_unique<IconSliderButton>(
+      IconSliderButton::PressedCallback(), &kCaptureModeCameraIcon,
+      u"video mode"));
+
+  // Create an instance of icon slider with three buttons, no background, a
+  // customized layout, and no slider animation.
+  auto icon_slider_three = std::make_unique<TabSlider>(
+      /*has_background_=*/false, /*has_slider_background_animation=*/false);
+  icon_slider_three->SetCustomLayout(
+      {/*button_container_spacing=*/0, /*between_buttons_spacing=*/16});
+  // Add the buttons with ctor arguments of `IconSliderButton`.
+  auto* fullscreen_button = icon_slider_three->AddButton<IconSliderButton>(
+      IconSliderButton::PressedCallback(), &kCaptureModeFullscreenIcon,
+      u"fullscreen mode");
+  fullscreen_button->SetSelected(true);
+  icon_slider_three->AddButton<IconSliderButton>(
+      IconSliderButton::PressedCallback(), &kCaptureModeRegionIcon,
+      u"region mode");
+  icon_slider_three->AddButton<IconSliderButton>(
+      IconSliderButton::PressedCallback(), &kCaptureModeWindowIcon,
+      u"window mode");
+
+  // Create an instance of disabled icon slider with two buttons.
+  auto icon_slider_two_disabled = std::make_unique<TabSlider>();
+  auto* disabled_image_button =
+      icon_slider_two_disabled->AddButton<IconSliderButton>(
+          IconSliderButton::PressedCallback(), &kCaptureModeImageIcon,
+          u"image mode");
+  disabled_image_button->SetSelected(true);
+  icon_slider_two_disabled->AddButton<IconSliderButton>(
+      IconSliderButton::PressedCallback(), &kCaptureModeCameraIcon,
+      u"video mode");
+  icon_slider_two_disabled->SetEnabled(false);
+
+  // Create an instance of label slider with two buttons.
+  auto label_slider_two = std::make_unique<TabSlider>();
+  // Add the buttons with `TabSliderButton` unique pointer.
+  auto* label_button =
+      label_slider_two->AddButton(std::make_unique<LabelSliderButton>(
+          LabelSliderButton::PressedCallback(), u"Label 1", u"label 1"));
+  label_button->SetSelected(true);
+  label_slider_two->AddButton(std::make_unique<LabelSliderButton>(
+      LabelSliderButton::PressedCallback(), u"Label 2", u"label 2"));
+
+  // Create an instance of label slider with three buttons with customized
+  // layout.
+  auto label_slider_three = std::make_unique<TabSlider>();
+  label_slider_three->SetCustomLayout(
+      {/*button_container_spacing=*/2, /*between_buttons_spacing=*/16});
+  // Add the buttons with ctor arguments of `TabSliderButton`.
+  auto* label_button_1 = label_slider_three->AddButton<LabelSliderButton>(
+      LabelSliderButton::PressedCallback(), u"Label 1", u"label 1");
+  label_button_1->SetSelected(true);
+  label_slider_three->AddButton<LabelSliderButton>(
+      LabelSliderButton::PressedCallback(), u"Label 2", u"label 2");
+  label_slider_three->AddButton<LabelSliderButton>(
+      LabelSliderButton::PressedCallback(), u"Label 3", u"label 3");
+
+  // Create an instance of disabled label slider with two buttons.
+  auto label_slider_two_disabled = std::make_unique<TabSlider>();
+  auto* disabled_label_button =
+      label_slider_two_disabled->AddButton<LabelSliderButton>(
+          LabelSliderButton::PressedCallback(), u"Label 1", u"label 1");
+  disabled_label_button->SetSelected(true);
+  label_slider_two_disabled->AddButton<LabelSliderButton>(
+      LabelSliderButton::PressedCallback(), u"Label 2", u"label 2");
+  label_slider_two_disabled->SetEnabled(false);
+
+  grid_view->AddInstance(
+      u"Icon tab slider with 2 buttons, recommended layout, selector "
+      u"animation, and background",
+      std::move(icon_slider_two));
+  grid_view->AddInstance(
+      u"Icon tab slider with 3 buttons, custom layout, no selector "
+      u"animation, and no background",
+      std::move(icon_slider_three));
+  grid_view->AddInstance(u"Disabled icon slider with 2 buttons",
+                         std::move(icon_slider_two_disabled));
+
+  grid_view->AddInstance(
+      u"Label tab slider with 2 buttons, recommended layout, selector "
+      u"animation, and background",
+      std::move(label_slider_two));
+  grid_view->AddInstance(
+      u"Label tab slider with 3 buttons, recommended layout, selector "
+      u"animation, and background",
+      std::move(label_slider_three));
+  grid_view->AddInstance(u"Disabled label slider with 2 buttons",
+                         std::move(label_slider_two_disabled));
+
+  return grid_view;
+}
+
+}  // namespace ash
diff --git a/ash/style/system_textfield.cc b/ash/style/system_textfield.cc
new file mode 100644
index 0000000..1f99590
--- /dev/null
+++ b/ash/style/system_textfield.cc
@@ -0,0 +1,218 @@
+// 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.
+
+#include "ash/style/system_textfield.h"
+
+#include "ash/style/system_textfield_controller.h"
+#include "system_textfield.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/color/color_provider.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/focus_ring.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+
+namespace {
+
+// The name of font family.
+constexpr char kGoogleSansFont[] = "Google Sans";
+// The heights of textfield containers for different font sizes.
+constexpr int kSmallContainerHeight = 24;
+constexpr int kMediumContainerHeight = 28;
+constexpr int kLargeContainerHeight = 28;
+// The font sizes in pixels.
+constexpr int kSmallFontSize = 12;
+constexpr int kMediumFontSize = 14;
+constexpr int kLargeFontSize = 16;
+// The minimum textfield container width.
+constexpr int kMinWidth = 80;
+// The gap between the focus ring and textfield container.
+constexpr float kFocusRingGap = 2.0f;
+// The border insets to add horizontal paddings in container.
+constexpr gfx::Insets kBorderInsets = gfx::Insets::VH(0, 8);
+// The rounded conner radius of textfield container.
+constexpr int kCornerRadius = 4;
+
+// Gets textfield container heights for different types.
+int GetContainerHeightFromType(SystemTextfield::Type type) {
+  int container_height;
+  switch (type) {
+    case SystemTextfield::Type::kSmall:
+      container_height = kSmallContainerHeight;
+      break;
+    case SystemTextfield::Type::kMedium:
+      container_height = kMediumContainerHeight;
+      break;
+    case SystemTextfield::Type::kLarge:
+      container_height = kLargeContainerHeight;
+      break;
+  }
+  return container_height;
+}
+
+// Gets font list for different types.
+gfx::FontList GetFontListFromType(SystemTextfield::Type type) {
+  int font_size;
+  switch (type) {
+    case SystemTextfield::Type::kSmall:
+      font_size = kSmallFontSize;
+      break;
+    case SystemTextfield::Type::kMedium:
+      font_size = kMediumFontSize;
+      break;
+    case SystemTextfield::Type::kLarge:
+      font_size = kLargeFontSize;
+      break;
+  }
+  return gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, font_size,
+                       gfx::Font::Weight::NORMAL);
+}
+
+}  // namespace
+
+SystemTextfield::SystemTextfield(Type type) : type_(type) {
+  SetFontList(GetFontListFromType(type_));
+  SetBorder(views::CreateEmptyBorder(kBorderInsets));
+
+  // Configure focus ring.
+  auto* focus_ring = views::FocusRing::Get(this);
+  DCHECK(focus_ring);
+  const float halo_thickness = focus_ring->GetHaloThickness();
+  focus_ring->SetHaloInset(-kFocusRingGap - 0.5f * halo_thickness);
+  focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing);
+
+  enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
+      &SystemTextfield::OnEnabledStateChanged, base::Unretained(this)));
+}
+
+SystemTextfield::~SystemTextfield() = default;
+
+void SystemTextfield::SetActive(bool active) {
+  if (active_ == active)
+    return;
+
+  active_ = active;
+
+  if (active_) {
+    // Activate the textfield and record the text content.
+    views::Textfield::OnFocus();
+    restored_text_content_ = GetText();
+  } else {
+    // Clear selection when the textfield is deactivated.
+    ClearSelection();
+    views::Textfield::OnBlur();
+  }
+
+  views::FocusRing::Get(this)->SchedulePaint();
+}
+
+void SystemTextfield::RestoreText() {
+  SetText(restored_text_content_);
+}
+
+gfx::Size SystemTextfield::CalculatePreferredSize() const {
+  // The width of container equals to the content width with horizontal padding.
+  // The height of the container dependents on the type.
+  const std::u16string& text = GetText();
+  int width = 0;
+  int height = 0;
+  gfx::Canvas::SizeStringInt(text.empty() ? GetPlaceholderText() : text,
+                             GetFontListFromType(type_), &width, &height, 0,
+                             gfx::Canvas::NO_ELLIPSIS);
+  return gfx::Size(
+      std::max(width + GetCaretBounds().width() + GetInsets().width(),
+               kMinWidth),
+      GetContainerHeightFromType(type_));
+}
+
+void SystemTextfield::SetBorder(std::unique_ptr<views::Border> b) {
+  // The base `Textfield` has a preset border. When a new border is set, the
+  // focus ring will be removed. The `SystemTextfield` needs an empty border for
+  // horizontal padding and keeps the focus ring.
+  views::View::SetBorder(std::move(b));
+}
+
+void SystemTextfield::OnMouseEntered(const ui::MouseEvent& event) {
+  UpdateBackground();
+}
+
+void SystemTextfield::OnMouseExited(const ui::MouseEvent& event) {
+  UpdateBackground();
+}
+
+void SystemTextfield::OnThemeChanged() {
+  views::View::OnThemeChanged();
+
+  // Only update the text color since the background color will be handled by
+  // themed background.
+  UpdateTextColor();
+}
+
+void SystemTextfield::OnFocus() {
+  if (delegate_) {
+    delegate_->OnTextfieldFocused(this);
+  } else {
+    views::Textfield::OnFocus();
+  }
+
+  UpdateBackground();
+}
+
+void SystemTextfield::OnBlur() {
+  if (delegate_) {
+    delegate_->OnTextfieldBlurred(this);
+  } else {
+    views::Textfield::OnBlur();
+  }
+
+  UpdateBackground();
+}
+
+void SystemTextfield::OnEnabledStateChanged() {
+  UpdateBackground();
+  UpdateTextColor();
+  SchedulePaint();
+}
+
+void SystemTextfield::UpdateTextColor() {
+  if (!GetWidget())
+    return;
+
+  // Set text color.
+  auto* color_provider = GetColorProvider();
+  gfx::RenderText* render_text = GetRenderText();
+  if (!GetEnabled()) {
+    SetColor(color_provider->GetColor(cros_tokens::kCrosSysDisabled));
+    return;
+  }
+
+  // Set text color and selection text and background (highlight part) colors.
+  SetColor(color_provider->GetColor(cros_tokens::kCrosSysOnSurface));
+  render_text->set_selection_color(
+      color_provider->GetColor(cros_tokens::kCrosSysOnSurface));
+  render_text->set_selection_background_focused_color(
+      color_provider->GetColor(cros_tokens::kCrosSysHighlightText));
+}
+
+void SystemTextfield::UpdateBackground() {
+  // Create a themed rounded rect background when the mouse hovers on the
+  // textfield or the textfield is focused.
+  if (IsMouseHovered() || HasFocus()) {
+    SetBackground(views::CreateThemedRoundedRectBackground(
+        cros_tokens::kCrosSysHoverOnSubtle, kCornerRadius));
+    return;
+  }
+
+  // In other cases, use a transparent background.
+  SetBackground(nullptr);
+}
+
+BEGIN_METADATA(SystemTextfield, views::Textfield)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/style/system_textfield.h b/ash/style/system_textfield.h
new file mode 100644
index 0000000..c88d3a7
--- /dev/null
+++ b/ash/style/system_textfield.h
@@ -0,0 +1,81 @@
+// 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 ASH_STYLE_SYSTEM_TEXTFIELD_H_
+#define ASH_STYLE_SYSTEM_TEXTFIELD_H_
+
+#include "ash/ash_export.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/color/color_id.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace ash {
+
+class SystemTextfieldController;
+
+// SystemTextfield is an extension of `Views::Textfield` used for system UIs. It
+// has specific small, medium, and large types and applies dynamic colors.
+class ASH_EXPORT SystemTextfield : public views::Textfield {
+ public:
+  METADATA_HEADER(SystemTextfield);
+
+  enum class Type {
+    kSmall,
+    kMedium,
+    kLarge,
+  };
+
+  // The delegate that handles the textfield behaviors on focused and blurred.
+  class Delegate {
+   public:
+    virtual void OnTextfieldFocused(SystemTextfield* textfield) = 0;
+    virtual void OnTextfieldBlurred(SystemTextfield* textfield) = 0;
+
+   protected:
+    virtual ~Delegate() = default;
+  };
+
+  explicit SystemTextfield(Type type);
+  SystemTextfield(const SystemTextfield&) = delete;
+  SystemTextfield& operator=(const SystemTextfield&) = delete;
+  ~SystemTextfield() override;
+
+  void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+  // Activates or deactivates the textfield. The method is mainly used by
+  // `SystemTextfieldController`.
+  void SetActive(bool active);
+  bool active() const { return active_; }
+  // Restores to previous text when the changes are discarded.
+  void RestoreText();
+
+  // views::Textfield:
+  gfx::Size CalculatePreferredSize() const override;
+  void SetBorder(std::unique_ptr<views::Border> b) override;
+  void OnMouseEntered(const ui::MouseEvent& event) override;
+  void OnMouseExited(const ui::MouseEvent& event) override;
+  void OnThemeChanged() override;
+  void OnFocus() override;
+  void OnBlur() override;
+
+ private:
+  // Called when the enabled state is changed.
+  void OnEnabledStateChanged();
+  // Updates text and selection text colors.
+  void UpdateTextColor();
+  // Creates themed or transparent background according to the textfield states.
+  void UpdateBackground();
+
+  Type type_;
+  bool active_ = false;
+  // Text content to restore when changes are discarded.
+  std::u16string restored_text_content_;
+  Delegate* delegate_ = nullptr;
+  // Enabled state changed callback.
+  base::CallbackListSubscription enabled_changed_subscription_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_STYLE_SYSTEM_TEXTFIELD_H_
diff --git a/ash/style/system_textfield_controller.cc b/ash/style/system_textfield_controller.cc
new file mode 100644
index 0000000..4b239d4
--- /dev/null
+++ b/ash/style/system_textfield_controller.cc
@@ -0,0 +1,104 @@
+// 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.
+
+#include "ash/style/system_textfield_controller.h"
+
+#include "ui/views/controls/focus_ring.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+SystemTextfieldController::SystemTextfieldController(SystemTextfield* textfield)
+    : textfield_(textfield) {
+  textfield_->SetController(this);
+  textfield_->set_delegate(this);
+  views::FocusRing::Get(textfield_)
+      ->SetHasFocusPredicate(
+          [&](views::View* view) { return textfield_->active(); });
+}
+
+SystemTextfieldController::~SystemTextfieldController() = default;
+
+void SystemTextfieldController::OnTextfieldFocused(SystemTextfield* textfield) {
+  DCHECK_EQ(textfield_, textfield);
+  // Do not activate the textfield immediately on focus.
+}
+
+void SystemTextfieldController::OnTextfieldBlurred(SystemTextfield* textfield) {
+  DCHECK_EQ(textfield_, textfield);
+  // Deactivate the textfield on blur.
+  textfield_->SetActive(false);
+}
+
+bool SystemTextfieldController::HandleKeyEvent(views::Textfield* sender,
+                                               const ui::KeyEvent& key_event) {
+  DCHECK_EQ(textfield_, sender);
+
+  if (key_event.type() != ui::ET_KEY_PRESSED)
+    return false;
+
+  const bool active = textfield_->active();
+  if (key_event.key_code() == ui::VKEY_RETURN) {
+    // If the textfield is focused but not active, activate the textfield and
+    // highlight all the text.
+    if (!active) {
+      textfield_->SetActive(true);
+      textfield_->SelectAll(false);
+      return true;
+    }
+
+    // Otherwise, commit the changes and deactivate the textfield.
+    textfield_->SetActive(false);
+    return true;
+  }
+
+  if (key_event.key_code() == ui::VKEY_ESCAPE) {
+    // If the textfield is active, discard the changes, deactivate the
+    // textfield.
+    if (active) {
+      textfield_->RestoreText();
+      textfield_->SetActive(false);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool SystemTextfieldController::HandleMouseEvent(
+    views::Textfield* sender,
+    const ui::MouseEvent& mouse_event) {
+  DCHECK_EQ(textfield_, sender);
+
+  if (!mouse_event.IsOnlyLeftMouseButton() &&
+      !mouse_event.IsOnlyRightMouseButton()) {
+    return false;
+  }
+
+  switch (mouse_event.type()) {
+    case ui::ET_MOUSE_PRESSED:
+      // When the mouse is pressed and the textfield is not active, activate
+      // the textfield but defer selecting all text until the mouse is
+      // released.
+      if (!textfield_->active()) {
+        defer_select_all_ = true;
+        textfield_->SetActive(true);
+      }
+      break;
+    case ui::ET_MOUSE_RELEASED:
+      // When selecting all text was deferred, do it if there is no selection.
+      if (defer_select_all_) {
+        defer_select_all_ = false;
+        if (!textfield_->HasSelection())
+          textfield_->SelectAll(false);
+        return true;
+      }
+      break;
+    default:
+      break;
+  }
+  return false;
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/style/system_textfield_controller.h b/ash/style/system_textfield_controller.h
new file mode 100644
index 0000000..a2e0b1d
--- /dev/null
+++ b/ash/style/system_textfield_controller.h
@@ -0,0 +1,53 @@
+// 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 ASH_STYLE_SYSTEM_TEXTFIELD_CONTROLLER_H_
+#define ASH_STYLE_SYSTEM_TEXTFIELD_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/style/system_textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+
+namespace ash {
+
+// Defines the following textfield behaviors of system UI. Note that the
+// controller can only be set to one textfield at a time:
+// - When the textfield just gets focused with Tab key, it will not be activated
+// until the user presses RETURN.
+// - Clicking the textfield will both focus and activate the textfield. Single
+// clicking will highlight all the text.
+// - While editing the textfield, pressing RETURN will commit the changes and
+// deactivate the textfield but keep focus.
+// - While editing the textfield, pressing ESCAPE will discard the changes and
+// deactivate the textfield but keep focus.
+// - The focus ring would only show on active.
+class ASH_EXPORT SystemTextfieldController : public views::TextfieldController,
+                                             public SystemTextfield::Delegate {
+ public:
+  explicit SystemTextfieldController(SystemTextfield* textfield);
+  SystemTextfieldController(const SystemTextfieldController&) = delete;
+  SystemTextfieldController& operator=(const SystemTextfieldController&) =
+      delete;
+  ~SystemTextfieldController() override;
+
+  // SystemTextfield::Delegate:
+  void OnTextfieldFocused(SystemTextfield* textfield) override;
+  void OnTextfieldBlurred(SystemTextfield* textfield) override;
+
+  // views::TextfieldController:
+  bool HandleKeyEvent(views::Textfield* sender,
+                      const ui::KeyEvent& key_event) override;
+  bool HandleMouseEvent(views::Textfield* sender,
+                        const ui::MouseEvent& mouse_event) override;
+
+ private:
+  // The textfield that the controller binds with.
+  SystemTextfield* const textfield_;
+  // Indicates if selecting all text should be deferred.
+  bool defer_select_all_ = false;
+};
+
+}  // namespace ash
+
+#endif  // ASH_STYLE_SYSTEM_TEXTFIELD_CONTROLLER_H_
\ No newline at end of file
diff --git a/ash/system/diagnostics/diagnostics_log_controller.cc b/ash/system/diagnostics/diagnostics_log_controller.cc
index 94b1b16..9894a68 100644
--- a/ash/system/diagnostics/diagnostics_log_controller.cc
+++ b/ash/system/diagnostics/diagnostics_log_controller.cc
@@ -150,21 +150,19 @@
   // Add the routine section for the system category.
   log_pieces.push_back(GetRoutineResultsString(system_routines));
 
-  if (features::IsNetworkingInDiagnosticsAppEnabled()) {
-    // Add networking category.
-    log_pieces.push_back(kNetworkingLogSectionHeader);
+  // Add networking category.
+  log_pieces.push_back(kNetworkingLogSectionHeader);
 
-    // Add the network info section.
-    log_pieces.push_back(networking_log_->GetNetworkInfo());
+  // Add the network info section.
+  log_pieces.push_back(networking_log_->GetNetworkInfo());
 
-    // Add the routine section for the network category.
-    const std::string network_routines = routine_log_->GetContentsForCategory(
-        RoutineLog::RoutineCategory::kNetwork);
-    log_pieces.push_back(GetRoutineResultsString(network_routines));
+  // Add the routine section for the network category.
+  const std::string network_routines = routine_log_->GetContentsForCategory(
+      RoutineLog::RoutineCategory::kNetwork);
+  log_pieces.push_back(GetRoutineResultsString(network_routines));
 
-    // Add the network events section.
-    log_pieces.push_back(networking_log_->GetNetworkEvents());
-  }
+  // Add the network events section.
+  log_pieces.push_back(networking_log_->GetNetworkEvents());
 
   if (features::IsInputInDiagnosticsAppEnabled()) {
     std::string input_log_contents = keyboard_input_log_->GetLogContents();
diff --git a/ash/system/network/managed_sim_lock_notifier_unittest.cc b/ash/system/network/managed_sim_lock_notifier_unittest.cc
index e02f6fe..cc570bc 100644
--- a/ash/system/network/managed_sim_lock_notifier_unittest.cc
+++ b/ash/system/network/managed_sim_lock_notifier_unittest.cc
@@ -107,13 +107,13 @@
   }
 
   void SetAllowCellularSimLock(bool allow_cellular_sim_lock) {
-    base::DictionaryValue global_config;
-    global_config.SetBoolKey(
-        ::onc::global_network_config::kAllowCellularSimLock,
-        allow_cellular_sim_lock);
+    base::Value::Dict global_config;
+    global_config.Set(::onc::global_network_config::kAllowCellularSimLock,
+                      allow_cellular_sim_lock);
     managed_network_configuration_handler()->SetPolicy(
         ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
-        base::Value(base::Value::Type::LIST), global_config);
+        base::Value(base::Value::Type::LIST),
+        base::Value(std::move(global_config)));
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/ash/system/network/network_info_bubble_unittest.cc b/ash/system/network/network_info_bubble_unittest.cc
index f14f7af..a527535 100644
--- a/ash/system/network/network_info_bubble_unittest.cc
+++ b/ash/system/network/network_info_bubble_unittest.cc
@@ -131,18 +131,18 @@
   }
 
   void AddDefaultNetworkWithIPAddresses() {
-    base::DictionaryValue ipv4;
-    ipv4.SetKey(shill::kAddressProperty, base::Value(kIpv4Address));
-    ipv4.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv4));
+    base::Value::Dict ipv4;
+    ipv4.Set(shill::kAddressProperty, base::Value(kIpv4Address));
+    ipv4.Set(shill::kMethodProperty, base::Value(shill::kTypeIPv4));
 
-    base::DictionaryValue ipv6;
-    ipv6.SetKey(shill::kAddressProperty, base::Value(kIpv6Address));
-    ipv6.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv6));
+    base::Value::Dict ipv6;
+    ipv6.Set(shill::kAddressProperty, base::Value(kIpv6Address));
+    ipv6.Set(shill::kMethodProperty, base::Value(shill::kTypeIPv6));
 
     network_config_helper_.network_state_helper().ip_config_test()->AddIPConfig(
-        kIPv4ConfigPath, ipv4);
+        kIPv4ConfigPath, base::Value(std::move(ipv4)));
     network_config_helper_.network_state_helper().ip_config_test()->AddIPConfig(
-        kIPv6ConfigPath, ipv6);
+        kIPv6ConfigPath, base::Value(std::move(ipv6)));
 
     base::Value::List ip_configs;
     ip_configs.Append(kIPv4ConfigPath);
diff --git a/ash/system/network/sms_observer_unittest.cc b/ash/system/network/sms_observer_unittest.cc
index 85ceab87..97e783d 100644
--- a/ash/system/network/sms_observer_unittest.cc
+++ b/ash/system/network/sms_observer_unittest.cc
@@ -21,18 +21,17 @@
 
 namespace {
 
-std::unique_ptr<base::DictionaryValue> CreateMessage(
+base::Value::Dict CreateMessage(
     const char* kDefaultMessage = "FakeSMSClient: \xF0\x9F\x98\x8A",
     const char* kDefaultNumber = "000-000-0000",
     const char* kDefaultTimestamp = "Fri Jun  8 13:26:04 EDT 2016") {
-  std::unique_ptr<base::DictionaryValue> sms =
-      std::make_unique<base::DictionaryValue>();
+  base::Value::Dict sms;
   if (kDefaultNumber)
-    sms->SetStringKey("number", kDefaultNumber);
+    sms.Set("number", kDefaultNumber);
   if (kDefaultMessage)
-    sms->SetStringKey("text", kDefaultMessage);
+    sms.Set("text", kDefaultMessage);
   if (kDefaultTimestamp)
-    sms->SetStringKey("timestamp", kDefaultMessage);
+    sms.Set("timestamp", kDefaultMessage);
   return sms;
 }
 
@@ -56,8 +55,7 @@
   SmsObserver* sms_observer = GetSmsObserver();
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 
-  std::unique_ptr<base::DictionaryValue> sms(CreateMessage());
-  sms_observer->MessageReceived(*sms);
+  sms_observer->MessageReceived(base::Value(CreateMessage()));
 
   const message_center::NotificationList::Notifications notifications =
       MessageCenter::Get()->GetVisibleNotifications();
@@ -76,9 +74,8 @@
   SmsObserver* sms_observer = GetSmsObserver();
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 
-  std::unique_ptr<base::DictionaryValue> sms(
-      CreateMessage("FakeSMSClient: Test Message.", nullptr));
-  sms_observer->MessageReceived(*sms);
+  sms_observer->MessageReceived(
+      base::Value(CreateMessage("FakeSMSClient: Test Message.", nullptr)));
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 }
 
@@ -87,8 +84,7 @@
   SmsObserver* sms_observer = GetSmsObserver();
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 
-  std::unique_ptr<base::DictionaryValue> sms(CreateMessage(""));
-  sms_observer->MessageReceived(*sms);
+  sms_observer->MessageReceived(base::Value(CreateMessage("")));
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 }
 
@@ -96,8 +92,7 @@
 TEST_F(SmsObserverTest, TextMessageMissingText) {
   SmsObserver* sms_observer = GetSmsObserver();
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
-  std::unique_ptr<base::DictionaryValue> sms(CreateMessage(""));
-  sms_observer->MessageReceived(*sms);
+  sms_observer->MessageReceived(base::Value(CreateMessage("")));
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 }
 
@@ -107,10 +102,8 @@
   SmsObserver* sms_observer = GetSmsObserver();
   EXPECT_EQ(0u, MessageCenter::Get()->GetVisibleNotifications().size());
 
-  std::unique_ptr<base::DictionaryValue> sms(CreateMessage("first message"));
-  sms_observer->MessageReceived(*sms);
-  std::unique_ptr<base::DictionaryValue> sms2(CreateMessage("second message"));
-  sms_observer->MessageReceived(*sms2);
+  sms_observer->MessageReceived(base::Value(CreateMessage("first message")));
+  sms_observer->MessageReceived(base::Value(CreateMessage("second message")));
   const message_center::NotificationList::Notifications notifications =
       MessageCenter::Get()->GetVisibleNotifications();
   EXPECT_EQ(2u, notifications.size());
diff --git a/ash/system/power/power_button_controller_unittest.cc b/ash/system/power/power_button_controller_unittest.cc
index d64c4be8..5751c5d 100644
--- a/ash/system/power/power_button_controller_unittest.cc
+++ b/ash/system/power/power_button_controller_unittest.cc
@@ -1085,29 +1085,29 @@
       public testing::WithParamInterface<PowerButtonPosition> {
  public:
   PowerButtonControllerWithPositionTest() : power_button_position_(GetParam()) {
-    base::DictionaryValue position_info;
+    base::Value::Dict position_info;
     switch (power_button_position_) {
       case PowerButtonPosition::LEFT:
-        position_info.SetStringKey(PowerButtonController::kEdgeField,
-                                   PowerButtonController::kLeftEdge);
+        position_info.Set(PowerButtonController::kEdgeField,
+                          PowerButtonController::kLeftEdge);
         break;
       case PowerButtonPosition::RIGHT:
-        position_info.SetStringKey(PowerButtonController::kEdgeField,
-                                   PowerButtonController::kRightEdge);
+        position_info.Set(PowerButtonController::kEdgeField,
+                          PowerButtonController::kRightEdge);
         break;
       case PowerButtonPosition::TOP:
-        position_info.SetStringKey(PowerButtonController::kEdgeField,
-                                   PowerButtonController::kTopEdge);
+        position_info.Set(PowerButtonController::kEdgeField,
+                          PowerButtonController::kTopEdge);
         break;
       case PowerButtonPosition::BOTTOM:
-        position_info.SetStringKey(PowerButtonController::kEdgeField,
-                                   PowerButtonController::kBottomEdge);
+        position_info.Set(PowerButtonController::kEdgeField,
+                          PowerButtonController::kBottomEdge);
         break;
       default:
         return;
     }
-    position_info.SetDoubleKey(PowerButtonController::kPositionField,
-                               kPowerButtonPercentage);
+    position_info.Set(PowerButtonController::kPositionField,
+                      kPowerButtonPercentage);
 
     std::string json_position_info;
     base::JSONWriter::Write(position_info, &json_position_info);
diff --git a/ash/system/video_conference/bubble/set_value_effects_view.cc b/ash/system/video_conference/bubble/set_value_effects_view.cc
index 7c6bec1..b9ede5e 100644
--- a/ash/system/video_conference/bubble/set_value_effects_view.cc
+++ b/ash/system/video_conference/bubble/set_value_effects_view.cc
@@ -35,6 +35,7 @@
     }
 
     // Add a button for each state.
+    const int current_state = effect->get_state_callback().Run();
     for (int i = 0; i < effect->GetNumStates(); ++i) {
       const VcEffectState* state = effect->GetState(/*index=*/i);
       std::unique_ptr<views::RadioButton> state_button =
@@ -49,6 +50,11 @@
                               ? BubbleViewID::kSetValueButtonMin + i
                               : BubbleViewID::kSetValueButtonMax);
 
+      // Set-value effects require an actual integer state. Mark it checked
+      // (selected) if the current state of the effect is this state's value,
+      DCHECK(state->state().has_value());
+      state_button->SetChecked(state->state().value() == current_state);
+
       AddChildView(std::move(state_button));
     }
 
diff --git a/ash/system/video_conference/bubble/toggle_effects_view.cc b/ash/system/video_conference/bubble/toggle_effects_view.cc
index 6d1907b..753c9fe 100644
--- a/ash/system/video_conference/bubble/toggle_effects_view.cc
+++ b/ash/system/video_conference/bubble/toggle_effects_view.cc
@@ -10,6 +10,7 @@
 #include "ash/system/video_conference/bubble/bubble_view_ids.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 #include "ash/system/video_conference/video_conference_tray_controller.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/label.h"
@@ -21,10 +22,11 @@
 
 // A single toggle button for a video conference effect, combined with a text
 // label.
-class ButtonContainer : public views::View {
+class ButtonContainer : public views::View, public IconButton::Delegate {
  public:
   ButtonContainer(views::Button::PressedCallback callback,
                   const gfx::VectorIcon* icon,
+                  bool toggle_state,
                   const std::u16string& label_text,
                   const int accessible_name_id) {
     views::FlexLayout* layout =
@@ -35,11 +37,26 @@
 
     AddChildView(std::make_unique<views::Label>(label_text));
 
+    // Construct the `IconButton`, set ID and initial toggle state (from the
+    // passed-in value, which is the current state of the effect).
     std::unique_ptr<IconButton> button = std::make_unique<IconButton>(
         callback, IconButton::Type::kMedium, icon, accessible_name_id,
         /*is_togglable=*/true,
         /*has_border=*/true);
     button->SetID(video_conference::BubbleViewID::kToggleEffectsButton);
+    button->SetToggled(toggle_state);
+
+    // Delegate is the `ButtonContainer`, which changes the toggle-state of the
+    // button when clicked.
+    button->set_delegate(this);
+
+    // TODO(b/253249205): This is temporary, will be replaced wholesale when the
+    // effect toggle buttons are made to conform with the spec.
+    button->SetBackgroundToggledColorId(
+        cros_tokens::kCrosSysSystemNegativeContainer);
+    button->SetIconToggledColorId(
+        cros_tokens::kCrosSysSystemOnNegativeContainer);
+
     AddChildView(std::move(button));
 
     SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(10, 10)));
@@ -51,6 +68,12 @@
   ButtonContainer& operator=(const ButtonContainer&) = delete;
 
   ~ButtonContainer() override = default;
+
+  // IconButton::Delegate:
+  void OnButtonToggled(IconButton* button) override {}
+  void OnButtonClicked(IconButton* button) override {
+    button->SetToggled(!button->toggled());
+  }
 };
 
 }  // namespace
@@ -86,8 +109,10 @@
       DCHECK_EQ(tile->type(), VcEffectType::kToggle);
       DCHECK_EQ(tile->GetNumStates(), 1);
       const VcEffectState* state = tile->GetState(/*index=*/0);
+      bool current_state = tile->get_state_callback().Run();
       row_view->AddChildView(std::make_unique<ButtonContainer>(
-          state->button_callback(), state->icon(), state->label_text(),
+          state->button_callback(), state->icon(),
+          /*toggle_state=*/current_state, state->label_text(),
           state->accessible_name_id()));
     }
 
diff --git a/ash/system/video_conference/effects/fake_video_conference_effects.cc b/ash/system/video_conference/effects/fake_video_conference_effects.cc
index e6874c7..c8d560e 100644
--- a/ash/system/video_conference/effects/fake_video_conference_effects.cc
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.cc
@@ -21,19 +21,24 @@
                          /*icon=*/absl::nullopt,
                          /*accessible_name_id=*/absl::nullopt) {}
 
+SimpleToggleEffect::~SimpleToggleEffect() = default;
+
 SimpleToggleEffect::SimpleToggleEffect(
     const std::u16string& label_text,
     absl::optional<const gfx::VectorIcon*> icon,
     absl::optional<int> accessible_name_id) {
-  std::unique_ptr<VcHostedEffect> effect =
-      std::make_unique<VcHostedEffect>(VcEffectType::kToggle);
+  std::unique_ptr<VcHostedEffect> effect = std::make_unique<VcHostedEffect>(
+      VcEffectType::kToggle,
+      base::BindRepeating(&SimpleToggleEffect::GetEffectState,
+                          base::Unretained(this),
+                          /*effect_id=*/VcEffectState::kUnusedId));
 
   // Use default `icon` and/or `accessible_name_id` if none was passed in.
   std::unique_ptr<VcEffectState> state = std::make_unique<VcEffectState>(
       icon.value_or(&ash::kPrivacyIndicatorsCameraIcon), label_text,
       accessible_name_id.value_or(IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA),
       base::BindRepeating(&SimpleToggleEffect::OnEffectControlActivated,
-                          base::Unretained(this),
+                          weak_factory_.GetWeakPtr(),
                           /*effect_id=*/VcEffectState::kUnusedId,
                           /*value=*/0));
   effect->AddState(std::move(state));
@@ -79,42 +84,22 @@
 // Delegates that host a set-value effect.
 
 ShaggyFurEffect::ShaggyFurEffect() {
-  std::unique_ptr<VcHostedEffect> effect =
-      std::make_unique<VcHostedEffect>(VcEffectType::kSetValue);
-  std::unique_ptr<VcEffectState> bald_state = std::make_unique<VcEffectState>(
-      /*icon=*/nullptr,
-      /*label_text=*/u"Bald",
-      /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-      /*button_callback=*/
-      base::BindRepeating(&ShaggyFurEffect::OnEffectControlActivated,
+  std::unique_ptr<VcHostedEffect> effect = std::make_unique<VcHostedEffect>(
+      VcEffectType::kSetValue,
+      base::BindRepeating(&ShaggyFurEffect::GetEffectState,
                           base::Unretained(this),
-                          /*effect_id=*/0,
-                          /*value=*/static_cast<int>(FurShagginess::kBald)));
-  std::unique_ptr<VcEffectState> buzzcut_state =
-      std::make_unique<VcEffectState>(
-          /*icon=*/nullptr,
-          /*label_text=*/u"Buzzcut",
-          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-          /*button_callback=*/
-          base::BindRepeating(
-              &ShaggyFurEffect::OnEffectControlActivated,
-              base::Unretained(this),
-              /*effect_id=*/0,
-              /*value=*/static_cast<int>(FurShagginess::kBuzzcut)));
-  std::unique_ptr<VcEffectState> thick_state = std::make_unique<VcEffectState>(
-      /*icon=*/nullptr,
-      /*label_text=*/u"Thick",
-      /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-      /*button_callback=*/
-      base::BindRepeating(&ShaggyFurEffect::OnEffectControlActivated,
-                          base::Unretained(this),
-                          /*effect_id=*/0,
-                          /*value=*/static_cast<int>(FurShagginess::kThick)));
-  effect->AddState(std::move(bald_state));
-  effect->AddState(std::move(buzzcut_state));
-  effect->AddState(std::move(thick_state));
+                          /*effect_id=*/0));
   effect->set_label_text(u"Shaggy Fur");
   effect->set_id(100);
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(FurShagginess::kBald),
+                   /*label_text=*/u"Bald");
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(FurShagginess::kBuzzcut),
+                   /*label_text=*/u"Buzzcut");
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(FurShagginess::kThick),
+                   /*label_text=*/u"Thick");
   AddEffect(std::move(effect));
 
   // Initialize click counts.
@@ -139,55 +124,41 @@
   return num_activations_for_testing_[value];
 }
 
-SuperCutnessEffect::SuperCutnessEffect() {
-  std::unique_ptr<VcHostedEffect> effect =
-      std::make_unique<VcHostedEffect>(VcEffectType::kSetValue);
-  std::unique_ptr<VcEffectState> ugly_dog_state =
-      std::make_unique<VcEffectState>(
-          /*icon=*/nullptr,
-          /*label_text=*/u"Ugly Dog",
-          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-          /*button_callback=*/
-          base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
-                              base::Unretained(this),
-                              /*effect_id=*/0,
-                              /*value=*/static_cast<int>(HowCute::kUglyDog)));
-  std::unique_ptr<VcEffectState> teddy_bear_state =
-      std::make_unique<VcEffectState>(
-          /*icon=*/nullptr,
-          /*label_text=*/u"Teddy Bear",
-          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-          /*button_callback=*/
-          base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
-                              base::Unretained(this),
-                              /*effect_id=*/0,
-                              /*value=*/static_cast<int>(HowCute::kTeddyBear)));
-  std::unique_ptr<VcEffectState> zara_state = std::make_unique<VcEffectState>(
+void ShaggyFurEffect::AddStateToEffect(VcHostedEffect* effect,
+                                       int state_value,
+                                       std::u16string label_text) {
+  DCHECK(effect);
+  effect->AddState(std::make_unique<VcEffectState>(
       /*icon=*/nullptr,
-      /*label_text=*/u"Zara",
+      /*label_text=*/label_text,
       /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
       /*button_callback=*/
-      base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
+      base::BindRepeating(&ShaggyFurEffect::OnEffectControlActivated,
+                          weak_factory_.GetWeakPtr(), /*effect_id=*/0,
+                          /*value=*/state_value),
+      /*state=*/state_value));
+}
+
+SuperCutnessEffect::SuperCutnessEffect() {
+  std::unique_ptr<VcHostedEffect> effect = std::make_unique<VcHostedEffect>(
+      VcEffectType::kSetValue,
+      base::BindRepeating(&SuperCutnessEffect::GetEffectState,
                           base::Unretained(this),
-                          /*effect_id=*/0,
-                          /*value=*/static_cast<int>(HowCute::kZara)));
-  std::unique_ptr<VcEffectState> inscrutable_state =
-      std::make_unique<VcEffectState>(
-          /*icon=*/nullptr,
-          /*label_text=*/u"Inscrutable",
-          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
-          /*button_callback=*/
-          base::BindRepeating(
-              &SuperCutnessEffect::OnEffectControlActivated,
-              base::Unretained(this),
-              /*effect_id=*/0,
-              /*value=*/static_cast<int>(HowCute::kInscrutable)));
-  effect->AddState(std::move(ugly_dog_state));
-  effect->AddState(std::move(teddy_bear_state));
-  effect->AddState(std::move(zara_state));
-  effect->AddState(std::move(inscrutable_state));
+                          /*effect_id=*/0));
   effect->set_label_text(u"Super Cuteness");
   effect->set_id(200);
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(HowCute::kUglyDog),
+                   /*label_text=*/u"Ugly Dog");
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(HowCute::kTeddyBear),
+                   /*label_text=*/u"Teddy Bear");
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(HowCute::kZara),
+                   /*label_text=*/u"Zara");
+  AddStateToEffect(effect.get(),
+                   /*state_value=*/static_cast<int>(HowCute::kInscrutable),
+                   /*label_text=*/u"Inscrutable");
   AddEffect(std::move(effect));
 
   // Initialize click counts.
@@ -212,6 +183,21 @@
   return num_activations_for_testing_[value];
 }
 
+void SuperCutnessEffect::AddStateToEffect(VcHostedEffect* effect,
+                                          int state_value,
+                                          std::u16string label_text) {
+  DCHECK(effect);
+  effect->AddState(std::make_unique<VcEffectState>(
+      /*icon=*/nullptr,
+      /*label_text=*/label_text,
+      /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
+      /*button_callback=*/
+      base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
+                          weak_factory_.GetWeakPtr(), /*effect_id=*/0,
+                          /*value=*/state_value),
+      /*state=*/state_value));
+}
+
 // This registers/unregisters all effects owned by `EffectRepository`.
 // Comment-out the `RegisterDelegate`/`UnregisterDelegate` calls for effects
 // that are not needed e.g. to test `ash::video_conference::BubbleView` with
diff --git a/ash/system/video_conference/effects/fake_video_conference_effects.h b/ash/system/video_conference/effects/fake_video_conference_effects.h
index f0a61bc..00ede90e 100644
--- a/ash/system/video_conference/effects/fake_video_conference_effects.h
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/views/controls/button/button.h"
 
 namespace gfx {
@@ -36,7 +37,7 @@
   SimpleToggleEffect(const SimpleToggleEffect&) = delete;
   SimpleToggleEffect& operator=(const SimpleToggleEffect&) = delete;
 
-  ~SimpleToggleEffect() override = default;
+  ~SimpleToggleEffect() override;
 
   // VcEffectsDelegate:
   int GetEffectState(int effect_id) override;
@@ -47,6 +48,8 @@
  private:
   // Number of times the control has been activated, used by unit tests.
   int num_activations_for_testing_ = 0;
+
+  base::WeakPtrFactory<SimpleToggleEffect> weak_factory_{this};
 };
 
 // Delegates that host a series of "fake" effects used in unit tests and the
@@ -149,9 +152,16 @@
   int GetNumActivationsForTesting(int value);
 
  private:
+  // Adds a `std::unique_ptr<VcEffectState>` to `effect`.
+  void AddStateToEffect(VcHostedEffect* effect,
+                        int state_value,
+                        std::u16string label_text);
+
   // Number of times each value has been clicked, one count for each value in
   // `FurShagginess`.
   std::vector<int> num_activations_for_testing_;
+
+  base::WeakPtrFactory<ShaggyFurEffect> weak_factory_{this};
 };
 
 class ASH_EXPORT SuperCutnessEffect : public VcEffectsDelegate {
@@ -180,9 +190,16 @@
   int GetNumActivationsForTesting(int value);
 
  private:
+  // Adds a `std::unique_ptr<VcEffectState>` to `effect`.
+  void AddStateToEffect(VcHostedEffect* effect,
+                        int state_value,
+                        std::u16string label_text);
+
   // Number of times each value has been clicked, one count for each value in
   // `HowCute`.
   std::vector<int> num_activations_for_testing_;
+
+  base::WeakPtrFactory<SuperCutnessEffect> weak_factory_{this};
 };
 
 // A simple residence for any fake effects used for testing. For all of these
diff --git a/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
index 5f77269..c6b4476 100644
--- a/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.cc
@@ -15,16 +15,21 @@
 VcEffectState::VcEffectState(const gfx::VectorIcon* icon,
                              const std::u16string& label_text,
                              int accessible_name_id,
-                             views::Button::PressedCallback button_callback)
+                             views::Button::PressedCallback button_callback,
+                             absl::optional<int> state /*= absl::nullopt*/)
     : icon_(icon),
       label_text_(label_text),
       accessible_name_id_(accessible_name_id),
-      button_callback_(button_callback) {}
+      button_callback_(button_callback),
+      state_(state) {}
 
 VcEffectState::~VcEffectState() = default;
 
-VcHostedEffect::VcHostedEffect(VcEffectType type)
-    : type_(type), id_(VcEffectState::kUnusedId) {}
+VcHostedEffect::VcHostedEffect(VcEffectType type,
+                               GetEffectStateCallback get_state_callback)
+    : type_(type),
+      get_state_callback_(get_state_callback),
+      id_(VcEffectState::kUnusedId) {}
 
 VcHostedEffect::~VcHostedEffect() = default;
 
diff --git a/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h
index 94ecf838d..6706ce42 100644
--- a/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h
@@ -36,16 +36,21 @@
   //
   // `button_callback` - A callback that's invoked when the user sets the effect
   // to this state.
+  //
+  // `state` - The actual state value. Optional because only certain types of
+  // effects (e.g. set-value) actually need it.
   VcEffectState(const gfx::VectorIcon* icon,
                 const std::u16string& label_text,
                 int accessible_name_id,
-                views::Button::PressedCallback button_callback);
+                views::Button::PressedCallback button_callback,
+                absl::optional<int> state = absl::nullopt);
 
   VcEffectState(const VcEffectState&) = delete;
   VcEffectState& operator=(const VcEffectState&) = delete;
 
   ~VcEffectState();
 
+  absl::optional<int> state() const { return state_; }
   const gfx::VectorIcon* icon() const { return icon_; }
   const std::u16string& label_text() const { return label_text_; }
   int accessible_name_id() const { return accessible_name_id_; }
@@ -68,6 +73,9 @@
   // with the effect's ID and the actual (integer) value (e.g.
   // kBackgroundBlurMedium) member as arguments.
   views::Button::PressedCallback button_callback_;
+
+  // The state value.
+  absl::optional<int> state_;
 };
 
 // Designates the type of user-adjustments made to this effect.
@@ -92,8 +100,13 @@
     kOn = 1,
   };
 
+  // Callback for obtaining the current state of the effect. The callback must
+  // have the effect ID bound as an argument.
+  using GetEffectStateCallback = base::RepeatingCallback<int(void)>;
+
   // `type` is the type of value adjustment allowed.
-  explicit VcHostedEffect(VcEffectType type);
+  explicit VcHostedEffect(VcEffectType type,
+                          GetEffectStateCallback get_state_callback);
 
   VcHostedEffect(const VcHostedEffect&) = delete;
   VcHostedEffect& operator=(const VcHostedEffect&) = delete;
@@ -111,6 +124,9 @@
 
   VcEffectType type() const { return type_; }
   void set_id(int id) { id_ = id; }
+  const GetEffectStateCallback& get_state_callback() const {
+    return get_state_callback_;
+  }
   int id() const { return id_; }
   void set_label_text(const std::u16string label_text) {
     label_text_ = label_text;
@@ -122,10 +138,14 @@
   // user-supplied ID.
   VcEffectType type_;
 
+  // Callback supplied by the parent `VcEffectsDelegate`, for obtaining the
+  // state of the effect.
+  GetEffectStateCallback get_state_callback_;
+
   // Unique ID of the effect, if desired.
   int id_;
 
-  // Label text for the effect itself (that's separate from the label text of
+  // Label text for the effect (that's separate from the label text of
   // individual child states).
   std::u16string label_text_;
 
diff --git a/ash/webui/common/resources/network_health/network_diagnostics.html b/ash/webui/common/resources/network_health/network_diagnostics.html
index 16d6f7f..8625e02 100644
--- a/ash/webui/common/resources/network_health/network_diagnostics.html
+++ b/ash/webui/common/resources/network_health/network_diagnostics.html
@@ -20,8 +20,6 @@
     routines=
         "[[getRoutineGroup_(routines_.*, RoutineGroup_.GOOGLE_SERVICES)]]">
 </routine-group>
-<template is="dom-if" if="[[areArcNetworkingRoutinesEnabled_]]" restamp>
-    <routine-group  name="[[i18n('NetworkDiagnosticsArcGroup')]]"
-        routines="[[getRoutineGroup_(routines_.*, RoutineGroup_.ARC)]]">
-    </routine-group>
-</template>
+<routine-group  name="[[i18n('NetworkDiagnosticsArcGroup')]]"
+    routines="[[getRoutineGroup_(routines_.*, RoutineGroup_.ARC)]]">
+</routine-group>
diff --git a/ash/webui/common/resources/network_health/network_diagnostics.js b/ash/webui/common/resources/network_health/network_diagnostics.js
index d6b02f2f..5d3f5ca 100644
--- a/ash/webui/common/resources/network_health/network_diagnostics.js
+++ b/ash/webui/common/resources/network_health/network_diagnostics.js
@@ -5,7 +5,6 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import './routine_group.js';
 
-import {loadTimeData} from '//resources/ash/common/load_time_data.m.js';
 import {Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {I18nBehavior} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {ArcDnsResolutionProblem, ArcHttpProblem, ArcPingProblem, CaptivePortalProblem, DnsLatencyProblem, DnsResolutionProblem, DnsResolverPresentProblem, GatewayCanBePingedProblem, HasSecureWiFiConnectionProblem, HttpFirewallProblem, HttpsFirewallProblem, HttpsLatencyProblem, RoutineProblems, RoutineType, RoutineVerdict, SignalStrengthProblem, VideoConferencingProblem} from 'chrome://resources/mojo/chromeos/services/network_health/public/mojom/network_diagnostics.mojom-webui.js';
@@ -48,14 +47,6 @@
   ],
 
   properties: {
-    /** @private */
-    areArcNetworkingRoutinesEnabled_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean('enableArcNetworkDiagnostics');
-      },
-    },
-
     /**
      * List of Diagnostics Routines
      * @private {!Array<!Routine>}
@@ -166,9 +157,7 @@
               },
             ],
           },
-        ];
-        if (this.areArcNetworkingRoutinesEnabled_) {
-          routineGroups.push({
+          {
             group: RoutineGroup.ARC,
             routines: [
               {
@@ -188,8 +177,8 @@
                     getNetworkDiagnosticsService().runArcDnsResolution(),
               },
             ],
-          });
-        }
+          },
+        ];
         const routines = [];
 
         for (const group of routineGroups) {
diff --git a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider.cc b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider.cc
index 6bef6f26..798fc45 100644
--- a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider.cc
+++ b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider.cc
@@ -7,7 +7,6 @@
 #include <string>
 #include <utility>
 
-#include "ash/constants/ash_features.h"
 #include "ash/system/diagnostics/networking_log.h"
 #include "ash/webui/diagnostics_ui/backend/common/histogram_util.h"
 #include "base/bind.h"
@@ -632,7 +631,6 @@
 
 void NetworkHealthProvider::BindInterface(
     mojo::PendingReceiver<mojom::NetworkHealthProvider> pending_receiver) {
-  DCHECK(features::IsNetworkingInDiagnosticsAppEnabled());
   receiver_.reset();
   receiver_.Bind(std::move(pending_receiver));
 }
diff --git a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
index 638215d..efe5179 100644
--- a/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
+++ b/ash/webui/diagnostics_ui/backend/connectivity/network_health_provider_unittest.cc
@@ -6,14 +6,11 @@
 
 #include <utility>
 
-#include "ash/constants/ash_features.h"
 #include "ash/system/diagnostics/networking_log.h"
 #include "ash/webui/diagnostics_ui/backend/common/histogram_util.h"
-#include "base/feature_list.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/ptr_util.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h"
@@ -1477,8 +1474,6 @@
   // This test simulates a user refreshing the WebUI page. The receiver should
   // be reset before binding the new receiver. Otherwise we would get a DCHECK
   // error from mojo::Receiver
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kEnableNetworkingInDiagnosticsApp);
   mojo::Remote<mojom::NetworkHealthProvider> remote;
   network_health_provider_->BindInterface(remote.BindNewPipeAndPassReceiver());
   base::RunLoop().RunUntilIdle();
diff --git a/ash/webui/diagnostics_ui/backend/diagnostics_manager.cc b/ash/webui/diagnostics_ui/backend/diagnostics_manager.cc
index 66215c3d..354165a 100644
--- a/ash/webui/diagnostics_ui/backend/diagnostics_manager.cc
+++ b/ash/webui/diagnostics_ui/backend/diagnostics_manager.cc
@@ -27,11 +27,8 @@
         DiagnosticsLogController::Get()->GetTelemetryLog());
     system_routine_controller_ = std::make_unique<SystemRoutineController>(
         DiagnosticsLogController::Get()->GetRoutineLog());
-
-    if (features::IsNetworkingInDiagnosticsAppEnabled()) {
-      network_health_provider_ = std::make_unique<NetworkHealthProvider>(
-          DiagnosticsLogController::Get()->GetNetworkingLog());
-    }
+    network_health_provider_ = std::make_unique<NetworkHealthProvider>(
+        DiagnosticsLogController::Get()->GetNetworkingLog());
   } else {
     // TODO(b/226574520): Remove else block as part of DiagnosticsLogController
     // flag clean up.
@@ -39,18 +36,14 @@
         session_log_handler->GetTelemetryLog());
     system_routine_controller_ = std::make_unique<SystemRoutineController>(
         session_log_handler->GetRoutineLog());
-
-    if (features::IsNetworkingInDiagnosticsAppEnabled()) {
-      network_health_provider_ = std::make_unique<NetworkHealthProvider>(
-          session_log_handler->GetNetworkingLog());
-    }
+    network_health_provider_ = std::make_unique<NetworkHealthProvider>(
+        session_log_handler->GetNetworkingLog());
   }
 }
 
 DiagnosticsManager::~DiagnosticsManager() = default;
 
 NetworkHealthProvider* DiagnosticsManager::GetNetworkHealthProvider() const {
-  DCHECK(features::IsNetworkingInDiagnosticsAppEnabled());
   return network_health_provider_.get();
 }
 
diff --git a/ash/webui/diagnostics_ui/backend/session_log_async_helper.cc b/ash/webui/diagnostics_ui/backend/session_log_async_helper.cc
index aafa214..356389b4 100644
--- a/ash/webui/diagnostics_ui/backend/session_log_async_helper.cc
+++ b/ash/webui/diagnostics_ui/backend/session_log_async_helper.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "ash/system/diagnostics/networking_log.h"
 #include "ash/system/diagnostics/routine_log.h"
 #include "ash/system/diagnostics/telemetry_log.h"
@@ -67,20 +66,20 @@
   // Add the routine section for the system category.
   pieces.push_back(GetRoutineResultsString(system_routines));
 
-  if (features::IsNetworkingInDiagnosticsAppEnabled()) {
-    // Add networking category.
-    pieces.push_back(kNetworkingLogSectionHeader);
+  // Add networking category.
+  pieces.push_back(kNetworkingLogSectionHeader);
 
-    // Add the network info section.
-    if (networking_log)
-      pieces.push_back(networking_log->GetNetworkInfo());
+  // Add the network info section.
+  if (networking_log) {
+    pieces.push_back(networking_log->GetNetworkInfo());
+  }
 
-    // Add the routine section for the network category.
-    pieces.push_back(GetRoutineResultsString(network_routines));
+  // Add the routine section for the network category.
+  pieces.push_back(GetRoutineResultsString(network_routines));
 
-    // Add the network events section.
-    if (networking_log)
-      pieces.push_back(networking_log->GetNetworkEvents());
+  // Add the network events section.
+  if (networking_log) {
+    pieces.push_back(networking_log->GetNetworkEvents());
   }
 
   return base::WriteFile(file_path, base::JoinString(pieces, "\n"));
diff --git a/ash/webui/diagnostics_ui/diagnostics_ui.cc b/ash/webui/diagnostics_ui/diagnostics_ui.cc
index 6ab470e..2a659bd 100644
--- a/ash/webui/diagnostics_ui/diagnostics_ui.cc
+++ b/ash/webui/diagnostics_ui/diagnostics_ui.cc
@@ -378,14 +378,10 @@
   source->AddBoolean("isLoggedIn", LoginState::Get()->IsUserLoggedIn());
   source->AddBoolean("isInputEnabled",
                      features::IsInputInDiagnosticsAppEnabled());
-  source->AddBoolean("isNetworkingEnabled",
-                     features::IsNetworkingInDiagnosticsAppEnabled());
   source->AddBoolean("isTouchpadEnabled",
                      features::IsTouchpadInDiagnosticsAppEnabled());
   source->AddBoolean("isTouchscreenEnabled",
                      features::IsTouchscreenInDiagnosticsAppEnabled());
-  source->AddBoolean("enableArcNetworkDiagnostics",
-                     features::IsArcNetworkDiagnosticsButtonEnabled());
 }
 
 void SetUpPluralStringHandler(content::WebUI* web_ui) {
@@ -457,12 +453,10 @@
 
 void DiagnosticsDialogUI::BindInterface(
     mojo::PendingReceiver<diagnostics::mojom::NetworkHealthProvider> receiver) {
-  if (features::IsNetworkingInDiagnosticsAppEnabled()) {
-    diagnostics::NetworkHealthProvider* network_health_provider =
-        diagnostics_manager_->GetNetworkHealthProvider();
-    if (network_health_provider) {
-      network_health_provider->BindInterface(std::move(receiver));
-    }
+  diagnostics::NetworkHealthProvider* network_health_provider =
+      diagnostics_manager_->GetNetworkHealthProvider();
+  if (network_health_provider) {
+    network_health_provider->BindInterface(std::move(receiver));
   }
 }
 
diff --git a/ash/webui/diagnostics_ui/resources/connectivity_card.ts b/ash/webui/diagnostics_ui/resources/connectivity_card.ts
index 4dd568f..acf1ea5 100644
--- a/ash/webui/diagnostics_ui/resources/connectivity_card.ts
+++ b/ash/webui/diagnostics_ui/resources/connectivity_card.ts
@@ -9,7 +9,6 @@
 import './network_info.js';
 import './routine_section.js';
 
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -54,12 +53,12 @@
       activeGuid: {
         type: String,
         value: '',
-        observer: 'activeGuidChanged_',
+        observer: ConnectivityCardElement.prototype.activeGuidChanged_,
       },
 
       isActive: {
         type: Boolean,
-        observer: 'isActiveChanged_',
+        observer: ConnectivityCardElement.prototype.isActiveChanged_,
       },
 
       network: {
@@ -135,9 +134,7 @@
     this.macAddress_ = network.macAddress || '';
 
     if (this.testSuiteStatus === TestSuiteStatus.NOT_RUNNING) {
-      const isArcEnabled =
-          loadTimeData.getBoolean('enableArcNetworkDiagnostics');
-      this.routineGroups_ = getRoutineGroups(network.type, isArcEnabled);
+      this.routineGroups_ = getRoutineGroups(network.type);
       this.getRoutineSectionElem_().runTests();
     }
 
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_utils.ts b/ash/webui/diagnostics_ui/resources/diagnostics_utils.ts
index 89cf0163..005022e 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_utils.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_utils.ts
@@ -104,12 +104,12 @@
   return {routine, blocking};
 }
 
-export function getRoutineGroups(
-    type: NetworkType, isArcEnabled: boolean): RoutineGroup[] {
+export function getRoutineGroups(type: NetworkType): RoutineGroup[] {
   const localNetworkGroup = new RoutineGroup(
       [
         createRoutine(RoutineType.kGatewayCanBePinged, false),
         createRoutine(RoutineType.kLanConnectivity, true),
+        createRoutine(RoutineType.kArcPing, false),
       ],
       'localNetworkGroupLabel');
 
@@ -118,6 +118,7 @@
         createRoutine(RoutineType.kDnsResolverPresent, true),
         createRoutine(RoutineType.kDnsResolution, true),
         createRoutine(RoutineType.kDnsLatency, true),
+        createRoutine(RoutineType.kArcDnsResolution, false),
       ],
       'nameResolutionGroupLabel');
 
@@ -133,18 +134,10 @@
         createRoutine(RoutineType.kHttpsFirewall, true),
         createRoutine(RoutineType.kHttpFirewall, true),
         createRoutine(RoutineType.kHttpsLatency, true),
+        createRoutine(RoutineType.kArcHttp, false),
       ],
       'internetConnectivityGroupLabel');
 
-  if (isArcEnabled) {
-    // Add ARC routines to their corresponding groups.
-    nameResolutionGroup.addRoutine(
-        (createRoutine(RoutineType.kArcDnsResolution, false)));
-    localNetworkGroup.addRoutine((createRoutine(RoutineType.kArcPing, false)));
-    internetConnectivityGroup.addRoutine(
-        (createRoutine(RoutineType.kArcHttp, false)));
-  }
-
   const groupsToAdd = type === NetworkType.kWiFi ?
       [wifiGroup, internetConnectivityGroup] :
       [internetConnectivityGroup];
@@ -187,10 +180,6 @@
   return pieces.join('.');
 }
 
-export function isNavEnabled(): boolean {
-  return loadTimeData.getBoolean('isNetworkingEnabled');
-}
-
 
 export function formatMacAddress(macAddress: string): string {
   return `${loadTimeData.getString('macAddressLabel')}: ${macAddress}`;
diff --git a/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.js b/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.js
index 0f319c51..61b8458 100644
--- a/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.js
+++ b/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.js
@@ -11,7 +11,6 @@
 import {assert} from 'chrome://resources/ash/common/assert.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {isNavEnabled} from './diagnostics_utils.js';
 import {getTemplate} from './realtime_cpu_chart.html.js';
 
 /**
@@ -187,9 +186,8 @@
   updateChartWidth_() {
     // parseFloat() is used to convert the string returned by
     // getComputedStyleValue() into a number ("642px" --> 642).
-    const chartVar = isNavEnabled() ? '--chart-width-nav' : '--chart-width';
-    this.width_ =
-        parseFloat(window.getComputedStyle(this).getPropertyValue(chartVar));
+    this.width_ = parseFloat(
+        window.getComputedStyle(this).getPropertyValue('--chart-width-nav'));
   }
 
   /**
diff --git a/ash/webui/diagnostics_ui/resources/system_page.html b/ash/webui/diagnostics_ui/resources/system_page.html
index c0a24e0..8896fca 100644
--- a/ash/webui/diagnostics_ui/resources/system_page.html
+++ b/ash/webui/diagnostics_ui/resources/system_page.html
@@ -1,23 +1,4 @@
 <style include="cr-shared-style diagnostics-shared">
-  #banner {
-   align-items: center;
-   background-color: var(--google-blue-50);
-   display: flex;
-   height: 56px;
-   position: sticky;
-   top: 0;
-   width: 100vw;
-   z-index: 1;
- }
-
- #bannerIcon {
-   --iron-icon-height: 20px;
-   --iron-icon-width: 20px;
-   fill: var(--cros-icon-color-prominent);
-   padding-inline-end: 16px;
-   padding-inline-start: 40px;
- }
-
  #diagnosticsContainer {
    align-items: center;
    box-sizing: border-box;
@@ -34,10 +15,6 @@
    right: 4px;
  }
 
- .elevation-2 {
-   box-shadow: var(--diagnostics-box-shadow-elevation-2);
- }
-
  #header {
    align-self: flex-start;
    color: var(--cros-text-color-primary);
@@ -64,17 +41,10 @@
  }
 </style>
 <div id="diagnosticsContainer" hidden="[[!systemInfoReceived_]]">
-  <div id="header" hidden$="[[isNetworkingEnabled]]">
-    [[i18n('diagnosticsTitle')]]
-  </div>
-  <div id="banner" hidden="[[!bannerMessage]]" class$="[[scrollingClass_]]">
-    <iron-icon icon="diagnostics:info" id="bannerIcon"></iron-icon>
-    <span id="bannerMsg" class="diagnostics-caution-banner-font">[[bannerMessage]]</span>
-  </div>
   <div class="overview-container">
     <overview-card id="overviewCard"></overview-card>
   </div>
-  <div class$="[[getCardContainerClass_(isNetworkingEnabled)]]">
+  <div class$="diagnostics-cards-container-nav">
     <template is="dom-if" if="[[showBatteryStatusCard_]]" restamp>
       <div class="card-width">
         <battery-status-card id="batteryStatusCard"
@@ -96,15 +66,6 @@
       </memory-card>
     </div>
   </div>
-  <template is="dom-if" if="[[!isNetworkingEnabled]]">
-    <div class="session-log-container">
-      <cr-button on-click="onSessionLogClick_" class="session-log-button"
-          disabled="[[!saveSessionLogEnabled_]]" hidden="[[!isLoggedIn_]]">
-        <iron-icon icon="diagnostics:download" id="download-icon"></iron-icon>
-        <span>[[i18n('sessionLog')]]</span>
-      </cr-button>
-    </div>
-  </template>
   <cr-toast id="toast" duration="2500">
     <span>[[toastText_]]</span>
   </cr-toast>
diff --git a/ash/webui/diagnostics_ui/resources/system_page.ts b/ash/webui/diagnostics_ui/resources/system_page.ts
index ed5eead..12f68e1 100644
--- a/ash/webui/diagnostics_ui/resources/system_page.ts
+++ b/ash/webui/diagnostics_ui/resources/system_page.ts
@@ -20,7 +20,6 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DiagnosticsBrowserProxyImpl} from './diagnostics_browser_proxy.js';
-import {ShowCautionBannerEvent} from './diagnostics_sticky_banner.js';
 import {getSystemDataProvider} from './mojo_interface_provider.js';
 import {OverviewCardElement} from './overview_card.js';
 import {TestSuiteStatus} from './routine_list_executor.js';
@@ -82,45 +81,21 @@
         value: loadTimeData.getBoolean('isLoggedIn'),
       },
 
-      bannerMessage: {
-        type: String,
-        value: '',
-      },
-
-      scrollingClass_: {
-        type: String,
-        value: '',
-      },
-
-      scrollTimerId_: {
-        type: Number,
-        value: -1,
-      },
-
       isActive: {
         type: Boolean,
         value: true,
       },
 
-      isNetworkingEnabled: {
-        type: Boolean,
-        value: loadTimeData.getBoolean('isNetworkingEnabled'),
-      },
-
     };
   }
 
   testSuiteStatus: TestSuiteStatus;
-  bannerMessage: string;
   isActive: boolean;
-  isNetworkingEnabled: boolean;
   protected systemInfoReceived_: boolean;
   protected saveSessionLogEnabled_: boolean;
   private showBatteryStatusCard_: boolean;
   private toastText_: string;
   private isLoggedIn_: boolean;
-  private scrollingClass_: string;
-  private scrollTimerId_: number;
   private systemDataProvider_: SystemDataProviderInterface =
       getSystemDataProvider();
   private browserProxy_: DiagnosticsBrowserProxyImpl =
@@ -130,11 +105,6 @@
     super();
     this.fetchSystemInfo_();
     this.browserProxy_.initialize();
-
-    // Only use inner banner behavior if system page is in stand-alone mode.
-    if (!this.isNetworkingEnabled) {
-      this.addCautionBannerEventListeners_();
-    }
   }
 
   private fetchSystemInfo_(): void {
@@ -176,35 +146,6 @@
         });
   }
 
-  private addCautionBannerEventListeners_(): void {
-    window.addEventListener('show-caution-banner', (e) => {
-      const event = e as ShowCautionBannerEvent;
-      assert(event.detail.message);
-      this.bannerMessage = event.detail.message;
-    });
-
-    window.addEventListener('dismiss-caution-banner', () => {
-      this.bannerMessage = '';
-    });
-
-    window.addEventListener('scroll', () => {
-      if (!this.bannerMessage) {
-        return;
-      }
-
-      // Reset timer since we've received another 'scroll' event.
-      if (this.scrollTimerId_ !== -1) {
-        this.scrollingClass_ = 'elevation-2';
-        clearTimeout(this.scrollTimerId_);
-      }
-
-      // Remove box shadow from banner since the user has stopped scrolling
-      // for at least 300ms.
-      this.scrollTimerId_ =
-          window.setTimeout(() => this.scrollingClass_ = '', 300);
-    });
-  }
-
   /**
    * 'navigation-view-panel' is responsible for calling this function when
    * the active page changes.
@@ -225,11 +166,6 @@
       this.browserProxy_.recordNavigation('system');
     }
   }
-
-  protected getCardContainerClass_(): string {
-    const cardContainer = 'diagnostics-cards-container';
-    return `${cardContainer}${this.isNetworkingEnabled ? '-nav' : ''}`;
-  }
 }
 
 customElements.define(SystemPageElement.is, SystemPageElement);
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
index ceacb8dc..1b4647011 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
@@ -65,7 +65,7 @@
       installationProgress: {
         type: Object,
         value: {percentage: 0, state: UpdateState.kIdle},
-        observer: 'progressChanged_',
+        observer: FirmwareUpdateDialogElement.prototype.progressChanged_,
       },
 
       /** @private {boolean} */
diff --git a/ash/webui/network_ui/network_diagnostics_resource_provider.cc b/ash/webui/network_ui/network_diagnostics_resource_provider.cc
index cceab7bcd..8180cff 100644
--- a/ash/webui/network_ui/network_diagnostics_resource_provider.cc
+++ b/ash/webui/network_ui/network_diagnostics_resource_provider.cc
@@ -4,7 +4,6 @@
 
 #include "ash/webui/network_ui/network_diagnostics_resource_provider.h"
 
-#include "ash/constants/ash_features.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "ui/base/webui/web_ui_util.h"
@@ -156,9 +155,6 @@
 
 void AddResources(content::WebUIDataSource* html_source) {
   html_source->AddLocalizedStrings(kLocalizedStrings);
-  html_source->AddBoolean(
-      "enableArcNetworkDiagnostics",
-      ash::features::IsArcNetworkDiagnosticsButtonEnabled());
 
   for (const auto& resource : kResources)
     html_source->AddResourcePath(resource.name, resource.id);
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.js b/ash/webui/os_feedback_ui/resources/share_data_page.js
index bbcb11727..59abb513 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.js
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.js
@@ -47,7 +47,7 @@
         type: FeedbackContext,
         readOnly: false,
         notify: true,
-        observer: 'onFeedbackContextChanged_',
+        observer: ShareDataPageElement.prototype.onFeedbackContextChanged_,
       },
 
       screenshotUrl: {type: String, readOnly: false, notify: true},
diff --git a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
index 07f0745..7ce3b55 100644
--- a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
+++ b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
@@ -125,6 +125,8 @@
   }
 
   automaticSeedColorEnabled: boolean;
+  private previousStaticColorSelected_: SkColor|null;
+  private previousColorSchemeSelected_: ColorScheme|null;
   private staticColorSelected_: SkColor|null;
   private colorSchemeSelected_: ColorScheme|null;
   private staticColors_: string[];
@@ -164,10 +166,14 @@
 
   private onToggleChanged_() {
     if (this.automaticSeedColorEnabled) {
-      const staticColor = this.staticColorSelected_ || DEFAULT_STATIC_COLOR;
+      this.previousColorSchemeSelected_ = this.colorSchemeSelected_;
+      const staticColor =
+          this.previousStaticColorSelected_ || DEFAULT_STATIC_COLOR;
       setStaticColorPref(staticColor, getThemeProvider(), this.getStore());
     } else {
-      const colorScheme = this.colorSchemeSelected_ || DEFAULT_COLOR_SCHEME;
+      this.previousStaticColorSelected_ = this.staticColorSelected_;
+      const colorScheme =
+          this.previousColorSchemeSelected_ || DEFAULT_COLOR_SCHEME;
       setColorSchemePref(colorScheme, getThemeProvider(), this.getStore());
     }
   }
diff --git a/ash/webui/shortcut_customization_ui/resources/index.html b/ash/webui/shortcut_customization_ui/resources/index.html
index 4b5fb1bd..2bcc39c 100644
--- a/ash/webui/shortcut_customization_ui/resources/index.html
+++ b/ash/webui/shortcut_customization_ui/resources/index.html
@@ -7,6 +7,10 @@
     <title></title>
     <meta charset="utf-8">
     <style>
+      html {
+        background-color: var(--cros-bg-color);
+      }
+
       body {
         margin: 0;
       }
diff --git a/ash/webui/shortcut_customization_ui/resources/js/input_key.html b/ash/webui/shortcut_customization_ui/resources/js/input_key.html
index 222a906..80c07b6 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/input_key.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/input_key.html
@@ -3,7 +3,6 @@
     align-items: center;
     border-radius: 5px;
     box-sizing: border-box;
-    color: var(--cros-text-color-secondary);
     display: flex;
     font-weight: 500;
     height: 28px;
@@ -13,6 +12,10 @@
     padding-inline: 8px;
   }
 
+  #key-text {
+    color: var(--cros-text-color-secondary);
+  }
+
   :host([key-state='not-selected']) .key-container {
     border-color: var(--google-grey-200);
     border-style: solid;
@@ -21,15 +24,15 @@
   }
 
   :host([key-state='modifier-selected']) .key-container {
-    background-color: var(--google-blue-50);
+    background-color: var(--cros-highlight-color);
     border: none;
-    box-shadow: 0 1px 1px var(--google-blue-50);
+    box-shadow: 0 1px 1px var(--cros-highlight-color);
   }
 
   :host([key-state='alpha-numeric-selected']) .key-container {
-    background-color: var(--google-grey-100);
+    background-color: var(--cros-bg-color-dropped-elevation-1);
     border: none;
-    box-shadow: 0 1px 1px var(--google-grey-100);
+    box-shadow: 0 1px 1px var(--cros-bg-color-dropped-elevation-1);
   }
 
   :host(#ctrlKey) .key-container, 
diff --git a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
index 5d4490e..060ff895 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
@@ -23,14 +23,14 @@
 
   #keyboardSettingsLinkContainer a {
     align-items: center;
-    color: var(--cros-text-color-secondary);
+    color: var(--cros-link-color);
     display: flex;
     font-weight: var(--shortcuts-font-weight-medium);
     text-decoration: none;
   }
 
   #keyboardSettingsLinkContainer a iron-icon {
-    fill: var(--cros-text-color-secondary);
+    fill: var(--cros-link-color);
     margin-inline-start: 4px;
   }
 
diff --git a/ash/wm/float/COMMON_METADATA b/ash/wm/float/COMMON_METADATA
new file mode 100644
index 0000000..81be4693
--- /dev/null
+++ b/ash/wm/float/COMMON_METADATA
@@ -0,0 +1,10 @@
+team_email: "chromeos-wm-corexp@google.com"
+buganizer: {
+  component_id:1252568
+}
+buganizer_public: {
+  component_id:1253054
+}
+monorail {
+  component: "UI>Shell>WindowManager>FloatingWindow"
+}
\ No newline at end of file
diff --git a/ash/wm/float/DIR_METADATA b/ash/wm/float/DIR_METADATA
index c1b5a4b..1ecfe34 100644
--- a/ash/wm/float/DIR_METADATA
+++ b/ash/wm/float/DIR_METADATA
@@ -1,10 +1 @@
-team_email: "chromeos-wm-corexp@google.com"
-buganizer: {
-  component_id:1252568
-}
-buganizer_public: {
-  component_id:1253054
-}
-monorail {
-  component: "UI>Shell>WindowManager>FloatingWindow"
-}
+mixins: "//ash/wm/float/COMMON_METADATA"
diff --git a/ash/wm/float/OWNERS b/ash/wm/float/OWNERS
index fe8e5fb..3ec8fe5 100644
--- a/ash/wm/float/OWNERS
+++ b/ash/wm/float/OWNERS
@@ -1 +1,2 @@
+sammiequon@chromium.org
 shidi@chromium.org
diff --git a/ash/wm/tablet_mode/OWNERS b/ash/wm/tablet_mode/OWNERS
index 2656212c..34200d8 100644
--- a/ash/wm/tablet_mode/OWNERS
+++ b/ash/wm/tablet_mode/OWNERS
@@ -1 +1,3 @@
 xdai@chromium.org
+
+per-file *multitask*=file://ash/wm/float/OWNERS
diff --git a/base/values.cc b/base/values.cc
index 0f64764..0d7e0e5 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -218,20 +218,6 @@
 
 Value::Value(List&& value) noexcept : data_(std::move(value)) {}
 
-Value::Value(const LegacyDictStorage& storage)
-    : data_(absl::in_place_type_t<Dict>()) {
-  dict().reserve(storage.size());
-  for (const auto& it : storage) {
-    dict().try_emplace(dict().end(), it.first,
-                       std::make_unique<Value>(it.second->Clone()));
-  }
-}
-
-Value::Value(LegacyDictStorage&& storage) noexcept
-    : data_(absl::in_place_type_t<Dict>()) {
-  dict() = std::move(storage);
-}
-
 Value::Value(absl::monostate) {}
 
 Value::Value(DoubleStorage storage) : data_(std::move(storage)) {}
@@ -1595,12 +1581,6 @@
 
 DictionaryValue::DictionaryValue() : Value(Type::DICTIONARY) {}
 
-DictionaryValue::DictionaryValue(const LegacyDictStorage& storage)
-    : Value(storage) {}
-
-DictionaryValue::DictionaryValue(LegacyDictStorage&& storage) noexcept
-    : Value(std::move(storage)) {}
-
 Value* DictionaryValue::Set(StringPiece path, std::unique_ptr<Value> in_value) {
   DCHECK(IsStringUTF8AllowingNoncharacters(path));
   DCHECK(in_value);
diff --git a/base/values.h b/base/values.h
index 8929779..1d0effd 100644
--- a/base/values.h
+++ b/base/values.h
@@ -1093,10 +1093,6 @@
   const ListStorage& list() const { return GetList().storage_; }
   ListStorage& list() { return GetList().storage_; }
 
-  // Internal constructors, allowing the simplify the implementation of Clone().
-  explicit Value(const LegacyDictStorage& storage);
-  explicit Value(LegacyDictStorage&& storage) noexcept;
-
  private:
   // For access to DoubleStorage.
   friend class ValueView;
@@ -1255,8 +1251,6 @@
   static std::unique_ptr<DictionaryValue> From(std::unique_ptr<Value> value);
 
   DictionaryValue();
-  explicit DictionaryValue(const LegacyDictStorage& in_dict);
-  explicit DictionaryValue(LegacyDictStorage&& in_dict) noexcept;
 
   // Sets the Value associated with the given path starting from this object.
   // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes
diff --git a/build/android/pylib/local/emulator/avd.py b/build/android/pylib/local/emulator/avd.py
index 45baea5..8e61a98 100644
--- a/build/android/pylib/local/emulator/avd.py
+++ b/build/android/pylib/local/emulator/avd.py
@@ -357,6 +357,29 @@
   def _features_ini_path(self):
     return os.path.join(self.emulator_home, 'advancedFeatures.ini')
 
+  @property
+  def xdg_config_dir(self):
+    """The base directory to store qt config file.
+
+    This dir should be added to the env variable $XDG_CONFIG_DIRS so that
+    _qt_config_path can take effect. See https://bit.ly/3HIQRZ3 for context.
+    """
+    config_dir = os.path.join(self.emulator_home, '.config')
+    if not os.path.exists(config_dir):
+      os.makedirs(config_dir)
+
+    return config_dir
+
+  @property
+  def _qt_config_path(self):
+    """The qt config file for emulator."""
+    qt_config_dir = os.path.join(self.xdg_config_dir,
+                                 'Android Open Source Project')
+    if not os.path.exists(qt_config_dir):
+      os.makedirs(qt_config_dir)
+
+    return os.path.join(qt_config_dir, 'Emulator.conf')
+
   def HasSnapshot(self, snapshot_name):
     """Check if a given snapshot exists or not."""
     snapshot_path = os.path.join(self._avd_dir, 'snapshots', snapshot_name)
@@ -731,16 +754,17 @@
      * Emulator instance can be booted correctly.
      * The snapshot can be loaded successfully.
     """
+    logging.info('Updating AVD configurations.')
     # Update the absolute avd path in root_ini file
     with ini.update_ini_file(self._root_ini_path) as r_ini_contents:
       r_ini_contents['path'] = self._avd_dir
 
     # Update hardware settings.
-    config_files = [self._config_ini_path]
+    config_paths = [self._config_ini_path]
     # The file hardware.ini within each snapshot need to be updated as well.
     hw_ini_glob_pattern = os.path.join(self._avd_dir, 'snapshots', '*',
                                        'hardware.ini')
-    config_files.extend(glob.glob(hw_ini_glob_pattern))
+    config_paths.extend(glob.glob(hw_ini_glob_pattern))
 
     properties = {}
     # Update hw.sdCard.path if applicable
@@ -748,10 +772,14 @@
     if os.path.exists(sdcard_path):
       properties['hw.sdCard.path'] = sdcard_path
 
-    for config_file in config_files:
-      with ini.update_ini_file(config_file) as config_contents:
+    for config_path in config_paths:
+      with ini.update_ini_file(config_path) as config_contents:
         config_contents.update(properties)
 
+    # Create qt config file to disable adb warning when launched in window mode.
+    with ini.update_ini_file(self._qt_config_path) as config_contents:
+      config_contents['set'] = {'autoFindAdb': 'false'}
+
   def _Initialize(self):
     if self._initialized:
       return
@@ -882,9 +910,9 @@
       emulator_env = {
           # kill immediately when emulator hang.
           'ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL': '0',
+          # Sets the emulator configuration directory
+          'ANDROID_EMULATOR_HOME': self._emulator_home,
       }
-      if self._emulator_home:
-        emulator_env['ANDROID_EMULATOR_HOME'] = self._emulator_home
       if 'DISPLAY' in os.environ:
         emulator_env['DISPLAY'] = os.environ.get('DISPLAY')
       if window:
@@ -893,6 +921,12 @@
       else:
         emulator_cmd.append('-no-window')
 
+      # Need this for the qt config file to take effect.
+      xdg_config_dirs = [self._avd_config.xdg_config_dir]
+      if 'XDG_CONFIG_DIRS' in os.environ:
+        xdg_config_dirs.append(os.environ.get('XDG_CONFIG_DIRS'))
+      emulator_env['XDG_CONFIG_DIRS'] = ':'.join(xdg_config_dirs)
+
       sock.listen(1)
 
       logging.info('Starting emulator...')
diff --git a/build/android/pylib/local/emulator/ini.py b/build/android/pylib/local/emulator/ini.py
index 396874a..79eb015 100644
--- a/build/android/pylib/local/emulator/ini.py
+++ b/build/android/pylib/local/emulator/ini.py
@@ -2,22 +2,59 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Basic .ini encoding and decoding."""
+"""Basic .ini encoding and decoding.
+
+The basic element in an ini file is the key. Every key is constructed by a name
+and a value, delimited by an equals sign (=).
+
+Keys may be grouped into sections. The secetion name will be a line by itself,
+in square brackets ([ and ]). All keys after the section are associated with
+that section until another section occurs.
+
+Keys that are not under any section are considered at the top level.
+
+Section and key names are case sensitive.
+"""
 
 
 import contextlib
 import os
 
 
+def add_key(line, config, strict=True):
+  key, val = line.split('=', 1)
+  key = key.strip()
+  val = val.strip()
+  if strict and key in config:
+    raise ValueError('Multiple entries present for key "%s"' % key)
+  config[key] = val
+
+
 def loads(ini_str, strict=True):
+  """Deserialize int_str to a dict (nested dict when has sections) object.
+
+  Duplicated sections will merge their keys.
+
+  When there are multiple entries for a key, at the top level, or under the
+  same section:
+   - If strict is true, ValueError will be raised.
+   - If strict is false, only the last occurrence will be stored.
+  """
   ret = {}
+  section = None
   for line in ini_str.splitlines():
-    key, val = line.split('=', 1)
-    key = key.strip()
-    val = val.strip()
-    if strict and key in ret:
-      raise ValueError('Multiple entries present for key "%s"' % key)
-    ret[key] = val
+    # Empty line
+    if not line:
+      continue
+    # Section line
+    if line[0] == '[' and line[-1] == ']':
+      section = line[1:-1]
+      if section not in ret:
+        ret[section] = {}
+    # Key line
+    else:
+      config = ret if section is None else ret[section]
+      add_key(line, config, strict=strict)
 
   return ret
 
@@ -27,10 +64,20 @@
 
 
 def dumps(obj):
-  ret = ''
+  results = []
+  key_str = ''
+
   for k, v in sorted(obj.items()):
-    ret += '%s = %s\n' % (k, str(v))
-  return ret
+    if isinstance(v, dict):
+      results.append('[%s]\n' % k + dumps(v))
+    else:
+      key_str += '%s = %s\n' % (k, str(v))
+
+  # Insert key_str at the first position, before any sections
+  if key_str:
+    results.insert(0, key_str)
+
+  return '\n'.join(results)
 
 
 def dump(obj, fp):
@@ -46,11 +93,10 @@
   Yields:
     The contents of the file, as a dict
   """
+  ini_contents = {}
   if os.path.exists(ini_file_path):
     with open(ini_file_path) as ini_file:
       ini_contents = load(ini_file)
-  else:
-    ini_contents = {}
 
   yield ini_contents
 
diff --git a/build/android/pylib/local/emulator/ini_test.py b/build/android/pylib/local/emulator/ini_test.py
index 752a835..327d6bf7 100755
--- a/build/android/pylib/local/emulator/ini_test.py
+++ b/build/android/pylib/local/emulator/ini_test.py
@@ -5,9 +5,13 @@
 """Tests for ini.py."""
 
 
+import os
+import sys
 import textwrap
 import unittest
 
+sys.path.append(
+    os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
 from pylib.local.emulator import ini
 
 
@@ -17,15 +21,35 @@
         foo.bar = 1
         foo.baz= example
         bar.bad =/path/to/thing
+
+        [section_1]
+        foo.bar = 1
+        foo.baz= example
+
+        [section_2]
+        foo.baz= example
+        bar.bad =/path/to/thing
+
+        [section_1]
+        bar.bad =/path/to/thing
         """)
     expected = {
         'foo.bar': '1',
         'foo.baz': 'example',
         'bar.bad': '/path/to/thing',
+        'section_1': {
+            'foo.bar': '1',
+            'foo.baz': 'example',
+            'bar.bad': '/path/to/thing',
+        },
+        'section_2': {
+            'foo.baz': 'example',
+            'bar.bad': '/path/to/thing',
+        },
     }
     self.assertEqual(expected, ini.loads(ini_str))
 
-  def testLoadsStrictFailure(self):
+  def testLoadsDuplicatedKeysStrictFailure(self):
     ini_str = textwrap.dedent("""\
         foo.bar = 1
         foo.baz = example
@@ -35,17 +59,39 @@
     with self.assertRaises(ValueError):
       ini.loads(ini_str, strict=True)
 
+  def testLoadsDuplicatedKeysInSectionStrictFailure(self):
+    ini_str = textwrap.dedent("""\
+        [section_1]
+        foo.bar = 1
+        foo.baz = example
+        bar.bad = /path/to/thing
+        foo.bar = duplicate
+        """)
+    with self.assertRaises(ValueError):
+      ini.loads(ini_str, strict=True)
+
   def testLoadsPermissive(self):
     ini_str = textwrap.dedent("""\
         foo.bar = 1
         foo.baz = example
         bar.bad = /path/to/thing
         foo.bar = duplicate
+
+        [section_1]
+        foo.bar = 1
+        foo.baz = example
+        bar.bad = /path/to/thing
+        foo.bar = duplicate
         """)
     expected = {
         'foo.bar': 'duplicate',
         'foo.baz': 'example',
         'bar.bad': '/path/to/thing',
+        'section_1': {
+            'foo.bar': 'duplicate',
+            'foo.baz': 'example',
+            'bar.bad': '/path/to/thing',
+        },
     }
     self.assertEqual(expected, ini.loads(ini_str, strict=False))
 
@@ -54,13 +100,53 @@
         'foo.bar': '1',
         'foo.baz': 'example',
         'bar.bad': '/path/to/thing',
+        'section_2': {
+            'foo.baz': 'example',
+            'bar.bad': '/path/to/thing',
+        },
+        'section_1': {
+            'foo.bar': '1',
+            'foo.baz': 'example',
+        },
     }
     # ini.dumps is expected to dump to string alphabetically
-    # by key.
+    # by key and section name.
     expected = textwrap.dedent("""\
         bar.bad = /path/to/thing
         foo.bar = 1
         foo.baz = example
+
+        [section_1]
+        foo.bar = 1
+        foo.baz = example
+
+        [section_2]
+        bar.bad = /path/to/thing
+        foo.baz = example
+        """)
+    self.assertEqual(expected, ini.dumps(ini_contents))
+
+  def testDumpsSections(self):
+    ini_contents = {
+        'section_2': {
+            'foo.baz': 'example',
+            'bar.bad': '/path/to/thing',
+        },
+        'section_1': {
+            'foo.bar': '1',
+            'foo.baz': 'example',
+        },
+    }
+    # ini.dumps is expected to dump to string alphabetically
+    # by key first, and then by section and the associated keys
+    expected = textwrap.dedent("""\
+        [section_1]
+        foo.bar = 1
+        foo.baz = example
+
+        [section_2]
+        bar.bad = /path/to/thing
+        foo.baz = example
         """)
     self.assertEqual(expected, ini.dumps(ini_contents))
 
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index b2f7fc9..d272842 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -618,7 +618,7 @@
     }
   } else if (is_win) {
     cflags_c += [ "/std:c11" ]
-    if (use_cxx17 || (is_clang && defined(msvc_use_cxx17) && msvc_use_cxx17)) {
+    if (use_cxx17 || (!is_clang && defined(msvc_use_cxx17) && msvc_use_cxx17)) {
       cflags_cc += [ "/std:c++17" ]
     } else {
       cflags_cc += [ "/std:c++20" ]
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 4cc1d84..80e2d539 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-11.20221221.3.1
+11.20221222.0.1
diff --git a/chrome/VERSION b/chrome/VERSION
index 96bd757..a66d22f 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=111
 MINOR=0
-BUILD=5493
+BUILD=5494
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index d2175db..88ad8428 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2628,6 +2628,7 @@
       "java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java",
       "java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java",
       "java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerServices.java",
+      "java/src/org/chromium/chrome/browser/metrics/SimpleStartupForegroundSessionDetector.java",
       "java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
       "java/src/org/chromium/chrome/browser/notifications/NotificationJobService.java",
       "java/src/org/chromium/chrome/browser/notifications/NotificationService.java",
diff --git a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
index 613a19d..9d85afb 100644
--- a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
+++ b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
@@ -701,25 +701,25 @@
   return ++global_data.instance_num;
 }
 
-std::tuple<base::span<const uint8_t, device::kP256X962Length>,
-           base::span<const uint8_t, device::cablev2::kQRSecretSize>,
+std::tuple<std::array<uint8_t, device::kP256X962Length>,
+           std::array<uint8_t, device::cablev2::kQRSecretSize>,
            std::array<uint8_t, device::cablev2::kTunnelIdSize>>
 ParseServerLinkData(JNIEnv* env,
                     const JavaParamRef<jbyteArray>& server_link_data_java) {
-  constexpr size_t kDataSize =
-      device::kP256X962Length + device::cablev2::kQRSecretSize;
-  const std::vector<uint8_t> server_link_data_vec =
+  const std::vector<uint8_t> server_link_data =
       JavaByteArrayToByteVector(env, server_link_data_java);
   // validateServerLinkData should have been called to check this already.
-  CHECK_EQ(server_link_data_vec.size(), kDataSize);
-  base::span<const uint8_t> server_link_data =
-      base::make_span(server_link_data_vec);
+  CHECK_EQ(server_link_data.size(),
+           device::kP256X962Length + device::cablev2::kQRSecretSize);
 
-  const base::span<const uint8_t, device::kP256X962Length> peer_identity =
-      server_link_data.subspan<0, device::kP256X962Length>();
-  const base::span<const uint8_t, device::cablev2::kQRSecretSize> qr_secret =
-      server_link_data
-          .subspan<device::kP256X962Length, device::cablev2::kQRSecretSize>();
+  std::array<uint8_t, device::kP256X962Length> peer_identity;
+  memcpy(peer_identity.data(), server_link_data.data(),
+         device::kP256X962Length);
+
+  std::array<uint8_t, device::cablev2::kQRSecretSize> qr_secret;
+  memcpy(qr_secret.data(), server_link_data.data() + device::kP256X962Length,
+         device::cablev2::kQRSecretSize);
+
   const std::array<uint8_t, device::cablev2::kTunnelIdSize> tunnel_id =
       device::cablev2::Derive<device::cablev2::kTunnelIdSize>(
           qr_secret, base::span<uint8_t>(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index f0c023b..220e7ae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -257,9 +257,6 @@
     private static final String ACTION_CLOSE_TABS =
             "com.google.android.apps.chrome.ACTION_CLOSE_TABS";
 
-    @VisibleForTesting
-    public static final String STARTUP_UMA_HISTOGRAM_SUFFIX = ".Tabbed";
-
     // Name of the ChromeTabbedActivity alias that handles MAIN intents.
     public static final String MAIN_LAUNCHER_ACTIVITY_NAME = "com.google.android.apps.chrome.Main";
 
@@ -1206,7 +1203,7 @@
         assert getActivityTabStartupMetricsTracker() != null;
 
         if (shouldTrackColdStartupMetrics) {
-            getActivityTabStartupMetricsTracker().trackStartupMetrics(STARTUP_UMA_HISTOGRAM_SUFFIX);
+            getActivityTabStartupMetricsTracker().setHistogramSuffix(ActivityType.TABBED);
         } else {
             getActivityTabStartupMetricsTracker().cancelTrackingStartupMetrics();
         }
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 be2eccc4..6f4a0c2 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
@@ -82,7 +82,6 @@
                 add(ChromeFeatureList.sCctIncognitoAvailableToThirdParty);
                 add(ChromeFeatureList.sCctRemoveRemoteViewIds);
                 add(ChromeFeatureList.sCctResizable90MaximumHeight);
-                add(ChromeFeatureList.sCctResizableAllowResizeByUserGesture);
                 add(ChromeFeatureList.sCctResizableForThirdParties);
                 add(ChromeFeatureList.sCctResizableSideSheet);
                 add(ChromeFeatureList.sCctRetainableStateInMemory);
@@ -142,6 +141,7 @@
                         add(BrandingController.BRANDING_CADENCE_MS);
                         add(BrandingController.MAX_BLANK_TOOLBAR_TIMEOUT_MS);
                         add(BrandingController.USE_TEMPORARY_STORAGE);
+                        add(BrandingController.ANIMATE_TOOLBAR_ICON_TRANSITION);
                         add(ChimeFeatures.ALWAYS_REGISTER);
                         add(StartSurfaceConfiguration.BEHAVIOURAL_TARGETING);
                         add(ConditionalTabStripUtils.CONDITIONAL_TAB_STRIP_INFOBAR_LIMIT);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHandleStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHandleStrategy.java
index 6e6d757..80031c0e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHandleStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHandleStrategy.java
@@ -15,7 +15,6 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.customtabs.PartialCustomTabHeightStrategy.HeightStatus;
 import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbar;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 
 import java.util.function.BooleanSupplier;
 
@@ -83,10 +82,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (!ChromeFeatureList.sCctResizableAllowResizeByUserGesture.isEnabled()) {
-            return false;
-        }
-
         if (mStatus.get() == HeightStatus.TRANSITION) {
             return true;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 2f9af2f2..d5e20b3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -759,6 +759,7 @@
         private final Runnable[] mAfterBrandingRunnables = new Runnable[TOTAL_POST_BRANDING_KEYS];
         private boolean mCurrentlyShowingBranding;
         private boolean mBrandingStarted;
+        private boolean mAnimateIconTransition = true;
         private CallbackController mCallbackController = new CallbackController();
         // Cached the state before branding start so we can reset to the state when its done.
         private @Nullable Integer mPreBandingState;
@@ -846,6 +847,11 @@
                     MIN_URL_BAR_VISIBLE_TIME_POST_BRANDING_MS);
         }
 
+        @Override
+        public void setIconTransitionEnabled(boolean enabled) {
+            mAnimateIconTransition = enabled;
+        }
+
         private void cacheRegularState() {
             String assertMsg =
                     "mPreBandingState already exists! mPreBandingState = " + mPreBandingState;
@@ -1068,7 +1074,7 @@
             ColorStateList colorStateList = AppCompatResources.getColorStateList(
                     getContext(), mLocationBarDataProvider.getSecurityIconColorStateList());
             ApiCompatibilityUtils.setImageTintList(mSecurityButton, colorStateList);
-            mAnimDelegate.updateSecurityButton(R.drawable.chromelogo16);
+            mAnimDelegate.updateSecurityButton(R.drawable.chromelogo16, mAnimateIconTransition);
 
             mUrlBar.setText(R.string.twa_running_in_chrome);
         }
@@ -1099,7 +1105,7 @@
                         getContext(), mLocationBarDataProvider.getSecurityIconColorStateList());
                 ApiCompatibilityUtils.setImageTintList(mSecurityButton, colorStateList);
             }
-            mAnimDelegate.updateSecurityButton(securityIconResource);
+            mAnimDelegate.updateSecurityButton(securityIconResource, mAnimateIconTransition);
 
             int contentDescriptionId =
                     mLocationBarDataProvider.getSecurityIconContentDescriptionResourceId();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java
index 174f2a3..eeae118 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java
@@ -151,16 +151,16 @@
      * @param securityIconResource  The updated resource to be assigned to the security status icon.
      * When this is null, the icon is animated to the left and faded out.
      */
-    void updateSecurityButton(@DrawableRes int securityIconResource) {
-        if (mUseRotationTransition) {
+    void updateSecurityButton(@DrawableRes int securityIconResource, boolean animate) {
+        if (mUseRotationTransition && animate) {
             mBrandingAnimationDelegate.updateDrawableResource(securityIconResource);
         } else {
             boolean isActualResourceChange = true;
             if (ToolbarFeatures.shouldSuppressCaptures()) {
                 isActualResourceChange = securityIconResource != mSecurityIconRes;
             }
-            mSecurityButtonAnimationDelegate.updateSecurityButton(securityIconResource,
-                    /*animate=*/true, isActualResourceChange);
+            mSecurityButtonAnimationDelegate.updateSecurityButton(
+                    securityIconResource, animate, isActualResourceChange);
         }
         mSecurityIconRes = securityIconResource;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
index 487be0e..23ccc16 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivityBase.java
@@ -21,6 +21,7 @@
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.init.AsyncInitializationActivity;
+import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
 import org.chromium.chrome.browser.metrics.UmaUtils;
 import org.chromium.chrome.browser.policy.PolicyServiceFactory;
 import org.chromium.chrome.browser.profiles.ProfileManagerUtils;
@@ -112,6 +113,7 @@
 
     @Override
     public void onResume() {
+        SimpleStartupForegroundSessionDetector.discardSession();
         super.onResume();
         // Since the FRE may be shown before any tab is shown, mark that this is the point at which
         // Chrome went to foreground. Other activities can only
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index 3f92ef9..f623997 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcherImpl;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -509,12 +510,14 @@
         mIsWarmOnResume = !mFirstResumePending || hadWarmStart();
         mFirstResumePending = false;
 
+        SimpleStartupForegroundSessionDetector.onTransitionToForeground();
         mNativeInitializationController.onResume();
     }
 
     @CallSuper
     @Override
     public void onPause() {
+        SimpleStartupForegroundSessionDetector.discardSession();
         mNativeInitializationController.onPause();
         super.onPause();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
index 7304945..f05a354 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
@@ -8,6 +8,7 @@
 
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.paint_preview.StartupPaintPreviewHelper;
 import org.chromium.chrome.browser.paint_preview.StartupPaintPreviewMetrics.PaintPreviewMetricsObserver;
 import org.chromium.chrome.browser.tab.Tab;
@@ -26,7 +27,6 @@
  * startup.
  */
 public class ActivityTabStartupMetricsTracker {
-    private static final String UMA_HISTOGRAM_TABBED_SUFFIX = ".Tabbed";
     private static final String FIRST_COMMIT_OCCURRED_PRE_FOREGROUND_HISTOGRAM =
             "Startup.Android.Cold.FirstNavigationCommitOccurredPreForeground";
     private static final String FIRST_PAINT_OCCURRED_PRE_FOREGROUND_HISTOGRAM =
@@ -60,10 +60,9 @@
 
     // Event duration recorded from the |mActivityStartTimeMs|.
     private long mFirstCommitTimeMs;
-    private String mHistogramSuffix;
+    private @ActivityType int mHistogramSuffix;
     private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
     private PageLoadMetricsObserverImpl mPageLoadMetricsObserver;
-    private UmaUtils.Observer mUmaUtilsObserver;
     private boolean mShouldTrackStartupMetrics;
     private boolean mFirstVisibleContentRecorded;
     private boolean mVisibleContentRecorded;
@@ -85,7 +84,7 @@
     public ActivityTabStartupMetricsTracker(
             ObservableSupplier<TabModelSelector> tabModelSelectorSupplier) {
         mActivityStartTimeMs = SystemClock.uptimeMillis();
-        tabModelSelectorSupplier.addObserver((selector) -> registerObservers(selector));
+        tabModelSelectorSupplier.addObserver(this::registerObservers);
         SafeBrowsingApiBridge.setOneTimeUrlCheckObserver(this::updateSafeBrowsingCheckTime);
     }
 
@@ -93,6 +92,16 @@
         mFirstSafeBrowsingResponseTimeMicros.compareAndSet(0, urlCheckTimeDeltaMicros);
     }
 
+    /**
+     * Choose the UMA histogram to record later. The {@link ActivityType} parameter indicates the
+     * kind of startup scenario to track. Only two scenarios are supported.
+     * @param activityType Either TABBED or WEB_APK.
+     */
+    public void setHistogramSuffix(@ActivityType int activityType) {
+        mHistogramSuffix = activityType;
+        mShouldTrackStartupMetrics = true;
+    }
+
     // Note: In addition to returning false when startup metrics are not being tracked at all, this
     // method will also return false after first navigation commit has occurred.
     public boolean isTrackingStartupMetrics() {
@@ -138,15 +147,14 @@
                 };
         mPageLoadMetricsObserver = new PageLoadMetricsObserverImpl();
         PageLoadMetrics.addObserver(mPageLoadMetricsObserver, false);
-        mUmaUtilsObserver = this::registerHasComeToForeground;
-        UmaUtils.addObserver(mUmaUtilsObserver);
+        UmaUtils.setObserver(this::registerHasComeToForegroundWithNative);
     }
 
     /**
      * Registers the fact that UmaUtils#hasComeToForeground() has just become true for the first
      * time.
      */
-    private void registerHasComeToForeground() {
+    private void registerHasComeToForegroundWithNative() {
         // Record cases where first navigation commit and/or StartupPaintPreview's first
         // paint happened pre-foregrounding.
         if (mRegisteredFirstCommitPreForeground) {
@@ -158,7 +166,7 @@
                     FIRST_PAINT_OCCURRED_PRE_FOREGROUND_HISTOGRAM, true);
         }
 
-        clearUmaUtilsObserver();
+        UmaUtils.removeObserver();
     }
 
     /**
@@ -189,15 +197,6 @@
     }
 
     /**
-     * Marks that startup metrics should be tracked with the |histogramSuffix|.
-     * Must only be called on the UI thread.
-     */
-    public void trackStartupMetrics(String histogramSuffix) {
-        mHistogramSuffix = histogramSuffix;
-        mShouldTrackStartupMetrics = true;
-    }
-
-    /**
      * Cancels tracking the startup metrics.
      * Must only be called on the UI thread.
      */
@@ -207,14 +206,13 @@
         // Ensure we haven't tried to record metrics already.
         assert mFirstCommitTimeMs == 0;
 
-        mHistogramSuffix = null;
         mShouldTrackStartupMetrics = false;
     }
 
     public void destroy() {
         mShouldTrackStartupMetrics = false;
         clearNavigationObservers();
-        clearUmaUtilsObserver();
+        UmaUtils.removeObserver();
     }
 
     private void clearNavigationObservers() {
@@ -229,13 +227,6 @@
         }
     }
 
-    private void clearUmaUtilsObserver() {
-        if (mUmaUtilsObserver != null) {
-            UmaUtils.removeObserver(mUmaUtilsObserver);
-            mUmaUtilsObserver = null;
-        }
-    }
-
     /**
      * Registers the fact that a navigation has finished. Based on this fact, may discard recording
      * histograms later.
@@ -247,9 +238,10 @@
                 && !UmaUtils.hasComeToBackgroundWithNative()) {
             mFirstCommitTimeMs = SystemClock.uptimeMillis() - mActivityStartTimeMs;
             RecordHistogram.recordMediumTimesHistogram(
-                    "Startup.Android.Cold.TimeToFirstNavigationCommit" + mHistogramSuffix,
+                    "Startup.Android.Cold.TimeToFirstNavigationCommit"
+                            + activityTypeToSuffix(mHistogramSuffix),
                     mFirstCommitTimeMs);
-            if (mHistogramSuffix.equals(UMA_HISTOGRAM_TABBED_SUFFIX)) {
+            if (mHistogramSuffix == ActivityType.TABBED) {
                 recordFirstVisibleContent(mFirstCommitTimeMs);
                 recordFirstSafeBrowsingResponseTime();
             }
@@ -260,9 +252,21 @@
             mRegisteredFirstCommitPreForeground = true;
         }
 
+        if (mHistogramSuffix == ActivityType.TABBED && isTrackedPage
+                && SimpleStartupForegroundSessionDetector.runningCleanForegroundSession()) {
+            RecordHistogram.recordMediumTimesHistogram(
+                    "Startup.Android.Cold.TimeToFirstNavigationCommit2.Tabbed", mFirstCommitTimeMs);
+        }
+
         mShouldTrackStartupMetrics = false;
     }
 
+    private String activityTypeToSuffix(@ActivityType int type) {
+        if (type == ActivityType.TABBED) return ".Tabbed";
+        assert type == ActivityType.WEB_APK;
+        return ".WebApk";
+    }
+
     private void recordFirstSafeBrowsingResponseTime() {
         long deltaMicros = mFirstSafeBrowsingResponseTimeMicros.getAndSet(0);
         if (deltaMicros == 0) return;
@@ -283,9 +287,10 @@
         if (UmaUtils.hasComeToForegroundWithNative() && !UmaUtils.hasComeToBackgroundWithNative()) {
             long durationMs = firstContentfulPaintMs - mActivityStartTimeMs;
             RecordHistogram.recordMediumTimesHistogram(
-                    "Startup.Android.Cold.TimeToFirstContentfulPaint" + mHistogramSuffix,
+                    "Startup.Android.Cold.TimeToFirstContentfulPaint"
+                            + activityTypeToSuffix(mHistogramSuffix),
                     durationMs);
-            if (mHistogramSuffix.equals(UMA_HISTOGRAM_TABBED_SUFFIX)) {
+            if (mHistogramSuffix == ActivityType.TABBED) {
                 recordVisibleContent(durationMs);
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/SimpleStartupForegroundSessionDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/SimpleStartupForegroundSessionDetector.java
new file mode 100644
index 0000000..ca48f6fc
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/SimpleStartupForegroundSessionDetector.java
@@ -0,0 +1,34 @@
+// 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.
+
+package org.chromium.chrome.browser.metrics;
+
+/**
+ * Determines whether the browsing session early at startup is good enough for startup metrics.
+ * Transitioning the activity to background would recommend omitting the metrics because of
+ * background restrictions and throttling. Must be subscribed to pause/resume events.
+ */
+public class SimpleStartupForegroundSessionDetector {
+    private static boolean sSessionDiscarded;
+    private static boolean sReachedForeground;
+
+    public static void onTransitionToForeground() {
+        if (sReachedForeground) {
+            sSessionDiscarded = true;
+            return;
+        }
+        sReachedForeground = true;
+    }
+
+    public static void discardSession() {
+        sSessionDiscarded = true;
+    }
+
+    /**
+     * @return whether the startup happened cleanly in the foreground.
+     */
+    public static boolean runningCleanForegroundSession() {
+        return sReachedForeground && !sSessionDiscarded;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
index 087d6ae..1d5ca72 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
@@ -14,7 +14,6 @@
 import androidx.annotation.IntDef;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -30,25 +29,25 @@
     /** Observer for this class. */
     public interface Observer {
         /**
-         * Called when hasComeToForeground() changes from false to true.
+         * Called when hasComeToForeground() changes from false to true for the first time after
+         * post-native initialization has started.
          */
-        void onHasComeToForeground();
+        void onHasComeToForegroundWithNative();
     }
 
-    private static ObserverList<Observer> sObservers;
+    private static Observer sObserver;
 
-    /** Adds an observer. */
-    public static boolean addObserver(Observer observer) {
+    /** Sets the observer. */
+    public static void setObserver(Observer observer) {
         ThreadUtils.assertOnUiThread();
-        if (sObservers == null) sObservers = new ObserverList<>();
-        return sObservers.addObserver(observer);
+        assert sObserver == null;
+        sObserver = observer;
     }
 
-    /** Removes an observer. */
-    public static boolean removeObserver(Observer observer) {
+    /** Removes the observer. */
+    public static void removeObserver() {
         ThreadUtils.assertOnUiThread();
-        if (sObservers == null) return false;
-        return sObservers.removeObserver(observer);
+        sObserver = null;
     }
 
     // All these values originate from SystemClock.uptimeMillis().
@@ -113,10 +112,8 @@
         // Chrome has been sent to background since the last foreground time.
         if (sForegroundStartWithNativeTimeMs == 0
                 || sForegroundStartWithNativeTimeMs < sBackgroundWithNativeTimeMs) {
-            if (sObservers != null && sForegroundStartWithNativeTimeMs == 0) {
-                for (Observer observer : sObservers) {
-                    observer.onHasComeToForeground();
-                }
+            if (sObserver != null && sForegroundStartWithNativeTimeMs == 0) {
+                sObserver.onHasComeToForegroundWithNative();
             }
             sForegroundStartWithNativeTimeMs = SystemClock.uptimeMillis();
         }
@@ -254,9 +251,6 @@
 
     @CalledByNative
     public static long getProcessStartTime() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            return 0;
-        }
         return ApiHelperForN.getStartUptimeMillis();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java
index e7fbad7..f3c2e36f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityLifecycleUmaTracker.java
@@ -11,8 +11,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 
-import androidx.annotation.VisibleForTesting;
-
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ApplicationStatus.ActivityStateListener;
@@ -25,6 +23,7 @@
 import org.chromium.chrome.browser.browserservices.metrics.WebApkUmaRecorder;
 import org.chromium.chrome.browser.browserservices.ui.splashscreen.SplashController;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
 import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
@@ -42,9 +41,6 @@
 @ActivityScope
 public class WebApkActivityLifecycleUmaTracker
         implements ActivityStateListener, InflationObserver, PauseResumeWithNativeObserver {
-    @VisibleForTesting
-    public static final String STARTUP_UMA_HISTOGRAM_SUFFIX = ".WebApk";
-
     private final Activity mActivity;
     private final BrowserServicesIntentDataProvider mIntentDataProvider;
     private final SplashController mSplashController;
@@ -93,7 +89,7 @@
         // Decide whether to record startup UMA histograms. This is a similar check to the one done
         // in ChromeTabbedActivity.performPreInflationStartup refer to the comment there for why.
         if (!LibraryLoader.getInstance().isInitialized()) {
-            mStartupMetricsTracker.get().trackStartupMetrics(STARTUP_UMA_HISTOGRAM_SUFFIX);
+            mStartupMetricsTracker.get().setHistogramSuffix(ActivityType.WEB_APK);
             // If there is a saved instance state, then the intent (and its stored timestamp) might
             // be stale (Android replays intents if there is a recents entry for the activity).
             if (mSavedInstanceStateSupplier.get() == null) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index 455405e..fabcfe5d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -28,10 +28,8 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.JniMocker;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.webapps.WebApkActivityLifecycleUmaTracker;
 import org.chromium.chrome.browser.webapps.WebApkActivityTestRule;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -55,6 +53,8 @@
     private static final String SLOW_PAGE = "/slow?2";
     private static final String FIRST_COMMIT_HISTOGRAM =
             "Startup.Android.Cold.TimeToFirstNavigationCommit";
+    private static final String FIRST_COMMIT_HISTOGRAM2 =
+            "Startup.Android.Cold.TimeToFirstNavigationCommit2.Tabbed";
     private static final String FIRST_CONTENTFUL_PAINT_HISTOGRAM =
             "Startup.Android.Cold.TimeToFirstContentfulPaint";
     private static final String FIRST_VISIBLE_CONTENT_HISTOGRAM =
@@ -64,9 +64,8 @@
     private static final String FIRST_COMMIT_OCCURRED_PRE_FOREGROUND_HISTOGRAM =
             "Startup.Android.Cold.FirstNavigationCommitOccurredPreForeground";
 
-    private static final String TABBED_SUFFIX = ChromeTabbedActivity.STARTUP_UMA_HISTOGRAM_SUFFIX;
-    private static final String WEBAPK_SUFFIX =
-            WebApkActivityLifecycleUmaTracker.STARTUP_UMA_HISTOGRAM_SUFFIX;
+    private static final String TABBED_SUFFIX = ".Tabbed";
+    private static final String WEB_APK_SUFFIX = ".WebApk";
 
     @Rule
     public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
@@ -140,6 +139,8 @@
                             FIRST_VISIBLE_CONTENT_HISTOGRAM));
             Assert.assertEquals(expectedCount,
                     RecordHistogram.getHistogramTotalCountForTesting(VISIBLE_CONTENT_HISTOGRAM));
+            Assert.assertEquals(expectedCount,
+                    RecordHistogram.getHistogramTotalCountForTesting(FIRST_COMMIT_HISTOGRAM2));
         }
 
         if (expectedCount > 0) {
@@ -169,9 +170,9 @@
     public void testWebApkStartRecorded() throws Exception {
         runAndWaitForPageLoadMetricsRecorded(
                 () -> mWebApkActivityTestRule.startWebApkActivity(mTestPage));
-        assertHistogramsRecorded(1, WEBAPK_SUFFIX);
+        assertHistogramsRecorded(1, WEB_APK_SUFFIX);
         loadUrlAndWaitForPageLoadMetricsRecorded(mWebApkActivityTestRule, mTestPage2);
-        assertHistogramsRecorded(1, WEBAPK_SUFFIX);
+        assertHistogramsRecorded(1, WEB_APK_SUFFIX);
     }
 
     /**
@@ -239,9 +240,9 @@
     public void testWebApkErrorPageNotRecorded() throws Exception {
         runAndWaitForPageLoadMetricsRecorded(
                 () -> mWebApkActivityTestRule.startWebApkActivity(mErrorPage));
-        assertHistogramsRecorded(0, WEBAPK_SUFFIX);
+        assertHistogramsRecorded(0, WEB_APK_SUFFIX);
         loadUrlAndWaitForPageLoadMetricsRecorded(mWebApkActivityTestRule, mTestPage2);
-        assertHistogramsRecorded(0, WEBAPK_SUFFIX);
+        assertHistogramsRecorded(0, WEB_APK_SUFFIX);
     }
 
     /**
@@ -303,6 +304,10 @@
         Assert.assertEquals(0,
                 RecordHistogram.getHistogramTotalCountForTesting(FIRST_VISIBLE_CONTENT_HISTOGRAM));
 
+        // The metric based on early foreground notification should be recorded.
+        Assert.assertEquals(
+                1, RecordHistogram.getHistogramTotalCountForTesting(FIRST_COMMIT_HISTOGRAM2));
+
         // The metric for the first navigation commit having occurred pre-foregrounding should also
         // not have been recorded at this point, as there hasn't yet been a notification that the
         // browser has come to the foreground.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
index 353d3c6..1648978 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -10,6 +10,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -237,6 +238,17 @@
 
     @Test
     public void testToolbarBrandingDelegateImpl_EmptyToBranding() {
+        mLocationBar.setIconTransitionEnabled(true);
+        doTestToolbarBrandingDelegateImpl_EmptyToBranding(true);
+    }
+
+    @Test
+    public void testToolbarBrandingDelegateImpl_EmptyToBranding_DisableTransition() {
+        mLocationBar.setIconTransitionEnabled(false);
+        doTestToolbarBrandingDelegateImpl_EmptyToBranding(false);
+    }
+
+    private void doTestToolbarBrandingDelegateImpl_EmptyToBranding(boolean animateIconTransition) {
         ChromeFeatureList.sCctBrandTransparency.setForTesting(true);
 
         assertUrlAndTitleVisible(/*titleVisible=*/false, /*urlVisible=*/true);
@@ -251,6 +263,7 @@
 
         mLocationBar.showBrandingLocationBar();
         assertUrlAndTitleVisible(/*titleVisible=*/false, /*urlVisible=*/true);
+        verify(mAnimationDelegate).updateSecurityButton(anyInt(), eq(animateIconTransition));
 
         // Attempt to update title and URL to show Title only - should be ignored during branding.
         reset(mLocationBarModel);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 7825078..cdc2630 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6065,6 +6065,20 @@
       <message name="IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION" desc="The description of the Google Password Manager's update password message when the user is signed in.">
         In Google Password Manager for <ph name="ACCOUNT">$1<ex>user@gmail.com</ex></ph>
       </message>
+      <if expr="is_android">
+        <message name="IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1" desc="The description of the Google Password Manager's save/update password prompt when the user is signed in.">
+          Google Password Manager <ph name="SEPARATOR">•</ph> <ph name="ACCOUNT">$1<ex>user@gmail.com</ex></ph>
+        </message>
+        <message name="IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2" desc="The description of the Google Password Manager's save password prompt when the user is signed in.">
+          To your Google Account, for <ph name="ACCOUNT">$1<ex>user@gmail.com</ex></ph>
+        </message>
+        <message name="IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2" desc="The description of the Google Password Manager's save password prompt when the user is signed in.">
+          In your Google Account, for <ph name="ACCOUNT">$1<ex>user@gmail.com</ex></ph>
+        </message>
+        <message name="IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1" desc="The description of the Google Password Manager's save password prompt when the user is signed out and passwords are only stored locally.">
+          Only on this device
+        </message>
+      </if>
       <if expr="use_titlecase">
         <message name="IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_SIGN_IN" desc="In Title Case: The title of the Sign in button in the account chooser when there is one account.">
           Sign In
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1
new file mode 100644
index 0000000..2e2d4e4
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1
@@ -0,0 +1 @@
+8287d85ba36e624ada15c29e4cd5a26258cc92cb
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1.png.sha1
new file mode 100644
index 0000000..d2720d9
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1.png.sha1
@@ -0,0 +1 @@
+c098a770c6bdc1db8eb2af8bc30f6c8f74c99608
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1.png.sha1
new file mode 100644
index 0000000..9cc2a8d
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1.png.sha1
@@ -0,0 +1 @@
+ac244af9a0214c43af43e0a475826e4d714b2b6d
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1
new file mode 100644
index 0000000..c679fe1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2.png.sha1
@@ -0,0 +1 @@
+9f302d188c61200852a74ffbc2a2564299326dde
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d8fb9b8..7cd4b3f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6214,12 +6214,6 @@
      flag_descriptions::kCCTResizable90MaximumHeightName,
      flag_descriptions::kCCTResizable90MaximumHeightDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kCCTResizable90MaximumHeight)},
-    {"cct-resizable-allow-resize-by-user-gesture",
-     flag_descriptions::kCCTResizableAllowResizeByUserGestureName,
-     flag_descriptions::kCCTResizableAllowResizeByUserGestureDescription,
-     kOsAndroid,
-     FEATURE_VALUE_TYPE(
-         chrome::android::kCCTResizableAllowResizeByUserGesture)},
     {"cct-resizable-for-third-parties",
      flag_descriptions::kCCTResizableForThirdPartiesName,
      flag_descriptions::kCCTResizableForThirdPartiesDescription, kOsAndroid,
@@ -6635,11 +6629,6 @@
      flag_descriptions::kFastPairSoftwareScanningDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kFastPairSoftwareScanning)},
 
-    {"fast-pair-subsequent-pairing-ux",
-     flag_descriptions::kFastPairSubsequentPairingUXName,
-     flag_descriptions::kFastPairSubsequentPairingUXDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kFastPairSubsequentPairingUX)},
-
     {"pcie-billboard-notification",
      flag_descriptions::kPcieBillboardNotificationName,
      flag_descriptions::kPcieBillboardNotificationDescription, kOsCrOS,
@@ -6668,11 +6657,6 @@
      flag_descriptions::kEnableHostnameSettingDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kEnableHostnameSetting)},
 
-    {"enable-networking-in-diagnostics-app",
-     flag_descriptions::kEnableNetworkingInDiagnosticsAppName,
-     flag_descriptions::kEnableNetworkingInDiagnosticsAppDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kEnableNetworkingInDiagnosticsApp)},
-
     {"enable-oauth-ipp", flag_descriptions::kEnableOAuthIppName,
      flag_descriptions::kEnableOAuthIppDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kEnableOAuthIpp)},
@@ -7448,9 +7432,9 @@
      flag_descriptions::kMacAddressRandomizationDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kMacAddressRandomization)},
 
-    {"oobe-material-next", flag_descriptions::kOobeMaterialNextName,
-     flag_descriptions::kOobeMaterialNextDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kOobeMaterialNext)},
+    {"oobe-jelly", flag_descriptions::kOobeJellyName,
+     flag_descriptions::kOobeJellyDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kOobeJelly)},
 
     {"search-result-inline-icon",
      flag_descriptions::kSearchResultInlineIconName,
diff --git a/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java b/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
index bacdcd1..fe7ff4b 100644
--- a/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
+++ b/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingController.java
@@ -37,6 +37,8 @@
     private static final String PARAM_BRANDING_CADENCE_NAME = "branding_cadence";
     private static final String PARAM_MAX_BLANK_TOOLBAR_TIMEOUT_MS = "max_blank_toolbar_timeout";
     private static final String PARAM_USE_TEMPORARY_STORAGE = "use_temporary_storage";
+    private static final String PARAM_ANIMATE_TOOLBAR_ICON_TRANSITION =
+            "animate_toolbar_transition";
     private static final int DEFAULT_BRANDING_CADENCE_MS = (int) TimeUnit.HOURS.toMillis(1);
     private static final int DEFAULT_MAX_BLANK_TOOLBAR_TIMEOUT_MS = 500;
     /**
@@ -67,12 +69,21 @@
             new BooleanCachedFieldTrialParameter(
                     ChromeFeatureList.CCT_BRAND_TRANSPARENCY, PARAM_USE_TEMPORARY_STORAGE, false);
 
+    /**
+     * Whether animation transition will be used for the security icon during toolbar branding.
+     * If set to false, the icon transition will be disabled.
+     */
+    public static final BooleanCachedFieldTrialParameter ANIMATE_TOOLBAR_ICON_TRANSITION =
+            new BooleanCachedFieldTrialParameter(ChromeFeatureList.CCT_BRAND_TRANSPARENCY,
+                    PARAM_ANIMATE_TOOLBAR_ICON_TRANSITION, true);
+
     private final CallbackController mCallbackController = new CallbackController();
     private final @BrandingDecision OneshotSupplierImpl<Integer> mBrandingDecision =
             new OneshotSupplierImpl<>();
     private final BrandingChecker mBrandingChecker;
     private final Context mContext;
     private final String mAppName;
+    private final boolean mEnableIconAnimation;
 
     private ToolbarBrandingDelegate mToolbarBrandingDelegate;
     private @Nullable Toast mToast;
@@ -95,6 +106,7 @@
         mContext = context;
         mAppName = appName;
         mExceptionReporter = exceptionReporter;
+        mEnableIconAnimation = ANIMATE_TOOLBAR_ICON_TRANSITION.getValue();
         mBrandingDecision.onAvailable(
                 mCallbackController.makeCancelable((decision) -> maybeMakeBrandingDecision()));
 
@@ -117,6 +129,7 @@
 
         mToolbarInitializedTime = SystemClock.elapsedRealtime();
         mToolbarBrandingDelegate = delegate;
+        mToolbarBrandingDelegate.setIconTransitionEnabled(mEnableIconAnimation);
 
         // Start the task to timeout the branding check. If mBrandingChecker already finished,
         // canceling the task does nothing. Does not interrupt if the task is running, since the
diff --git a/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/ToolbarBrandingDelegate.java b/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/ToolbarBrandingDelegate.java
index be66637..db7258ff 100644
--- a/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/ToolbarBrandingDelegate.java
+++ b/chrome/browser/android/customtabs/branding/java/src/org/chromium/chrome/browser/customtabs/features/branding/ToolbarBrandingDelegate.java
@@ -19,4 +19,12 @@
 
     /** Show the regular location with URL and Title, with start transition. */
     void showRegularToolbar();
+
+    /**
+     * Whether the animation transition between state for toolbar icon should be disable. By
+     * default, the animation transition will be enabled if this is not set.
+     * @param enabled If true, the animation will be enabled; if false, the animation will be
+     *                disabled.
+     */
+    void setIconTransitionEnabled(boolean enabled);
 }
diff --git a/chrome/browser/ash/arc/arc_support_host.cc b/chrome/browser/ash/arc/arc_support_host.cc
index 941407d..85f62c35 100644
--- a/chrome/browser/ash/arc/arc_support_host.cc
+++ b/chrome/browser/ash/arc/arc_support_host.cc
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "ash/constants/ash_features.h"
 #include "base/bind.h"
 #include "base/hash/sha1.h"
 #include "base/i18n/timezone.h"
@@ -418,9 +417,7 @@
 
   message_args.Set(kErrorMessage, message);
   message_args.Set(kShouldShowSendFeedback, should_show_send_feedback);
-  message_args.Set(kShouldShowNetworkTests,
-                   should_show_run_network_tests &&
-                       ash::features::IsArcNetworkDiagnosticsButtonEnabled());
+  message_args.Set(kShouldShowNetworkTests, should_show_run_network_tests);
   message_host_->SendMessage(message_args);
 }
 
diff --git a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
index ddb7aaf..e311f9e 100644
--- a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
@@ -1315,7 +1315,7 @@
   embedded_test_server()->StartAcceptingConnections();
 
   // Specify an external data reference for the key::kUserAvatarImage policy.
-  std::unique_ptr<base::DictionaryValue> metadata =
+  std::unique_ptr<base::Value::Dict> metadata =
       test::ConstructExternalDataReference(
           embedded_test_server()->GetURL(kExternalDataPath).spec(),
           kExternalData);
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
index 3ecfd832..1f70eb3 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ash/policy/core/device_policy_decoder.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
@@ -77,21 +79,19 @@
 };
 
 std::unique_ptr<base::Value> DevicePolicyDecoderTest::GetWallpaperDict() const {
-  auto dict = std::make_unique<base::DictionaryValue>();
-  dict->SetKey(kWallpaperUrlPropertyName,
-               base::Value(kWallpaperUrlPropertyValue));
-  dict->SetKey(kWallpaperHashPropertyName,
-               base::Value(kWallpaperHashPropertyValue));
-  return dict;
+  base::Value::Dict dict;
+  dict.Set(kWallpaperUrlPropertyName, kWallpaperUrlPropertyValue);
+  dict.Set(kWallpaperHashPropertyName, kWallpaperHashPropertyValue);
+  return std::make_unique<base::Value>(std::move(dict));
 }
 
 std::unique_ptr<base::Value>
 DevicePolicyDecoderTest::GetBluetoothServiceAllowedList() const {
-  auto list = std::make_unique<base::ListValue>();
-  list->Append(base::Value(kValidBluetoothServiceUUID4));
-  list->Append(base::Value(kValidBluetoothServiceUUID8));
-  list->Append(base::Value(kValidBluetoothServiceUUID32));
-  return list;
+  base::Value::List list;
+  list.Append(kValidBluetoothServiceUUID4);
+  list.Append(kValidBluetoothServiceUUID8);
+  list.Append(kValidBluetoothServiceUUID32);
+  return std::make_unique<base::Value>(std::move(list));
 }
 
 void DevicePolicyDecoderTest::DecodeDevicePolicyTestHelper(
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_config_unittest.cc b/chrome/browser/ash/policy/enrollment/enrollment_config_unittest.cc
index 1828fc5..a46e5da6 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_config_unittest.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_config_unittest.cc
@@ -94,11 +94,11 @@
   EXPECT_EQ(GetParam().auth_mechanism, config.auth_mechanism);
 
   // Server-backed state: advertised enrollment.
-  base::DictionaryValue state_dict;
-  state_dict.SetStringKey(kDeviceStateMode,
-                          kDeviceStateRestoreModeReEnrollmentRequested);
-  state_dict.SetStringKey(kDeviceStateManagementDomain, "example.com");
-  local_state_.Set(prefs::kServerBackedDeviceState, state_dict);
+  base::Value::Dict state_dict;
+  state_dict.Set(kDeviceStateMode,
+                 kDeviceStateRestoreModeReEnrollmentRequested);
+  state_dict.Set(kDeviceStateManagementDomain, "example.com");
+  local_state_.SetDict(prefs::kServerBackedDeviceState, state_dict.Clone());
   config = EnrollmentConfig::GetPrescribedEnrollmentConfig(
       local_state_, install_attributes_, &statistics_provider_);
   EXPECT_EQ(EnrollmentConfig::MODE_SERVER_ADVERTISED, config.mode);
@@ -129,9 +129,8 @@
   EXPECT_EQ(GetParam().auth_mechanism, config.auth_mechanism);
 
   // Server-backed state: forced enrollment.
-  state_dict.SetStringKey(kDeviceStateMode,
-                          kDeviceStateRestoreModeReEnrollmentEnforced);
-  local_state_.Set(prefs::kServerBackedDeviceState, state_dict);
+  state_dict.Set(kDeviceStateMode, kDeviceStateRestoreModeReEnrollmentEnforced);
+  local_state_.SetDict(prefs::kServerBackedDeviceState, state_dict.Clone());
   config = EnrollmentConfig::GetPrescribedEnrollmentConfig(
       local_state_, install_attributes_, &statistics_provider_);
   EXPECT_EQ(EnrollmentConfig::MODE_SERVER_FORCED, config.mode);
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
index 7541318..474dcbf 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/callback.h"
@@ -30,7 +31,7 @@
 namespace policy {
 
 namespace {
-// Keys for 'DictionaryValue' objects
+// Keys for the 'Value' objects
 const char kUrlKey[] = "url";
 const char kHashKey[] = "hash";
 }  // namespace
@@ -47,14 +48,13 @@
   std::move(done_callback).Run();
 }
 
-std::unique_ptr<base::DictionaryValue> ConstructExternalDataReference(
+std::unique_ptr<base::Value::Dict> ConstructExternalDataReference(
     const std::string& url,
     const std::string& data) {
   const std::string hash = crypto::SHA256HashString(data);
-  std::unique_ptr<base::DictionaryValue> metadata(new base::DictionaryValue);
-  metadata->SetKey(kUrlKey, base::Value(url));
-  metadata->SetKey(kHashKey,
-                   base::Value(base::HexEncode(hash.c_str(), hash.size())));
+  auto metadata = std::make_unique<base::Value::Dict>();
+  metadata->Set(kUrlKey, url);
+  metadata->Set(kHashKey, base::HexEncode(hash.c_str(), hash.size()));
   return metadata;
 }
 
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
index 1bf93f9..94377433 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
@@ -10,10 +10,7 @@
 
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
-
-namespace base {
-class DictionaryValue;
-}
+#include "base/values.h"
 
 namespace net {
 namespace test_server {
@@ -37,7 +34,7 @@
 
 // Constructs a value that points a policy referencing external data at |url|
 // and sets the expected hash of the external data to that of |data|.
-std::unique_ptr<base::DictionaryValue> ConstructExternalDataReference(
+std::unique_ptr<base::Value::Dict> ConstructExternalDataReference(
     const std::string& url,
     const std::string& data);
 
@@ -54,7 +51,7 @@
 // removed.
 void SetExternalDataReference(CloudPolicyCore* core,
                               const std::string& policy,
-                              std::unique_ptr<base::DictionaryValue> metadata);
+                              std::unique_ptr<base::Value::Dict> metadata);
 
 }  // namespace test
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc b/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
index 80e3895..746443c 100644
--- a/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
+++ b/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
@@ -68,7 +68,7 @@
   }
 
   std::string external_data_;
-  std::unique_ptr<base::DictionaryValue> metadata_;
+  std::unique_ptr<base::Value::Dict> metadata_;
 };
 
 IN_PROC_BROWSER_TEST_F(UserCloudExternalDataManagerTest, FetchExternalData) {
diff --git a/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc b/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
index 23799e3..5438ef0 100644
--- a/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/system_proxy_handler_unittest.cc
@@ -77,14 +77,12 @@
   void SetPolicy(bool system_proxy_enabled,
                  const std::string& system_services_username,
                  const std::string& system_services_password) {
-    base::DictionaryValue dict;
-    dict.SetKey("system_proxy_enabled", base::Value(system_proxy_enabled));
-    dict.SetKey("system_services_username",
-                base::Value(system_services_username));
-    dict.SetKey("system_services_password",
-                base::Value(system_services_password));
+    base::Value::Dict dict;
+    dict.Set("system_proxy_enabled", system_proxy_enabled);
+    dict.Set("system_services_username", system_services_username);
+    dict.Set("system_services_password", system_services_password);
     scoped_testing_cros_settings_.device_settings()->Set(
-        ash::kSystemProxySettings, dict);
+        ash::kSystemProxySettings, base::Value(std::move(dict)));
     task_environment_.RunUntilIdle();
   }
 
diff --git a/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc b/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
index 81dd6d1..a6b9083 100644
--- a/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/timer/mock_timer.h"
+#include "base/values.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/ash/tpm_firmware_update.h"
@@ -51,11 +52,11 @@
   }
 
   void SetAutoUpdateMode(AutoUpdateMode auto_update_mode) {
-    base::DictionaryValue dict;
-    dict.SetKey(ash::tpm_firmware_update::kSettingsKeyAutoUpdateMode,
-                base::Value(static_cast<int>(auto_update_mode)));
+    base::Value::Dict dict;
+    dict.Set(ash::tpm_firmware_update::kSettingsKeyAutoUpdateMode,
+             base::Value(static_cast<int>(auto_update_mode)));
     scoped_testing_cros_settings_.device_settings()->Set(
-        ash::kTPMFirmwareUpdateSettings, dict);
+        ash::kTPMFirmwareUpdateSettings, base::Value(std::move(dict)));
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
index 6599fb4..20c1d0f 100644
--- a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
@@ -246,7 +246,7 @@
   PolicyMap CreatePolicyWithForceInstalls(std::set<std::string> package_names) {
     PolicyMap policy_map;
 
-    base::DictionaryValue arc_policy;
+    base::Value::Dict arc_policy;
     base::Value::List list;
 
     for (std::string package_name : package_names) {
@@ -256,7 +256,7 @@
       list.Append(std::move(package));
     }
 
-    arc_policy.GetDict().Set("applications", std::move(list));
+    arc_policy.Set("applications", std::move(list));
     std::string arc_policy_string;
     base::JSONWriter::Write(arc_policy, &arc_policy_string);
     SetPolicy(&policy_map, key::kArcEnabled, base::Value(true));
@@ -265,7 +265,7 @@
     return policy_map;
   }
 
-  base::DictionaryValue CreateComplianceReport(
+  base::Value::Dict CreateComplianceReport(
       std::set<std::string> noncompliant_packages) {
     base::Value::List details;
 
@@ -276,8 +276,8 @@
       details.Append(std::move(package));
     }
 
-    base::DictionaryValue compliance_report;
-    compliance_report.GetDict().Set("nonComplianceDetails", std::move(details));
+    base::Value::Dict compliance_report;
+    compliance_report.Set("nonComplianceDetails", std::move(details));
     return compliance_report;
   }
 
@@ -464,7 +464,7 @@
 
   PolicyMap new_policy_map;
 
-  base::DictionaryValue arc_policy;
+  base::Value::Dict arc_policy;
   base::Value::List list;
 
   // Test that REQUIRED, PREINSTALLED and FORCE_INSTALLED are markers to include
@@ -489,7 +489,7 @@
   package5.Set("installType", "AVAILABLE");
   package5.Set("packageName", kPackageName5);
   list.Append(std::move(package5));
-  arc_policy.GetDict().Set("applications", std::move(list));
+  arc_policy.Set("applications", std::move(list));
 
   std::string arc_policy_string;
   base::JSONWriter::Write(arc_policy, &arc_policy_string);
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
index a0ddcc3..eebdd25 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
@@ -134,14 +134,15 @@
           base::Value(network_data.signal_strength));
       service_client->SetServiceProperty(service_path, shill::kDeviceProperty,
                                          base::Value(device_path));
-      base::DictionaryValue ip_config_properties;
-      ip_config_properties.SetKey(shill::kAddressProperty,
-                                  base::Value(network_data.ip_address));
-      ip_config_properties.SetKey(shill::kGatewayProperty,
-                                  base::Value(network_data.gateway));
+      base::Value::Dict ip_config_properties;
+      ip_config_properties.Set(shill::kAddressProperty,
+                               base::Value(network_data.ip_address));
+      ip_config_properties.Set(shill::kGatewayProperty,
+                               base::Value(network_data.gateway));
       const std::string kIPConfigPath =
           base::StrCat({"test_ip_config", network_data.guid});
-      ip_config_client->AddIPConfig(kIPConfigPath, ip_config_properties);
+      ip_config_client->AddIPConfig(
+          kIPConfigPath, base::Value(std::move(ip_config_properties)));
       service_client->SetServiceProperty(service_path, shill::kIPConfigProperty,
                                          base::Value(kIPConfigPath));
       if (network_data.type == shill::kTypeCellular) {
diff --git a/chrome/browser/ash/policy/status_collector/activity_storage.cc b/chrome/browser/ash/policy/status_collector/activity_storage.cc
index fda3fac..2797539 100644
--- a/chrome/browser/ash/policy/status_collector/activity_storage.cc
+++ b/chrome/browser/ash/policy/status_collector/activity_storage.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "chrome/common/pref_names.h"
@@ -58,10 +59,10 @@
 
 void ActivityStorage::TrimActivityPeriods(int64_t min_day_key,
                                           int64_t max_day_key) {
-  base::DictionaryValue copy;
+  base::Value::Dict copy;
 
   ForEachActivityPeriodFromPref(base::BindRepeating(
-      [](base::DictionaryValue* copy, int64_t min_day_key, int64_t max_day_key,
+      [](base::Value::Dict& copy, int64_t min_day_key, int64_t max_day_key,
          int64_t start, int64_t end, const std::string& activity_id) {
         int64_t day_key = start;
         // Remove data that is too old, or too far in the future.
@@ -76,12 +77,12 @@
         if (duration <= 0)
           return;
         const std::string key = MakeActivityPeriodPrefKey(day_key, activity_id);
-        copy->SetIntPath(key, duration);
+        copy.Set(key, base::saturated_cast<int>(duration));
       },
-      &copy, min_day_key, max_day_key));
+      std::ref(copy), min_day_key, max_day_key));
 
   // Flush the activities into pref_service_
-  pref_service_->Set(pref_name_, copy);
+  pref_service_->SetDict(pref_name_, std::move(copy));
 }
 
 void ActivityStorage::RemoveOverlappingActivityPeriods() {
@@ -185,19 +186,19 @@
 
 void ActivityStorage::SetActivityPeriods(
     const std::map<std::string, Activities>& new_activity_periods) {
-  base::DictionaryValue copy;
+  base::Value::Dict copy;
   for (const auto& activity_pair : new_activity_periods) {
     const std::string& activity_id = activity_pair.first;
     const Activities& activities = activity_pair.second;
     for (const auto& activity : activities) {
       const std::string& key =
           MakeActivityPeriodPrefKey(activity.start_timestamp(), activity_id);
-      copy.SetIntKey(key,
-                     activity.end_timestamp() - activity.start_timestamp());
+      copy.Set(key, base::saturated_cast<int>(activity.end_timestamp() -
+                                              activity.start_timestamp()));
     }
   }
 
-  pref_service_->Set(pref_name_, copy);
+  pref_service_->SetDict(pref_name_, std::move(copy));
 }
 
 int64_t ActivityStorage::LocalTimeToUtcDayStart(base::Time timestamp) const {
diff --git a/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
index ae88edb..602f437 100644
--- a/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/diagnostics_app_integration_browsertest.cc
@@ -2,11 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/constants/ash_features.h"
 #include "ash/webui/diagnostics_ui/url_constants.h"
 #include "ash/webui/system_apps/public/system_web_app_type.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h"
 #include "chrome/browser/lifetime/application_lifetime_desktop.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
@@ -37,10 +35,7 @@
 
 class DiagnosticsAppIntegrationTest : public ash::SystemWebAppIntegrationTest {
  public:
-  DiagnosticsAppIntegrationTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        ash::features::kEnableNetworkingInDiagnosticsApp);
-  }
+  DiagnosticsAppIntegrationTest() = default;
 
  protected:
   base::HistogramTester histogram_tester_;
@@ -64,9 +59,6 @@
 
     return params;
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Test that the Diagnostics App installs and launches correctly by running some
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index b924f22..b8686da 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -455,6 +455,12 @@
 
   battery_metrics_.reset();
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  // The Extensions Browser Client needs to teardown some members while the
+  // profile manager is still alive.
+  extensions_browser_client_->StartTearDown();
+#endif
+
   // Need to clear profiles (download managers) before the IO thread.
   {
     TRACE_EVENT0("shutdown",
@@ -472,6 +478,8 @@
   }
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
+  // The `media_file_system_registry_` cannot be reset until the
+  // `profile_manager_` has been.
   media_file_system_registry_.reset();
   // Remove the global instance of the Storage Monitor now. Otherwise the
   // FILE thread would be gone when we try to release it in the dtor and
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index ecae3d0e..0130685 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -59,7 +59,7 @@
 }  // namespace breadcrumbs
 
 namespace extensions {
-class ExtensionsBrowserClient;
+class ChromeExtensionsBrowserClient;
 }
 
 namespace gcm {
@@ -282,7 +282,7 @@
   std::unique_ptr<GpuModeManager> gpu_mode_manager_;
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-  std::unique_ptr<extensions::ExtensionsBrowserClient>
+  std::unique_ptr<extensions::ChromeExtensionsBrowserClient>
       extensions_browser_client_;
 
   scoped_refptr<extensions::EventRouterForwarder>
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index ddc5be9..d61bf8fd 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -1184,11 +1184,9 @@
       ash::diagnostics::mojom::InputDataProvider, ash::DiagnosticsDialogUI>(
       map);
 
-  if (ash::features::IsNetworkingInDiagnosticsAppEnabled()) {
-    RegisterWebUIControllerInterfaceBinder<
-        ash::diagnostics::mojom::NetworkHealthProvider,
-        ash::DiagnosticsDialogUI>(map);
-  }
+  RegisterWebUIControllerInterfaceBinder<
+      ash::diagnostics::mojom::NetworkHealthProvider, ash::DiagnosticsDialogUI>(
+      map);
 
   RegisterWebUIControllerInterfaceBinder<
       ash::diagnostics::mojom::SystemDataProvider, ash::DiagnosticsDialogUI>(
diff --git a/chrome/browser/content_settings/generated_cookie_prefs.cc b/chrome/browser/content_settings/generated_cookie_prefs.cc
index 63c6f702..74042a94 100644
--- a/chrome/browser/content_settings/generated_cookie_prefs.cc
+++ b/chrome/browser/content_settings/generated_cookie_prefs.cc
@@ -359,10 +359,15 @@
 
 extensions::settings_private::SetPrefResult
 GeneratedCookieDefaultContentSettingPref::SetPref(const base::Value* value) {
-  if (!value->is_int())
+  if (!value->is_string()) {
     return extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH;
+  }
 
-  int setting = value->GetInt();
+  ContentSetting setting;
+  if (!content_settings::ContentSettingFromString(value->GetString(),
+                                                  &setting)) {
+    return extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH;
+  }
   if (setting != CONTENT_SETTING_ALLOW &&
       setting != CONTENT_SETTING_SESSION_ONLY &&
       setting != CONTENT_SETTING_BLOCK) {
@@ -382,13 +387,14 @@
 GeneratedCookieDefaultContentSettingPref::GetPrefObject() const {
   auto pref_object = std::make_unique<settings_api::PrefObject>();
   pref_object->key = pref_name_;
-  pref_object->type = settings_api::PREF_TYPE_NUMBER;
+  pref_object->type = settings_api::PREF_TYPE_STRING;
 
   std::string content_setting_provider;
   auto content_setting = host_content_settings_map_->GetDefaultContentSetting(
       ContentSettingsType::COOKIES, &content_setting_provider);
 
-  pref_object->value = base::Value(content_setting);
+  pref_object->value =
+      base::Value(content_settings::ContentSettingToString(content_setting));
 
   // Cookies content setting can be managed via policy, extension or
   // supervision, but cannot be recommended.
diff --git a/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc b/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
index 1623061..3f5986e 100644
--- a/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
+++ b/chrome/browser/content_settings/generated_cookie_prefs_unittest.cc
@@ -549,37 +549,33 @@
   map->SetDefaultContentSetting(ContentSettingsType::COOKIES,
                                 CONTENT_SETTING_ALLOW);
   auto pref_object = pref->GetPrefObject();
-  EXPECT_EQ(pref_object->value->GetInt(), CONTENT_SETTING_ALLOW);
+  EXPECT_EQ(pref_object->value->GetString(), "allow");
 
   // Ensure setting the preference correctly updates content settings and the
   // preference state.
-  EXPECT_EQ(
-      pref->SetPref(
-          std::make_unique<base::Value>(CONTENT_SETTING_SESSION_ONLY).get()),
-      extensions::settings_private::SetPrefResult::SUCCESS);
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("session_only").get()),
+            extensions::settings_private::SetPrefResult::SUCCESS);
   EXPECT_EQ(
       map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr),
       CONTENT_SETTING_SESSION_ONLY);
   pref_object = pref->GetPrefObject();
-  EXPECT_EQ(pref_object->value->GetInt(), CONTENT_SETTING_SESSION_ONLY);
+  EXPECT_EQ(pref_object->value->GetString(), "session_only");
 
-  EXPECT_EQ(
-      pref->SetPref(std::make_unique<base::Value>(CONTENT_SETTING_ALLOW).get()),
-      extensions::settings_private::SetPrefResult::SUCCESS);
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("allow").get()),
+            extensions::settings_private::SetPrefResult::SUCCESS);
   EXPECT_EQ(
       map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr),
       CONTENT_SETTING_ALLOW);
   pref_object = pref->GetPrefObject();
-  EXPECT_EQ(pref_object->value->GetInt(), CONTENT_SETTING_ALLOW);
+  EXPECT_EQ(pref_object->value->GetString(), "allow");
 
-  EXPECT_EQ(
-      pref->SetPref(std::make_unique<base::Value>(CONTENT_SETTING_BLOCK).get()),
-      extensions::settings_private::SetPrefResult::SUCCESS);
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("block").get()),
+            extensions::settings_private::SetPrefResult::SUCCESS);
   EXPECT_EQ(
       map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr),
       CONTENT_SETTING_BLOCK);
   pref_object = pref->GetPrefObject();
-  EXPECT_EQ(pref_object->value->GetInt(), CONTENT_SETTING_BLOCK);
+  EXPECT_EQ(pref_object->value->GetString(), "block");
 }
 
 TEST_F(GeneratedCookiePrefsTest, DefaultContentSettingPref_TypeMismatch) {
@@ -589,12 +585,14 @@
   // Confirm that a type mismatch is reported as such.
   EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>(false).get()),
             extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
-  EXPECT_EQ(
-      pref->SetPref(std::make_unique<base::Value>(CONTENT_SETTING_ASK).get()),
-      extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
-  EXPECT_EQ(pref->SetPref(
-                std::make_unique<base::Value>(CONTENT_SETTING_DEFAULT).get()),
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("default").get()),
             extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("ask").get()),
+            extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
+  EXPECT_EQ(
+      pref->SetPref(
+          std::make_unique<base::Value>("detect_important_content").get()),
+      extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
   EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>(100).get()),
             extensions::settings_private::SetPrefResult::PREF_TYPE_MISMATCH);
 }
@@ -645,9 +643,8 @@
             settings_api::Enforcement::ENFORCEMENT_ENFORCED);
 
   // Ensure the preference cannot be changed when it is enforced.
-  EXPECT_EQ(
-      pref->SetPref(std::make_unique<base::Value>(CONTENT_SETTING_BLOCK).get()),
-      extensions::settings_private::SetPrefResult::PREF_NOT_MODIFIABLE);
+  EXPECT_EQ(pref->SetPref(std::make_unique<base::Value>("block").get()),
+            extensions::settings_private::SetPrefResult::PREF_NOT_MODIFIABLE);
   EXPECT_EQ(
       map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr),
       CONTENT_SETTING_ALLOW);
diff --git a/chrome/browser/enterprise/connectors/connectors_service.cc b/chrome/browser/enterprise/connectors/connectors_service.cc
index ec6ba37..65aeb49c 100644
--- a/chrome/browser/enterprise/connectors/connectors_service.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/enterprise/connectors/reporting/extension_install_event_router.h"
 #include "chrome/browser/enterprise/connectors/service_provider_config.h"
 #include "chrome/browser/enterprise/util/affiliation.h"
+#include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/policy/dm_token_utils.h"
 #include "chrome/browser/profiles/profile.h"
@@ -605,6 +606,13 @@
 
 content::BrowserContext* ConnectorsServiceFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
+  // Do not construct the connectors service if the extensions are disabled for
+  // the given context.
+  if (extensions::ChromeContentBrowserClientExtensionsPart::
+          AreExtensionsDisabledForProfile(context)) {
+    return nullptr;
+  }
+
   // On Chrome OS, settings from the primary/main profile apply to all
   // profiles, besides incognito.
   // However, the primary/main profile might not exist in tests - then the
diff --git a/chrome/browser/extensions/api/scripting/scripting_api.cc b/chrome/browser/extensions/api/scripting/scripting_api.cc
index 089980b0..4a0bdc63 100644
--- a/chrome/browser/extensions/api/scripting/scripting_api.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_api.cc
@@ -20,7 +20,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
-#include "extensions/browser/api/extension_types_utils.h"
 #include "extensions/browser/api/scripting/scripting_constants.h"
 #include "extensions/browser/extension_api_frame_id_map.h"
 #include "extensions/browser/extension_file_task_runner.h"
@@ -41,6 +40,7 @@
 #include "extensions/common/permissions/api_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/utils/content_script_utils.h"
+#include "extensions/common/utils/extension_types_utils.h"
 
 namespace extensions {
 
@@ -51,6 +51,9 @@
     "Duplicate file specified: '*'.";
 constexpr char kExactlyOneOfCssAndFilesError[] =
     "Exactly one of 'css' and 'files' must be specified.";
+constexpr char kFilesExceededSizeLimitError[] =
+    "Scripts could not be loaded because '*' exceeds the maximum script size "
+    "or the extension's maximum total script size.";
 
 // Note: CSS always injects as soon as possible, so we default to
 // document_start. Because of tab loading, there's no guarantee this will
@@ -505,6 +508,15 @@
   bool are_script_files_valid = script_parsing::ValidateFileSources(
       *scripts, symlink_policy, &error, &warnings);
 
+  // Script files over the per script/extension limit are recorded as warnings.
+  // However, for the scripting API we should treat "install warnings" as
+  // errors by turning this call into a no-op and returning an error.
+  if (!warnings.empty() && error.empty()) {
+    error = ErrorUtils::FormatErrorMessage(kFilesExceededSizeLimitError,
+                                           warnings[0].specific);
+    are_script_files_valid = false;
+  }
+
   return std::make_pair(std::move(scripts), are_script_files_valid
                                                 ? absl::nullopt
                                                 : absl::make_optional(error));
diff --git a/chrome/browser/extensions/api/scripting/scripting_apitest.cc b/chrome/browser/extensions/api/scripting/scripting_apitest.cc
index 6012620a..f7ba1aa7 100644
--- a/chrome/browser/extensions/api/scripting/scripting_apitest.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_apitest.cc
@@ -22,6 +22,7 @@
 #include "extensions/browser/background_script_executor.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/common/features/feature_channel.h"
+#include "extensions/common/utils/content_script_utils.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
@@ -190,6 +191,15 @@
       embedded_test_server()->GetURL("google.com", "/simple.html"));
 }
 
+IN_PROC_BROWSER_TEST_F(ScriptingAPITest, DynamicContentScriptsSizeLimits) {
+  auto single_scripts_limit_reset =
+      script_parsing::CreateScopedMaxScriptLengthForTesting(700u);
+  auto extension_scripts_limit_reset =
+      script_parsing::CreateScopedMaxScriptsLengthPerExtensionForTesting(1200u);
+  ASSERT_TRUE(RunExtensionTest("scripting/dynamic_scripts_size_limits"))
+      << message_;
+}
+
 // Tests that calling scripting.executeScript works on a newly created tab
 // before the initial commit has happened. Regression for crbug.com/1191971.
 IN_PROC_BROWSER_TEST_F(ScriptingAPITest, ExecuteScriptBeforeInitialCommit) {
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 49fa6bc8..d616df56 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -327,7 +327,7 @@
   (*s_allowlist)[::prefs::kCookieControlsMode] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
   (*s_allowlist)[::content_settings::kCookieDefaultContentSetting] =
-      settings_api::PrefType::PREF_TYPE_NUMBER;
+      settings_api::PrefType::PREF_TYPE_STRING;
   (*s_allowlist)[::content_settings::kCookiePrimarySetting] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
   (*s_allowlist)[::content_settings::kCookieSessionOnly] =
diff --git a/chrome/browser/extensions/chrome_app_sorting.cc b/chrome/browser/extensions/chrome_app_sorting.cc
index 4086af58..f51ac242 100644
--- a/chrome/browser/extensions/chrome_app_sorting.cc
+++ b/chrome/browser/extensions/chrome_app_sorting.cc
@@ -79,6 +79,7 @@
       default_ordinals_created_(false) {
   ExtensionIdList extensions;
   ExtensionPrefs::Get(browser_context_)->GetExtensions(&extensions);
+  registry_observation_.Observe(ExtensionRegistry::Get(browser_context_));
   InitializePageOrdinalMap(extensions);
   MigrateAppIndex(extensions);
 }
@@ -745,4 +746,15 @@
   return result;
 }
 
+void ChromeAppSorting::OnExtensionLoaded(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  if (!extension->RequiresSortOrdinal()) {
+    return;
+  }
+
+  SetExtensionVisible(extension->id(), extension->ShouldDisplayInNewTabPage());
+  EnsureValidOrdinals(extension->id(), syncer::StringOrdinal());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/chrome_app_sorting.h b/chrome/browser/extensions/chrome_app_sorting.h
index 4df5721..46096842 100644
--- a/chrome/browser/extensions/chrome_app_sorting.h
+++ b/chrome/browser/extensions/chrome_app_sorting.h
@@ -24,6 +24,8 @@
 #include "components/sync/model/string_ordinal.h"
 #include "extensions/browser/app_sorting.h"
 #include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_id.h"
 
 namespace web_app {
@@ -34,6 +36,7 @@
 namespace extensions {
 
 class ChromeAppSorting : public AppSorting,
+                         public ExtensionRegistryObserver,
                          public web_app::AppRegistrarObserver,
                          public web_app::WebAppInstallManagerObserver {
  public:
@@ -175,6 +178,10 @@
   // Returns the number of items in |m| visible on the new tab page.
   size_t CountItemsVisibleOnNtp(const AppLaunchOrdinalMap& m) const;
 
+  // ExtensionRegistryObserver:
+  void OnExtensionLoaded(content::BrowserContext* browser_context,
+                         const Extension* extension) override;
+
   const raw_ptr<content::BrowserContext, DanglingUntriaged> browser_context_ =
       nullptr;
   raw_ptr<const web_app::WebAppRegistrar, DanglingUntriaged>
@@ -207,6 +214,12 @@
   // The set of extensions that don't appear in the new tab page.
   std::set<std::string> ntp_hidden_extensions_;
 
+  // Observe the ExtensionRegistry. The registry is guaranteed to outlive this
+  // object, since this is owned by the ExtensionSystem, which depends on the
+  // ExtensionRegistry.
+  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
+      registry_observation_{this};
+
   base::WeakPtrFactory<ChromeAppSorting> weak_factory_{this};
 };
 
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc
index f016bcf..ff14f8f6 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.cc
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc
@@ -142,6 +142,10 @@
 
 ChromeExtensionsBrowserClient::~ChromeExtensionsBrowserClient() {}
 
+void ChromeExtensionsBrowserClient::StartTearDown() {
+  user_script_listener_.StartTearDown();
+}
+
 bool ChromeExtensionsBrowserClient::IsShuttingDown() {
   return g_browser_process->IsShuttingDown();
 }
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h
index 90cec7b..d33bb1c2 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.h
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.h
@@ -52,6 +52,14 @@
 
   ~ChromeExtensionsBrowserClient() override;
 
+  // Called by the BrowserProcess to indicate that we should perform any
+  // teardown necessary before being destroyed (e.g. unsubscribing observers, or
+  // any other pre-emptive freeing of resources. Note that we may still receive
+  // calls from other shutting down objects after this call, so this should
+  // primarily be used for things that may need to be cleaned up before other
+  // parts of the browser).
+  void StartTearDown();
+
   // ExtensionsBrowserClient overrides:
   bool IsShuttingDown() override;
   bool AreExtensionsDisabled(const base::CommandLine& command_line,
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 8960971..a2552b8f 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -431,15 +431,22 @@
   expected_warnings.emplace_back(
       l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTENT_SCRIPT_FILE_TOO_LARGE,
                                 u"big.js"),
-      api::content_scripts::ManifestKeys::kContentScripts);
+      api::content_scripts::ManifestKeys::kContentScripts, "big.js");
   expected_warnings.emplace_back(
       l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTENT_SCRIPT_FILE_TOO_LARGE,
                                 u"inject_element_2.js"),
-      api::content_scripts::ManifestKeys::kContentScripts);
+      api::content_scripts::ManifestKeys::kContentScripts,
+      "inject_element_2.js");
 
   EXPECT_EQ(extension->install_warnings(), expected_warnings);
 }
 
+IN_PROC_BROWSER_TEST_F(ContentScriptApiTest, MainWorldInjections) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ASSERT_TRUE(RunExtensionTest("content_scripts/main_world_injections"))
+      << message_;
+}
+
 class ContentScriptCssInjectionTest : public ExtensionApiTest {
  protected:
   // TODO(rdevlin.cronin): Make a testing switch that looks like FeatureSwitch,
diff --git a/chrome/browser/extensions/user_script_listener.cc b/chrome/browser/extensions/user_script_listener.cc
index 98766f4..9e937da2 100644
--- a/chrome/browser/extensions/user_script_listener.cc
+++ b/chrome/browser/extensions/user_script_listener.cc
@@ -10,13 +10,11 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/timer/elapsed_timer.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle.h"
-#include "content/public/browser/notification_service.h"
 #include "extensions/browser/api/scripting/scripting_utils.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/user_script_manager.h"
@@ -94,10 +92,9 @@
       extension_registry_observations_.AddObservation(
           ExtensionRegistry::Get(profile));
     }
-  }
 
-  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED,
-                 content::NotificationService::AllSources());
+    profile_manager_observation_.Observe(g_browser_process->profile_manager());
+  }
 }
 
 std::unique_ptr<NavigationThrottle>
@@ -115,6 +112,10 @@
   UserScriptsReady(context);
 }
 
+void UserScriptListener::StartTearDown() {
+  profile_manager_observation_.Reset();
+}
+
 void UserScriptListener::SetUserScriptsNotReadyForTesting(
     content::BrowserContext* context) {
   AppendNewURLPatterns(context, {URLPattern(URLPattern::SCHEME_ALL,
@@ -218,25 +219,16 @@
                    dynamic_patterns.end());
 }
 
-void UserScriptListener::Observe(int type,
-                                 const content::NotificationSource& source,
-                                 const content::NotificationDetails& details) {
-  switch (type) {
-    case chrome::NOTIFICATION_PROFILE_ADDED: {
-      Profile* profile = content::Source<Profile>(source).ptr();
-      if (extensions::ChromeContentBrowserClientExtensionsPart::
-              AreExtensionsDisabledForProfile(profile)) {
-        break;
-      }
-      auto* registry = ExtensionRegistry::Get(profile);
-      DCHECK(registry);
-      DCHECK(!extension_registry_observations_.IsObservingSource(registry));
-      extension_registry_observations_.AddObservation(registry);
-      break;
-    }
-    default:
-      NOTREACHED();
+void UserScriptListener::OnProfileAdded(Profile* profile) {
+  if (extensions::ChromeContentBrowserClientExtensionsPart::
+          AreExtensionsDisabledForProfile(profile)) {
+    return;
   }
+
+  auto* registry = ExtensionRegistry::Get(profile);
+  DCHECK(registry);
+  DCHECK(!extension_registry_observations_.IsObservingSource(registry));
+  extension_registry_observations_.AddObservation(registry);
 }
 
 void UserScriptListener::OnExtensionLoaded(
diff --git a/chrome/browser/extensions/user_script_listener.h b/chrome/browser/extensions/user_script_listener.h
index 00e0001..5c9760d5 100644
--- a/chrome/browser/extensions/user_script_listener.h
+++ b/chrome/browser/extensions/user_script_listener.h
@@ -11,13 +11,14 @@
 #include "base/containers/circular_deque.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_multi_source_observation.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class GURL;
 class URLPattern;
+class ProfileManager;
 
 namespace content {
 class BrowserContext;
@@ -34,8 +35,8 @@
 // script has not been loaded yet, then we delay the request.
 //
 // This class lives on the UI thread.
-class UserScriptListener : public content::NotificationObserver,
-                           public ExtensionRegistryObserver {
+class UserScriptListener : public ExtensionRegistryObserver,
+                           public ProfileManagerObserver {
  public:
   UserScriptListener();
 
@@ -53,6 +54,10 @@
   // BrowserContext.
   void OnScriptsLoaded(content::BrowserContext* context);
 
+  // Called when the owning BrowserClient is notified that we should begin
+  // releasing our resources.
+  void StartTearDown();
+
   void SetUserScriptsNotReadyForTesting(content::BrowserContext* context);
   void TriggerUserScriptsReadyForTesting(content::BrowserContext* context);
 
@@ -104,10 +109,8 @@
                           const Extension* extension,
                           URLPatterns* patterns);
 
-  // content::NotificationObserver
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  // ProfileManagerObserver
+  void OnProfileAdded(Profile* profile) override;
 
   // ExtensionRegistryObserver:
   void OnExtensionLoaded(content::BrowserContext* browser_context,
@@ -121,7 +124,8 @@
                                      extensions::ExtensionRegistryObserver>
       extension_registry_observations_{this};
 
-  content::NotificationRegistrar registrar_;
+  base::ScopedObservation<ProfileManager, ProfileManagerObserver>
+      profile_manager_observation_{this};
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/user_script_listener_unittest.cc b/chrome/browser/extensions/user_script_listener_unittest.cc
index 011c7e9..2ab843b7 100644
--- a/chrome/browser/extensions/user_script_listener_unittest.cc
+++ b/chrome/browser/extensions/user_script_listener_unittest.cc
@@ -86,12 +86,20 @@
         profile_manager_(
             new TestingProfileManager(TestingBrowserProcess::GetGlobal())) {}
 
+  ~UserScriptListenerTest() override {}
+
+  UserScriptListenerTest(const UserScriptListenerTest&) = delete;
+  UserScriptListenerTest& operator=(const UserScriptListenerTest&) = delete;
+
   void SetUp() override {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
         std::make_unique<ash::FakeChromeUserManager>());
 #endif
     ASSERT_TRUE(profile_manager_->SetUp());
+    // The listener must be set up after the profile manager has been set up/
+    // installed itself on the browser process.
+    listener_ = std::make_unique<UserScriptListener>();
     profile_ = profile_manager_->CreateTestingProfile("test-profile");
     ASSERT_TRUE(profile_);
     TestExtensionSystem* test_extension_system =
@@ -105,6 +113,14 @@
         profile_, std::move(instance));
   }
 
+  void TearDown() override {
+    // The Listener unsubscribes itself from the profile in StartTearDown;
+    // failure to unsubscribe will result in the profile_manager's destructor
+    // throwing an error since there's still a subscription in the callback
+    // list.
+    listener_->StartTearDown();
+  }
+
   void MarkNavigationResumed() { was_navigation_resumed_ = true; }
 
  protected:
@@ -133,7 +149,7 @@
   std::unique_ptr<NavigationThrottle> CreateListenerNavigationThrottle(
       content::NavigationHandle* handle) {
     std::unique_ptr<NavigationThrottle> throttle =
-        listener_.CreateNavigationThrottle(handle);
+        listener_->CreateNavigationThrottle(handle);
     throttle->set_resume_callback_for_testing(
         base::BindRepeating(&UserScriptListenerTest::MarkNavigationResumed,
                             base::Unretained(this)));
@@ -151,7 +167,7 @@
   content::BrowserTaskEnvironment task_environment_;
   content::RenderViewHostTestEnabler rvh_test_enabler_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
-  UserScriptListener listener_;
+  std::unique_ptr<UserScriptListener> listener_;
   raw_ptr<TestingProfile> profile_ = nullptr;
   raw_ptr<ExtensionService> service_ = nullptr;
   bool was_navigation_resumed_ = false;
@@ -172,7 +188,7 @@
       CreateListenerNavigationThrottle(&handle);
   EXPECT_EQ(NavigationThrottle::DEFER, throttle->WillStartRequest());
 
-  listener_.TriggerUserScriptsReadyForTesting(profile_);
+  listener_->TriggerUserScriptsReadyForTesting(profile_);
   EXPECT_TRUE(was_navigation_resumed_);
 }
 
@@ -190,7 +206,7 @@
       CreateListenerNavigationThrottle(&handle);
   EXPECT_EQ(NavigationThrottle::DEFER, throttle->WillStartRequest());
 
-  listener_.TriggerUserScriptsReadyForTesting(profile_);
+  listener_->TriggerUserScriptsReadyForTesting(profile_);
   EXPECT_TRUE(was_navigation_resumed_);
 }
 
@@ -210,7 +226,7 @@
   // listener that the user scripts have been updated.
   EXPECT_FALSE(was_navigation_resumed_);
 
-  listener_.TriggerUserScriptsReadyForTesting(profile_);
+  listener_->TriggerUserScriptsReadyForTesting(profile_);
   EXPECT_TRUE(was_navigation_resumed_);
 }
 
@@ -218,7 +234,7 @@
   content::MockNavigationHandle handle(GURL(kMatchingUrl),
                                        web_contents_->GetPrimaryMainFrame());
   std::unique_ptr<NavigationThrottle> throttle =
-      listener_.CreateNavigationThrottle(&handle);
+      listener_->CreateNavigationThrottle(&handle);
   EXPECT_EQ(nullptr, throttle);
 }
 
@@ -229,7 +245,7 @@
   content::MockNavigationHandle handle(GURL(kNotMatchingUrl),
                                        web_contents_->GetPrimaryMainFrame());
   std::unique_ptr<NavigationThrottle> throttle =
-      listener_.CreateNavigationThrottle(&handle);
+      listener_->CreateNavigationThrottle(&handle);
   EXPECT_EQ(nullptr, throttle);
 }
 
@@ -258,11 +274,11 @@
 
   // When the first profile's user scripts are ready, the request should still
   // be blocked waiting for profile2.
-  listener_.TriggerUserScriptsReadyForTesting(profile_);
+  listener_->TriggerUserScriptsReadyForTesting(profile_);
   EXPECT_FALSE(was_navigation_resumed_);
 
   // After profile2 is ready, the request should proceed.
-  listener_.TriggerUserScriptsReadyForTesting(profile2);
+  listener_->TriggerUserScriptsReadyForTesting(profile2);
   EXPECT_TRUE(was_navigation_resumed_);
 }
 
@@ -274,10 +290,10 @@
   content::MockNavigationHandle handle(GURL(kMatchingUrl),
                                        web_contents_->GetPrimaryMainFrame());
   std::unique_ptr<NavigationThrottle> throttle =
-      listener_.CreateNavigationThrottle(&handle);
+      listener_->CreateNavigationThrottle(&handle);
   ASSERT_TRUE(throttle);
 
-  listener_.TriggerUserScriptsReadyForTesting(profile_);
+  listener_->TriggerUserScriptsReadyForTesting(profile_);
 
   ASSERT_EQ(content::NavigationThrottle::PROCEED, throttle->WillStartRequest());
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 699925cc..937095c 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2671,11 +2671,6 @@
     "expiry_milestone": -1
   },
   {
-    "name": "enable-networking-in-diagnostics-app",
-    "owners": [ "//ash/webui/diagnostics_ui/OWNERS" ],
-    "expiry_milestone": 112
-  },
-  {
     "name": "enable-neural-palm-adaptive-hold",
     "owners": [ "robsc", "napper", "alanlxl" ],
     "expiry_milestone": 130
@@ -3202,7 +3197,7 @@
   {
     "name": "enable-user-policy",
     "owners": [ "vincb" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 130
   },
   {
     "name": "enable-vaapi-av1-decode-acceleration",
@@ -3600,11 +3595,6 @@
     "expiry_milestone": 112
   },
   {
-    "name": "fast-pair-subsequent-pairing-ux",
-    "owners": [ "//ash/quick_pair/OWNERS", "dclasson@google.com" ],
-    "expiry_milestone": 106
-  },
-  {
     "name": "feature-notification-guide",
     "owners": [ "shaktisahu"],
     "expiry_milestone": 110
@@ -5257,7 +5247,7 @@
     "expiry_milestone": 117
   },
   {
-    "name": "oobe-material-next",
+    "name": "oobe-jelly",
     "owners": [ "ziegltrum@google.com", "cros-oobe@google.com" ],
     "expiry_milestone": 118
   },
@@ -6315,16 +6305,6 @@
     "expiry_milestone": 114
   },
   {
-    "name": "smart-lock-sign-in-removed",
-    "owners": [ "bhartmire", "better-together-dev@google.com" ],
-    "expiry_milestone": 105
-  },
-  {
-    "name": "smart-lock-ui-revamp",
-    "owners": [ "cclem", "better-together-dev@google.com" ],
-    "expiry_milestone": 105
-  },
-  {
     "name": "smart-sorting-new-overflow-menu",
     "owners": [ "bwwilliams@google.com", "bling-flags@google.com" ],
     "expiry_milestone": 111
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 37dfe92..972f87fb 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3474,10 +3474,6 @@
     "of the screen height, otherwise the maximum height is 100% of the screen "
     "height. In both cases, Custom Tabs will yield to the top status bar when "
     "at full stop";
-const char kCCTResizableAllowResizeByUserGestureName[] =
-    "Bottom sheet Custom Tabs allow resize by user gesture";
-const char kCCTResizableAllowResizeByUserGestureDescription[] =
-    "Enable user gesture to resize bottom sheet Custom Tabs";
 const char kCCTResizableForThirdPartiesName[] =
     "Bottom sheet Custom Tabs (third party)";
 const char kCCTResizableForThirdPartiesDescription[] =
@@ -4992,12 +4988,6 @@
     "Allow using Fast Pair on devices which don't support hardware offloading "
     "of BLE scans. For development use.";
 
-const char kFastPairSubsequentPairingUXName[] =
-    "Enable Fast Pair Subsequent Pairing UX";
-const char kFastPairSubsequentPairingUXDescription[] =
-    "Enables the \"Subsequent Pairing\" Fast Pair scenario in Bluetooth "
-    "Settings and Quick Settings.";
-
 const char kFastPairSavedDevicesName[] = "Enable Fast Pair Saved Devices";
 const char kFastPairSavedDevicesDescription[] =
     "Enables the Fast Pair \"Saved Devices\" page to display a list of the "
@@ -5281,11 +5271,6 @@
     "Allows CrOS to analyze Android network information to provide more "
     "context on connection errors";
 
-const char kEnableNetworkingInDiagnosticsAppName[] =
-    "Enable networking cards in the Diagnostics App";
-const char kEnableNetworkingInDiagnosticsAppDescription[] =
-    "Enable networking cards in the Diagnostics App";
-
 const char kEnableOAuthIppName[] =
     "Enable OAuth when printing via the IPP protocol";
 const char kEnableOAuthIppDescription[] =
@@ -5831,17 +5816,6 @@
     "Ctrl-Alt-I shows a heads-up display view in the top-left corner. Helps "
     "debug hardware issues that generate spurious touch events.";
 
-const char kSmartLockSignInRemovedName[] = "Remove Sign in with Smart Lock";
-const char kSmartLockSignInRemovedDescription[] =
-    "Deprecates Sign in with Smart Lock feature. Hides Smart Lock on the sign "
-    "in screen, removes the Smart Lock subpage in settings, and shows a "
-    "one-time notification for users who previously had this feature enabled.";
-
-const char kSmartLockUIRevampName[] = "Enable Smart Lock UI Revamp";
-const char kSmartLockUIRevampDescription[] =
-    "Replaces the existing Smart Lock UI on the lock screen with a new design "
-    "and adds Smart Lock to the 'Lock screen and sign-in' section of settings.";
-
 const char kSpectreVariant2MitigationName[] = "Spectre variant 2 mitigation";
 const char kSpectreVariant2MitigationDescription[] =
     "Controls whether Spectre variant 2 mitigation is enabled when "
@@ -6012,9 +5986,9 @@
     "Feature to allow MAC address randomization to be enabled for WiFi "
     "networks.";
 
-const char kOobeMaterialNextName[] = "Material Next design for OOBE";
-const char kOobeMaterialNextDescription[] =
-    "Feature to enable the Material Next design in out of box experience.";
+const char kOobeJellyName[] = "Jelly design for OOBE";
+const char kOobeJellyDescription[] =
+    "Feature to enable the Jelly design in out of box experience.";
 
 const char kLibAssistantV2MigrationName[] = "LibAssistant V2 migration";
 const char kLibAssistantV2MigrationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index c15183b..b5481b28 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1982,8 +1982,6 @@
 
 extern const char kCCTResizable90MaximumHeightName[];
 extern const char kCCTResizable90MaximumHeightDescription[];
-extern const char kCCTResizableAllowResizeByUserGestureName[];
-extern const char kCCTResizableAllowResizeByUserGestureDescription[];
 extern const char kCCTResizableForThirdPartiesName[];
 extern const char kCCTResizableForThirdPartiesDescription[];
 extern const char kCCTResizableSideSheetName[];
@@ -2868,9 +2866,6 @@
 extern const char kFastPairSoftwareScanningName[];
 extern const char kFastPairSoftwareScanningDescription[];
 
-extern const char kFastPairSubsequentPairingUXName[];
-extern const char kFastPairSubsequentPairingUXDescription[];
-
 extern const char kFastPairSavedDevicesName[];
 extern const char kFastPairSavedDevicesDescription[];
 
@@ -3023,9 +3018,6 @@
 extern const char kEcheSWACheckAndroidNetworkInfoName[];
 extern const char kEcheSWACheckAndroidNetworkInfoDescription[];
 
-extern const char kEnableNetworkingInDiagnosticsAppName[];
-extern const char kEnableNetworkingInDiagnosticsAppDescription[];
-
 extern const char kEnableOAuthIppName[];
 extern const char kEnableOAuthIppDescription[];
 
@@ -3360,12 +3352,6 @@
 extern const char kSimLockPolicyName[];
 extern const char kSimLockPolicyDescription[];
 
-extern const char kSmartLockSignInRemovedName[];
-extern const char kSmartLockSignInRemovedDescription[];
-
-extern const char kSmartLockUIRevampName[];
-extern const char kSmartLockUIRevampDescription[];
-
 extern const char kSnoopingProtectionName[];
 extern const char kSnoopingProtectionDescription[];
 
@@ -3468,8 +3454,8 @@
 extern const char kMacAddressRandomizationName[];
 extern const char kMacAddressRandomizationDescription[];
 
-extern const char kOobeMaterialNextName[];
-extern const char kOobeMaterialNextDescription[];
+extern const char kOobeJellyName[];
+extern const char kOobeJellyDescription[];
 
 extern const char kLibAssistantV2MigrationName[];
 extern const char kLibAssistantV2MigrationDescription[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 5d215b3..d12a248 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -192,7 +192,6 @@
     &kCCTRemoveRemoteViewIds,
     &kCCTReportParallelRequestStatus,
     &kCCTResizable90MaximumHeight,
-    &kCCTResizableAllowResizeByUserGesture,
     &kCCTResizableForThirdParties,
     &kCCTResizableSideSheet,
     &kCCTRetainingStateInMemory,
@@ -558,10 +557,6 @@
              "CCTResizable90MaximumHeight",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kCCTResizableAllowResizeByUserGesture,
-             "CCTResizableAllowResizeByUserGesture",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kCCTResizableForThirdParties,
              "CCTResizableForThirdParties",
              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 fec97553..e209833 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -49,7 +49,6 @@
 BASE_DECLARE_FEATURE(kCCTRemoveRemoteViewIds);
 BASE_DECLARE_FEATURE(kCCTReportParallelRequestStatus);
 BASE_DECLARE_FEATURE(kCCTResizable90MaximumHeight);
-BASE_DECLARE_FEATURE(kCCTResizableAllowResizeByUserGesture);
 BASE_DECLARE_FEATURE(kCCTResizableForThirdParties);
 BASE_DECLARE_FEATURE(kCCTResizableSideSheet);
 BASE_DECLARE_FEATURE(kCCTResourcePrefetch);
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 e6e0143..e158310 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
@@ -573,8 +573,6 @@
             new CachedFlag(CCT_REMOVE_REMOTE_VIEW_IDS, true);
     public static final CachedFlag sCctResizable90MaximumHeight =
             new CachedFlag(CCT_RESIZABLE_90_MAXIMUM_HEIGHT, false);
-    public static final CachedFlag sCctResizableAllowResizeByUserGesture =
-            new CachedFlag(CCT_RESIZABLE_ALLOW_RESIZE_BY_USER_GESTURE, false);
     public static final CachedFlag sCctResizableForThirdParties =
             new CachedFlag(CCT_RESIZABLE_FOR_THIRD_PARTIES, false);
     public static final CachedFlag sCctResizableSideSheet =
diff --git a/chrome/browser/net/net_error_diagnostics_dialog_chromeos.cc b/chrome/browser/net/net_error_diagnostics_dialog_chromeos.cc
index 9ca45d50..1e0758a 100644
--- a/chrome/browser/net/net_error_diagnostics_dialog_chromeos.cc
+++ b/chrome/browser/net/net_error_diagnostics_dialog_chromeos.cc
@@ -4,16 +4,13 @@
 
 #include "chrome/browser/net/net_error_diagnostics_dialog.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/webui/connectivity_diagnostics/url_constants.h"
-#include "base/check.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/chrome_pages.h"
 
 namespace {
 void LaunchDiagnosticsAppAtConnectivityScreen(Profile* profile) {
-  DCHECK(ash::features::IsNetworkingInDiagnosticsAppEnabled());
   std::string diagnostics_connectivity_url = {
       "chrome://diagnostics/?connectivity"};
   ash::SystemAppLaunchParams params;
@@ -33,10 +30,5 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
 
-  if (ash::features::IsNetworkingInDiagnosticsAppEnabled()) {
-    LaunchDiagnosticsAppAtConnectivityScreen(std::move(profile));
-  } else {
-    ash::LaunchSystemWebAppAsync(
-        profile, ash::SystemWebAppType::CONNECTIVITY_DIAGNOSTICS);
-  }
+  LaunchDiagnosticsAppAtConnectivityScreen(std::move(profile));
 }
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index 8baeb82..03a8f87 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -437,6 +437,7 @@
     "password_store_android_backend_bridge_helper_impl_unittest.cc",
     "password_store_android_backend_unittest.cc",
     "password_sync_controller_delegate_android_unittest.cc",
+    "save_update_password_message_delegate_unittest.cc",
   ]
 
   deps = [
diff --git a/chrome/browser/password_manager/android/password_sync_controller_delegate_android.cc b/chrome/browser/password_manager/android/password_sync_controller_delegate_android.cc
index 225b73c2..124908e 100644
--- a/chrome/browser/password_manager/android/password_sync_controller_delegate_android.cc
+++ b/chrome/browser/password_manager/android/password_sync_controller_delegate_android.cc
@@ -55,7 +55,8 @@
     syncer::SyncService* sync_service) {
   sync_service_ = sync_service;
   sync_observation_.Observe(sync_service);
-  is_sync_enabled_ = IsSyncEnabled(IsPasswordSyncEnabled(sync_service_));
+  is_sync_enabled_ = IsSyncEnabled(IsPasswordSyncEnabled(sync_service));
+  UpdateCredentialManagerSyncStatus(is_sync_enabled_.value());
 }
 
 void PasswordSyncControllerDelegateAndroid::OnSyncStarting(
@@ -130,20 +131,7 @@
 
 void PasswordSyncControllerDelegateAndroid::OnStateChanged(
     syncer::SyncService* sync) {
-  // Notify credential manager about current account on startup or if
-  // password sync setting has changed.
-  if (sync_util::IsPasswordSyncEnabled(sync) &&
-      (!credential_manager_sync_setting_.has_value() ||
-       credential_manager_sync_setting_ == IsSyncEnabled(false))) {
-    bridge_->NotifyCredentialManagerWhenSyncing();
-    credential_manager_sync_setting_ = IsSyncEnabled(true);
-  }
-  if (!sync_util::IsPasswordSyncEnabled(sync) &&
-      (!credential_manager_sync_setting_.has_value() ||
-       credential_manager_sync_setting_ == IsSyncEnabled(true))) {
-    bridge_->NotifyCredentialManagerWhenNotSyncing();
-    credential_manager_sync_setting_ = IsSyncEnabled(false);
-  }
+  UpdateCredentialManagerSyncStatus(IsSyncEnabled(IsPasswordSyncEnabled(sync)));
 }
 
 void PasswordSyncControllerDelegateAndroid::OnSyncShutdown(
@@ -173,6 +161,21 @@
   }
 }
 
+void PasswordSyncControllerDelegateAndroid::UpdateCredentialManagerSyncStatus(
+    IsSyncEnabled is_enabled) {
+  if (credential_manager_sync_setting_.has_value() &&
+      credential_manager_sync_setting_ == is_enabled) {
+    return;
+  }
+
+  credential_manager_sync_setting_ = is_enabled;
+  if (is_enabled) {
+    bridge_->NotifyCredentialManagerWhenSyncing();
+  } else {
+    bridge_->NotifyCredentialManagerWhenNotSyncing();
+  }
+}
+
 base::WeakPtr<syncer::ModelTypeControllerDelegate>
 PasswordSyncControllerDelegateAndroid::GetWeakPtrToBaseClass() {
   return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/password_manager/android/password_sync_controller_delegate_android.h b/chrome/browser/password_manager/android/password_sync_controller_delegate_android.h
index cb345df..20322327 100644
--- a/chrome/browser/password_manager/android/password_sync_controller_delegate_android.h
+++ b/chrome/browser/password_manager/android/password_sync_controller_delegate_android.h
@@ -72,6 +72,10 @@
  private:
   using IsSyncEnabled = base::StrongAlias<struct IsSyncEnabledTag, bool>;
 
+  // Notify credential manager about current account on startup or if
+  // password sync setting has changed.
+  void UpdateCredentialManagerSyncStatus(IsSyncEnabled is_enabled);
+
   base::WeakPtr<syncer::ModelTypeControllerDelegate> GetWeakPtrToBaseClass();
 
   const std::unique_ptr<PasswordSyncControllerDelegateBridge> bridge_;
diff --git a/chrome/browser/password_manager/android/password_sync_controller_delegate_android_unittest.cc b/chrome/browser/password_manager/android/password_sync_controller_delegate_android_unittest.cc
index f63fac52..b864cb7 100644
--- a/chrome/browser/password_manager/android/password_sync_controller_delegate_android_unittest.cc
+++ b/chrome/browser/password_manager/android/password_sync_controller_delegate_android_unittest.cc
@@ -45,7 +45,7 @@
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
 
   MockPasswordSyncControllerDelegateBridge* bridge() { return bridge_; }
-  syncer::SyncService* sync_service() { return &sync_service_; }
+  syncer::TestSyncService* sync_service() { return &sync_service_; }
   PasswordSyncControllerDelegateAndroid* sync_controller_delegate() {
     return sync_controller_delegate_.get();
   }
@@ -70,15 +70,54 @@
 };
 
 TEST_F(PasswordSyncControllerDelegateAndroidTest,
+       OnSyncStatusEnabledOnStartup) {
+  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenSyncing);
+  sync_controller_delegate()->OnSyncServiceInitialized(sync_service());
+  testing::Mock::VerifyAndClearExpectations(bridge());
+
+  // Check that observing the same event again will not trigger another
+  // notification.
+  sync_controller_delegate()->OnStateChanged(sync_service());
+}
+
+TEST_F(PasswordSyncControllerDelegateAndroidTest,
+       OnSyncStatusEnabledWithoutPasswordsOnStartup) {
+  sync_service()->GetUserSettings()->SetSelectedTypes(/*sync_everything=*/false,
+                                                      /*types=*/{});
+
+  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenNotSyncing);
+  sync_controller_delegate()->OnStateChanged(sync_service());
+  testing::Mock::VerifyAndClearExpectations(bridge());
+
+  // Check that observing the same event again will not trigger another
+  // notification.
+  sync_controller_delegate()->OnStateChanged(sync_service());
+}
+
+TEST_F(PasswordSyncControllerDelegateAndroidTest,
+       OnSyncStatusDisabledOnStartup) {
+  sync_service()->SetDisableReasons(
+      syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN);
+
+  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenNotSyncing);
+  sync_controller_delegate()->OnStateChanged(sync_service());
+  testing::Mock::VerifyAndClearExpectations(bridge());
+
+  // Check that observing the same event again will not trigger another
+  // notification.
+  sync_controller_delegate()->OnStateChanged(sync_service());
+}
+
+TEST_F(PasswordSyncControllerDelegateAndroidTest,
        OnSyncStatusChangedToEnabledAfterStartup) {
   syncer::TestSyncService sync_service;
 
   EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenSyncing);
   sync_controller_delegate()->OnStateChanged(&sync_service);
+  testing::Mock::VerifyAndClearExpectations(bridge());
 
   // Check that observing the same event again will not trigger another
   // notification.
-  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenSyncing).Times(0);
   sync_controller_delegate()->OnStateChanged(&sync_service);
 }
 
@@ -88,7 +127,6 @@
   sync_service.GetUserSettings()->SetSelectedTypes(/*sync_everything=*/false,
                                                    /*types=*/{});
 
-  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenSyncing).Times(0);
   EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenNotSyncing);
   sync_controller_delegate()->OnStateChanged(&sync_service);
 }
@@ -117,10 +155,10 @@
 
   EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenNotSyncing);
   sync_controller_delegate()->OnStateChanged(&sync_service);
+  testing::Mock::VerifyAndClearExpectations(bridge());
 
   // Check that observing the same event again will not trigger another
   // notification.
-  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenNotSyncing).Times(0);
   sync_controller_delegate()->OnStateChanged(&sync_service);
 }
 
@@ -204,6 +242,7 @@
 
 TEST_F(PasswordSyncControllerDelegateAndroidTest,
        AttachesObserverOnSyncServiceInitialized) {
+  EXPECT_CALL(*bridge(), NotifyCredentialManagerWhenSyncing);
   sync_controller_delegate()->OnSyncServiceInitialized(sync_service());
   EXPECT_TRUE(sync_service()->HasObserver(sync_controller_delegate()));
 }
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
index 674f470a..ea4f225 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
@@ -293,40 +293,74 @@
     const password_manager::PasswordForm& pending_credentials,
     bool update_password,
     bool unified_password_manager) {
-  std::u16string description;
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kExploratorySaveUpdatePasswordStrings)) {
+    return GetExploratoryStringsMessageDescription(update_password);
+  }
+
   if (unified_password_manager) {
-    if (!account_email_.empty()) {
-      description = l10n_util::GetStringFUTF16(
-          update_password
-              ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION
-              : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
-          base::UTF8ToUTF16(account_email_));
-    } else {
-      description = l10n_util::GetStringUTF16(
-          update_password
-              ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION
-              : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION);
-    }
-    return description;
+    return GetUnifiedPasswordManagerMessageDescription(update_password);
   }
 
   if (!account_email_.empty()) {
-    description = l10n_util::GetStringFUTF16(
+    return l10n_util::GetStringFUTF16(
         update_password
             ? IDS_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_GOOGLE_ACCOUNT
             : IDS_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_GOOGLE_ACCOUNT,
         base::UTF8ToUTF16(account_email_));
-  } else {
+  }
+
     // TODO(crbug.com/1188971): There is no password when federation_origin is
     // set. Instead we should display federated provider in the description.
     // GetDisplayFederation() returns federation origin for a given form.
     const std::u16string masked_password =
         std::u16string(pending_credentials.password_value.size(), L'•');
+    std::u16string description;
     description.append(pending_credentials.username_value)
         .append(u" ")
         .append(masked_password);
-  }
-  return description;
+    return description;
+}
+
+std::u16string
+SaveUpdatePasswordMessageDelegate::GetExploratoryStringsMessageDescription(
+    bool update_password) {
+    if (account_email_.empty()) {
+      return l10n_util::GetStringUTF16(
+          IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1);
+    }
+
+    int string_version =
+        password_manager::features::kSaveUpdatePromptSyncingStringVersion.Get();
+    DCHECK(string_version == 1 || string_version == 2);
+    if (string_version == 1) {
+      return l10n_util::GetStringFUTF16(
+          IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1,
+          base::UTF8ToUTF16(account_email_));
+    }
+
+    return l10n_util::GetStringFUTF16(
+        update_password
+            ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2
+            : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2,
+        base::UTF8ToUTF16(account_email_));
+}
+
+std::u16string
+SaveUpdatePasswordMessageDelegate::GetUnifiedPasswordManagerMessageDescription(
+    bool update_password) {
+    if (!account_email_.empty()) {
+      return l10n_util::GetStringFUTF16(
+          update_password
+              ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION
+              : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
+          base::UTF8ToUTF16(account_email_));
+    }
+
+    return l10n_util::GetStringUTF16(
+        update_password
+            ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION
+            : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION);
 }
 
 int SaveUpdatePasswordMessageDelegate::GetPrimaryButtonTextId(
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.h b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
index f3078eb..6b2e3b3 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.h
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
@@ -101,6 +101,10 @@
       bool update_password,
       bool unified_password_manager);
 
+  std::u16string GetUnifiedPasswordManagerMessageDescription(
+      bool update_password);
+  std::u16string GetExploratoryStringsMessageDescription(bool update_password);
+
   // Returns string id for the message primary button. Takes into account
   // whether this is save or update password scenario and whether the update
   // message will be followed by a username confirmation dialog.
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc b/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
index e70d50f8..6e14c5e 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/android/jni_android.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -33,6 +34,8 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
+using base::test::FeatureRef;
+using base::test::FeatureRefAndParams;
 using password_manager::MockPasswordFormManagerForUI;
 using password_manager::PasswordForm;
 using password_manager::PasswordFormManagerForUI;
@@ -62,6 +65,13 @@
 constexpr char kConfirmUsernameMessageDismissalReason[] =
     "PasswordManager.SaveUpdateUIDismissalReasonAndroid."
     "UpdateWithUsernameConfirmation";
+
+struct FeatureConfigTestParam {
+  bool with_unified_password_manager_android;
+  bool with_exploratory_save_update_password_strings;
+  int save_update_prompt_syncing_string_version;
+};
+
 }  // namespace
 
 class MockPasswordEditDialog : public PasswordEditDialog {
@@ -83,7 +93,7 @@
 };
 
 class SaveUpdatePasswordMessageDelegateTest
-    : public base::test::WithFeatureOverride,
+    : public testing::WithParamInterface<FeatureConfigTestParam>,
       public ChromeRenderViewHostTestHarness {
  public:
   SaveUpdatePasswordMessageDelegateTest();
@@ -92,6 +102,8 @@
   void SetUp() override;
   void TearDown() override;
 
+  void InitFeatureList();
+
   std::unique_ptr<MockPasswordFormManagerForUI> CreateFormManager(
       const GURL& password_form_url,
       const std::vector<const PasswordForm*>* best_matches);
@@ -138,6 +150,17 @@
                         PasswordFormMetricsRecorder::BubbleDismissalReason
                             expected_dismissal_reason);
 
+  std::u16string GetUnifiedPasswordManagerMessageDescription(
+      bool is_update,
+      bool is_signed_in,
+      const std::u16string& account_email);
+
+  std::u16string GetExploratoryStringsMessageDescription(
+      bool is_update,
+      bool is_signed_in,
+      const std::u16string& account_email,
+      int new_string_version);
+
   messages::MockMessageDispatcherBridge* message_dispatcher_bridge() {
     return &message_dispatcher_bridge_;
   }
@@ -157,6 +180,7 @@
   const std::vector<const PasswordForm*> kTwoFormsBestMatches = {
       &kPasswordForm1, &kPasswordForm2};
 
+  base::test::ScopedFeatureList scoped_feature_list_;
   PasswordForm pending_credentials_;
   std::unique_ptr<SaveUpdatePasswordMessageDelegate> delegate_;
   GURL password_form_url_;
@@ -171,14 +195,14 @@
 };
 
 SaveUpdatePasswordMessageDelegateTest::SaveUpdatePasswordMessageDelegateTest()
-    : base::test::WithFeatureOverride(
-          password_manager::features::kUnifiedPasswordManagerAndroid),
-      delegate_(base::WrapUnique(
+    : delegate_(base::WrapUnique(
           new SaveUpdatePasswordMessageDelegate(base::BindRepeating(
               &SaveUpdatePasswordMessageDelegateTest::CreatePasswordEditDialog,
               base::Unretained(this))))) {}
 
 void SaveUpdatePasswordMessageDelegateTest::SetUp() {
+  InitFeatureList();
+
   ChromeRenderViewHostTestHarness::SetUp();
   ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
       web_contents(), nullptr);
@@ -196,6 +220,35 @@
   ChromeRenderViewHostTestHarness::TearDown();
 }
 
+void SaveUpdatePasswordMessageDelegateTest::InitFeatureList() {
+  std::vector<FeatureRefAndParams> enabled_features;
+  std::vector<FeatureRef> disabled_features;
+
+  FeatureConfigTestParam feature_config = GetParam();
+  if (feature_config.with_unified_password_manager_android) {
+    enabled_features.push_back(
+        {password_manager::features::kUnifiedPasswordManagerAndroid, {}});
+  } else {
+    disabled_features.push_back(
+        password_manager::features::kUnifiedPasswordManagerAndroid);
+  }
+
+  if (feature_config.with_exploratory_save_update_password_strings) {
+    enabled_features.push_back(
+        {password_manager::features::kExploratorySaveUpdatePasswordStrings,
+         {{password_manager::features::kSaveUpdatePromptSyncingStringVersion
+               .name,
+           base::NumberToString(
+               feature_config.save_update_prompt_syncing_string_version)}}});
+  } else {
+    disabled_features.push_back(
+        password_manager::features::kExploratorySaveUpdatePasswordStrings);
+  }
+
+  scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
+                                                     disabled_features);
+}
+
 std::unique_ptr<MockPasswordFormManagerForUI>
 SaveUpdatePasswordMessageDelegateTest::CreateFormManager(
     const GURL& password_form_url,
@@ -366,6 +419,52 @@
   }
 }
 
+std::u16string SaveUpdatePasswordMessageDelegateTest::
+    GetUnifiedPasswordManagerMessageDescription(
+        bool is_update,
+        bool is_signed_in,
+        const std::u16string& account_email) {
+  if (is_signed_in) {
+    return l10n_util::GetStringFUTF16(
+        is_update
+            ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION
+            : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
+        account_email);
+  }
+  return l10n_util::GetStringUTF16(
+      is_update
+          ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION
+          : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION);
+}
+
+std::u16string
+SaveUpdatePasswordMessageDelegateTest::GetExploratoryStringsMessageDescription(
+    bool is_update,
+    bool is_signed_in,
+    const std::u16string& account_email,
+    int new_string_version) {
+  if (!is_signed_in) {
+    return l10n_util::GetStringUTF16(
+        IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION_V1);
+  }
+
+  switch (new_string_version) {
+    case 1:
+      return l10n_util::GetStringFUTF16(
+          IDS_PASSWORD_MANAGER_SAVE_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V1,
+          account_email);
+    case 2:
+      return l10n_util::GetStringFUTF16(
+          is_update
+              ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2
+              : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION_V2,
+          account_email);
+    default:
+      ADD_FAILURE() << "All string version param values should be handled";
+      return u"";
+  }
+}
+
 // Tests that message properties (title, description, icon, button text) are
 // set correctly for save password message.
 TEST_P(SaveUpdatePasswordMessageDelegateTest,
@@ -373,18 +472,40 @@
   SetPendingCredentials(kUsername, kPassword);
   auto form_manager =
       CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
-  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
-                 /*update_password=*/false);
+  const bool is_signed_in = false;
+  const bool is_update = false;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
+                 /*update_password=*/is_update);
 
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON),
             GetMessageWrapper()->GetPrimaryButtonText());
 
-  if (IsParamFeatureEnabled()) {
+  // Validate message description that depends on
+  // kExploratorySaveUpdatePasswordStrings feature
+  if (GetParam().with_exploratory_save_update_password_strings) {
+    // password_manager::features::kExploratorySaveUpdatePasswordStrings is
+    // enabled
+    EXPECT_EQ(GetExploratoryStringsMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16,
+                  GetParam().save_update_prompt_syncing_string_version),
+              GetMessageWrapper()->GetDescription());
+  } else if (GetParam().with_unified_password_manager_android) {
     // password_manager::features::kUnifiedPasswordManagerAndroid is enabled
-    EXPECT_EQ(
-        l10n_util::GetStringUTF16(
-            IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION),
-        GetMessageWrapper()->GetDescription());
+    EXPECT_EQ(GetUnifiedPasswordManagerMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16),
+              GetMessageWrapper()->GetDescription());
+  } else {
+    EXPECT_NE(std::u16string::npos,
+              GetMessageWrapper()->GetDescription().find(kUsername));
+    EXPECT_EQ(std::u16string::npos,
+              GetMessageWrapper()->GetDescription().find(kPassword));
+    EXPECT_EQ(std::u16string::npos,
+              GetMessageWrapper()->GetDescription().find(kAccountEmail16));
+  }
+
+  // Validate remainig message fields
+  if (GetParam().with_unified_password_manager_android) {
+    // password_manager::features::kUnifiedPasswordManagerAndroid is enabled
     EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(
                   IDR_ANDROID_PASSWORD_MANAGER_LOGO_24DP),
               GetMessageWrapper()->GetIconResourceId());
@@ -394,12 +515,6 @@
   } else {
     EXPECT_EQ(l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD),
               GetMessageWrapper()->GetTitle());
-    EXPECT_NE(std::u16string::npos,
-              GetMessageWrapper()->GetDescription().find(kUsername));
-    EXPECT_EQ(std::u16string::npos,
-              GetMessageWrapper()->GetDescription().find(kPassword));
-    EXPECT_EQ(std::u16string::npos,
-              GetMessageWrapper()->GetDescription().find(kAccountEmail16));
     EXPECT_EQ(
         ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_INFOBAR_SAVE_PASSWORD),
         GetMessageWrapper()->GetIconResourceId());
@@ -420,18 +535,26 @@
   SetPendingCredentials(kUsername, kPassword);
   auto form_manager =
       CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
-  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
-                 /*update_password=*/true);
+  const bool is_signed_in = false;
+  const bool is_update = true;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
+                 /*update_password=*/is_update);
 
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_UPDATE_PASSWORD),
             GetMessageWrapper()->GetTitle());
 
-  if (IsParamFeatureEnabled()) {
+  if (GetParam().with_exploratory_save_update_password_strings) {
+    // password_manager::features::kExploratorySaveUpdatePasswordStrings is
+    // enabled
+    EXPECT_EQ(GetExploratoryStringsMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16,
+                  GetParam().save_update_prompt_syncing_string_version),
+              GetMessageWrapper()->GetDescription());
+  } else if (GetParam().with_unified_password_manager_android) {
     // password_manager::features::kUnifiedPasswordManagerAndroid is enabled
-    EXPECT_EQ(
-        l10n_util::GetStringUTF16(
-            IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION),
-        GetMessageWrapper()->GetDescription());
+    EXPECT_EQ(GetUnifiedPasswordManagerMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16),
+              GetMessageWrapper()->GetDescription());
   }
 
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UPDATE_BUTTON),
@@ -513,16 +636,23 @@
   SetPendingCredentials(kUsername, kPassword);
   auto form_manager =
       CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
-  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
-                 /*update_password=*/false);
+  const bool is_signed_in = true;
+  const bool is_update = false;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
+                 /*update_password=*/is_update);
 
-  if (IsParamFeatureEnabled()) {
+  if (GetParam().with_exploratory_save_update_password_strings) {
+    // password_manager::features::kExploratorySaveUpdatePasswordStrings is
+    // enabled
+    EXPECT_EQ(GetExploratoryStringsMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16,
+                  GetParam().save_update_prompt_syncing_string_version),
+              GetMessageWrapper()->GetDescription());
+  } else if (GetParam().with_unified_password_manager_android) {
     // password_manager::features::kUnifiedPasswordManagerAndroid is enabled
-    EXPECT_EQ(
-        l10n_util::GetStringFUTF16(
-            IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
-            kAccountEmail16),
-        GetMessageWrapper()->GetDescription());
+    EXPECT_EQ(GetUnifiedPasswordManagerMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16),
+              GetMessageWrapper()->GetDescription());
   } else {
     EXPECT_EQ(std::u16string::npos,
               GetMessageWrapper()->GetDescription().find(kUsername));
@@ -541,16 +671,23 @@
   SetPendingCredentials(kUsername, kPassword);
   auto form_manager =
       CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
-  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
-                 /*update_password=*/true);
+  const bool is_signed_in = true;
+  const bool is_update = true;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
+                 /*update_password=*/is_update);
 
-  if (IsParamFeatureEnabled()) {
+  if (GetParam().with_exploratory_save_update_password_strings) {
+    // password_manager::features::kExploratorySaveUpdatePasswordStrings is
+    // enabled
+    EXPECT_EQ(GetExploratoryStringsMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16,
+                  GetParam().save_update_prompt_syncing_string_version),
+              GetMessageWrapper()->GetDescription());
+  } else if (GetParam().with_unified_password_manager_android) {
     // password_manager::features::kUnifiedPasswordManagerAndroid is enabled
-    EXPECT_EQ(
-        l10n_util::GetStringFUTF16(
-            IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
-            kAccountEmail16),
-        GetMessageWrapper()->GetDescription());
+    EXPECT_EQ(GetUnifiedPasswordManagerMessageDescription(
+                  is_update, is_signed_in, kAccountEmail16),
+              GetMessageWrapper()->GetDescription());
   } else {
     EXPECT_EQ(std::u16string::npos,
               GetMessageWrapper()->GetDescription().find(kUsername));
@@ -1034,4 +1171,32 @@
       1);
 }
 
-INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(SaveUpdatePasswordMessageDelegateTest);
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SaveUpdatePasswordMessageDelegateTest,
+    testing::Values(
+        // Exploratory strings are disabled, no version specified
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = false,
+            .with_exploratory_save_update_password_strings = false},
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = true,
+            .with_exploratory_save_update_password_strings = false},
+        // Exploratory strings with UPM enabled
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = true,
+            .with_exploratory_save_update_password_strings = true,
+            .save_update_prompt_syncing_string_version = 1},
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = true,
+            .with_exploratory_save_update_password_strings = true,
+            .save_update_prompt_syncing_string_version = 2},
+        // Exploratory strings with UPM disabled
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = false,
+            .with_exploratory_save_update_password_strings = true,
+            .save_update_prompt_syncing_string_version = 1},
+        FeatureConfigTestParam{
+            .with_unified_password_manager_android = false,
+            .with_exploratory_save_update_password_strings = true,
+            .save_update_prompt_syncing_string_version = 2}));
diff --git a/chrome/browser/resources/settings/site_settings/site_data.html b/chrome/browser/resources/settings/site_settings/site_data.html
index dffb641..12c2a7d 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.html
+++ b/chrome/browser/resources/settings/site_settings/site_data.html
@@ -1,4 +1,26 @@
 <style include="settings-shared">
+  .content-settings-header,
+  .radio-group {
+    padding: 0 var(--cr-section-padding);
+  }
+
+  .radio-group-sub-heading {
+    padding-bottom: 10px;
+  }
+
+  settings-collapse-radio-button {
+    --settings-collapse-toggle-min-height: var(--settings-row-min-height);
+  }
+
+  settings-collapse-radio-button.two-line {
+    --settings-collapse-toggle-min-height:
+        var(--settings-row-two-line-min-height);
+  }
+
+  settings-collapse-radio-button:not(:first-of-type) {
+    --settings-collapse-separator-line: var(--cr-separator-line);
+  }
+
   #exceptionHeader {
     padding: 0 var(--cr-section-padding);
   }
@@ -7,6 +29,39 @@
     padding-bottom: 10px;
   }
 </style>
+<!-- TODO(crbug.com/1378703): Update all strings in this page. -->
+<div class="content-settings-header secondary">$i18n{privacyPageTitle}</div>
+<div id="notificationRadioGroup" class="radio-group">
+  <h2>$i18n{privacyPageTitle}</h2>
+  <div class="secondary radio-group-sub-heading">$i18n{privacyPageTitle}</div>
+  <settings-radio-group id="defaultGroup"
+      pref="{{prefs.generated.cookie_default_content_setting}}"
+      selectable-elements="cr-radio-button, settings-collapse-radio-button">
+    <settings-collapse-radio-button id="defaultAllow" no-collapse
+        name="[[contentSettingEnum_.ALLOW]]"
+        pref="[[prefs.generated.cookie_default_content_setting]]"
+        label="$i18n{privacyPageTitle}"
+        sub-label="$i18n{privacyPageTitle}"
+        icon="settings:notifications">
+    </settings-collapse-radio-button>
+    <settings-collapse-radio-button id="defaultSessionOnly" no-collapse
+        class="two-line"
+        name="[[contentSettingEnum_.SESSION_ONLY]]"
+        pref="[[prefs.generated.cookie_default_content_setting]]"
+        label="$i18n{privacyPageTitle}"
+        sub-label="$i18n{privacyPageTitle}"
+        icon="settings:notifications">
+    </settings-collapse-radio-button>
+    <settings-collapse-radio-button id="defaultBlock" no-collapse
+        class="two-line"
+        name="[[contentSettingEnum_.BLOCK]]"
+        pref="[[prefs.generated.cookie_default_content_setting]]"
+        label="$i18n{privacyPageTitle}"
+        sub-label="$i18n{privacyPageTitle}"
+        icon="settings:notifications-off">
+    </settings-collapse-radio-button>
+  </settings-radio-group>
+</div>
 <div id="exceptionHeader">
   <!-- TODO(crbug.com/1378703): Update all strings in this page. -->
   <h2>$i18n{privacyPageTitle}</h2>
diff --git a/chrome/browser/resources/settings/site_settings/site_data.ts b/chrome/browser/resources/settings/site_settings/site_data.ts
index 4144904c..5000046 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.ts
+++ b/chrome/browser/resources/settings/site_settings/site_data.ts
@@ -7,15 +7,29 @@
  * 'settings-site-data' is the polymer element for showing the
  * settings for site data under Site Settings.
  */
+import '../controls/settings_radio_group.js';
+import '../prefs/prefs.js';
+import '../privacy_page/collapse_radio_button.js';
 import './site_list.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsRadioGroupElement} from '../controls/settings_radio_group.js';
 import {PrefsMixin} from '../prefs/prefs_mixin.js';
+import {SettingsCollapseRadioButtonElement} from '../privacy_page/collapse_radio_button.js';
 
 import {ContentSetting, ContentSettingsTypes} from './constants.js';
 import {getTemplate} from './site_data.html.js';
 
+export interface SettingsSiteDataElement {
+  $: {
+    defaultGroup: SettingsRadioGroupElement,
+    defaultAllow: SettingsCollapseRadioButtonElement,
+    defaultSessionOnly: SettingsCollapseRadioButtonElement,
+    defaultBlock: SettingsCollapseRadioButtonElement,
+  };
+}
+
 const SettingsSiteDataElementBase = PrefsMixin(PolymerElement);
 
 export class SettingsSiteDataElement extends SettingsSiteDataElementBase {
@@ -46,6 +60,7 @@
         value: ContentSettingsTypes.COOKIES,
       },
 
+      /** Expose ContentSetting enum to HTML bindings. */
       contentSettingEnum_: {
         type: Object,
         value: ContentSetting,
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html b/chrome/browser/resources/side_panel/customize_chrome/app.html
index 2cd74b9..33124ab 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.html
@@ -32,7 +32,10 @@
   }
 
   customize-chrome-appearance,
-  customize-chrome-shortcuts {
+  customize-chrome-shortcuts,
+  customize-chrome-cards,
+  customize-chrome-categories,
+  customize-chrome-themes {
     display: block;
     max-width: 320px;
     width: 100%;
diff --git a/chrome/browser/resources/side_panel/customize_chrome/cards.html b/chrome/browser/resources/side_panel/customize_chrome/cards.html
index 77d7a3c..a11b7c98 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/cards.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/cards.html
@@ -1,21 +1,10 @@
 <style>
-  .container {
-    width: 320px;
-  }
-
-  #title {
-    font-weight: 500;
-    height: 20px;
-    margin-bottom: 16px;
-  }
-
   #showToggleContainer {
     align-items: center;
     display: flex;
-    height: 20px;
-    margin-bottom: 16px;
-    margin-inline-end: 16px;
-    margin-inline-start: 16px;
+    font-size: 13px;
+    line-height: 20px;
+    margin: 0 16px 16px;
   }
 
   #showToggleTitle {
@@ -24,27 +13,29 @@
 
   #cards {
     display: flex;
+    margin: 0 16px;
   }
 
   .card {
     display: flex;
-    height: 20px;
-    margin-top: 16px;
+    justify-content: space-between;
+    margin-bottom: 16px;
   }
 
   .card-name {
-    flex-grow: 1;
+    font-size: 13px;
+    line-height: 20px;
     margin-inline-start: 24px;
   }
 
   .card-checkbox {
-    margin-inline-start: 16px;
+    margin-inline-end: 3px;
     width: 16px;
   }
 
   iron-collapse {
     --iron-collapse-transition-duration: 300ms;
-    margin-inline-start: 16px;
+    width: 100%;
   }
 
   hr {
@@ -52,37 +43,34 @@
     border: none;
     height: 1px;
     margin-bottom: 16px;
-    margin-top: 16px;
-    width: 288px;
+    width: 100%;
   }
 
   cr-toggle {
     margin-inline-start: 16px;
   }
 </style>
-<div class="container">
-  <div id="showToggleContainer">
-    <div id="showToggleTitle">$i18n{showCardsToggleTitle}</div>
-    <cr-policy-indicator indicator-type="devicePolicy"
-        hidden="[[!managedByPolicy_]]">
-    </cr-policy-indicator>
-    <cr-toggle title="$i18n{showCardsToggleTitle}" checked="[[show_]]"
-        disabled="[[managedByPolicy_]]"
-        on-change="onShowChange_">
-    </cr-toggle>
-  </div>
-  <div id="cards">
-    <iron-collapse opened="[[show_]]">
-      <hr>
-      <template is="dom-repeat" items="[[modules_]]">
-        <div class="card">
-          <div class="card-name">[[item.name]]</div>
-          <cr-checkbox class="card-checkbox" checked="{{item.enabled}}"
-              disabled="[[managedByPolicy_]]"
-              title="[[item.name]]" on-change="onCardStatusChange_">
-          </cr-checkbox>
-        </div>
-      </template>
-    </iron-collapse>
-  </div>
+<div id="showToggleContainer">
+  <div id="showToggleTitle">$i18n{showCardsToggleTitle}</div>
+  <cr-policy-indicator indicator-type="devicePolicy"
+      hidden="[[!managedByPolicy_]]">
+  </cr-policy-indicator>
+  <cr-toggle title="$i18n{showCardsToggleTitle}" checked="[[show_]]"
+      disabled="[[managedByPolicy_]]"
+      on-change="onShowChange_">
+  </cr-toggle>
+</div>
+<div id="cards">
+  <iron-collapse opened="[[show_]]">
+    <hr>
+    <template is="dom-repeat" items="[[modules_]]">
+      <div class="card">
+        <div class="card-name">[[item.name]]</div>
+        <cr-checkbox class="card-checkbox" checked="{{item.enabled}}"
+            disabled="[[managedByPolicy_]]"
+            title="[[item.name]]" on-change="onCardStatusChange_">
+        </cr-checkbox>
+      </div>
+    </template>
+  </iron-collapse>
 </div>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.html b/chrome/browser/resources/side_panel/customize_chrome/categories.html
index dfbe9ae..6db0e19 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/categories.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.html
@@ -1,13 +1,8 @@
 <style include="cr-hidden-style cr-icons">
-  #container {
-    margin: 0 16px 32px 16px;
-    max-width: 284px;
-  }
-
   #headerContainer {
     align-items: center;
     display: flex;
-    margin: 10px 0;
+    margin: 16px;
   }
 
   cr-icon-button {
@@ -27,6 +22,8 @@
   cr-grid {
     --cr-column-width: 1fr;
     --cr-grid-gap: 16px 11px;
+    display: block;
+    margin: 0 16px 32px;
   }
 
   .tile {
@@ -151,56 +148,54 @@
     width: 32px;
   }
 </style>
-<div id="container">
-  <div id="headerContainer">
-    <cr-icon-button on-click="onBackClick_" id="backButton"
-        class="icon-arrow-back" title="$i18n{backButton}">
-    </cr-icon-button>
-    <h1>$i18n{categoriesHeader}</h1>
-  </div>
-  <cr-grid columns="6">
-    <div class="tile" tabindex="0" id="classicChromeTile"
-        role="button" on-click="onClassicChromeClick_">
-      <div class="image-container">
-        <img id="miniNewTabPage" src="icons/corner_new_tab_page.svg"></img>
-      </div>
-      <div class="label">$i18n{classicChrome}</div>
-    </div>
-    <div class="tile" tabindex="0" id="uploadImageTile"
-        role="button" on-click="onUploadImageClick_">
-      <div class="image-container">
-        <div id="uploadIcon" class="cr-icon"></div>
-      </div>
-      <div class="label">$i18n{uploadImage}</div>
-    </div>
-    <div class="tile" tabindex="0" role="button">
-      <div class="image-container">
-        <div class="chrome-color"></div>
-        <div class="chrome-color"></div>
-      </div>
-      <div class="label">$i18n{chromeColors}</div>
-    </div>
-    <template is="dom-repeat" id="collectionsRepeat" items="[[collections_]]">
-      <div class="tile collection" tabindex="0" role="button"
-          on-click="onCollectionClick_">
-        <div class="image-container">
-          <img is="cr-auto-img"
-              auto-src="[[item.previewImageUrl.url]]"
-              draggable="false">
-          </img>
-        </div>
-        <div class="label">[[item.label]]</div>
-      </div>
-    </template>
-    <div class="tile" tabindex="0" role="button"
-        on-click="onChromeWebStoreClick_" id="chromeWebStoreTile">
-      <div class="image-container">
-        <img id="chromeWebStore" src="icons/chrome_web_store.svg"></img>
-      </div>
-      <div class="label">
-        <div class="cr-icon icon-external"></div>
-        $i18n{chromeWebStore}
-      </div>
-    </div>
-  </cr-grid>
+<div id="headerContainer">
+  <cr-icon-button on-click="onBackClick_" id="backButton"
+      class="icon-arrow-back" title="$i18n{backButton}">
+  </cr-icon-button>
+  <h1>$i18n{categoriesHeader}</h1>
 </div>
+<cr-grid columns="6">
+  <div class="tile" tabindex="0" id="classicChromeTile"
+      role="button" on-click="onClassicChromeClick_">
+    <div class="image-container">
+      <img id="miniNewTabPage" src="icons/corner_new_tab_page.svg"></img>
+    </div>
+    <div class="label">$i18n{classicChrome}</div>
+  </div>
+  <div class="tile" tabindex="0" id="uploadImageTile"
+      role="button" on-click="onUploadImageClick_">
+    <div class="image-container">
+      <div id="uploadIcon" class="cr-icon"></div>
+    </div>
+    <div class="label">$i18n{uploadImage}</div>
+  </div>
+  <div class="tile" tabindex="0" role="button">
+    <div class="image-container">
+      <div class="chrome-color"></div>
+      <div class="chrome-color"></div>
+    </div>
+    <div class="label">$i18n{chromeColors}</div>
+  </div>
+  <template is="dom-repeat" id="collectionsRepeat" items="[[collections_]]">
+    <div class="tile collection" tabindex="0" role="button"
+        on-click="onCollectionClick_">
+      <div class="image-container">
+        <img is="cr-auto-img"
+            auto-src="[[item.previewImageUrl.url]]"
+            draggable="false">
+        </img>
+      </div>
+      <div class="label">[[item.label]]</div>
+    </div>
+  </template>
+  <div class="tile" tabindex="0" role="button"
+      on-click="onChromeWebStoreClick_" id="chromeWebStoreTile">
+    <div class="image-container">
+      <img id="chromeWebStore" src="icons/chrome_web_store.svg"></img>
+    </div>
+    <div class="label">
+      <div class="cr-icon icon-external"></div>
+      $i18n{chromeWebStore}
+    </div>
+  </div>
+</cr-grid>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/shortcuts.html b/chrome/browser/resources/side_panel/customize_chrome/shortcuts.html
index 44b5d9f..a4ddea78 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/shortcuts.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/shortcuts.html
@@ -26,7 +26,6 @@
 
   cr-radio-group {
     --cr-radio-group-item-padding: 0px;
-    margin-top: 16px;
     width: 100%;
   }
 
@@ -48,6 +47,14 @@
   .option-description {
     color: var(--cr-secondary-text-color);
   }
+
+  hr {
+    background-color: var(--horizontal-rule-color);
+    border: none;
+    height: 1px;
+    margin-bottom: 16px;
+    width: 100%;
+  }
 </style>
 <div id="showShortcutsToggleContainer">
   <div id="showShortcutsToggleTitle">$i18n{showShortcutsToggle}</div>
@@ -56,6 +63,7 @@
 </div>
 <div id="options">
   <iron-collapse opened="[[show_]]">
+    <hr>
     <cr-radio-group id="shortcutsRadioSelection" disabled="[[!show_]]"
       selected="[[shortcutsRadioSelection_(customLinksEnabled_)]]"
       on-selected-changed="onShortcutsRadioSelectionChanged_">
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.html b/chrome/browser/resources/side_panel/customize_chrome/themes.html
index c6f0d3c..ff590c42 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/themes.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.html
@@ -1,9 +1,4 @@
 <style include="cr-hidden-style cr-icons">
-  #container {
-    max-width: 320px;
-    width: 100%;
-  }
-
   #headerContainer {
     align-items: center;
     display: flex;
@@ -79,25 +74,23 @@
     border-radius: 12px;
   }
 </style>
-<div id="container">
-  <div id="headerContainer">
-    <cr-icon-button on-click="onBackClick_" id="backButton"
-        class="icon-arrow-back" title="$i18n{backButton}">
-    </cr-icon-button>
-    <h1 id="header">[[header_]]</h1>
-  </div>
-  <cr-grid columns="3">
-    <template is="dom-repeat" id="themesRepeat" items="[[themes_]]">
-      <div class="tile theme" tabindex="0" role="button"
-          on-click="onSelectTheme_">
-        <div class="image-container">
-          <img is="cr-auto-img"
-              auto-src="[[item.previewImageUrl.url]]"
-              draggable="false">
-          </img>
-        </div>
-        <div class="label">[[item.attribution1]]</div>
-      </div>
-    </template>
-  </cr-grid>
+<div id="headerContainer">
+  <cr-icon-button on-click="onBackClick_" id="backButton"
+      class="icon-arrow-back" title="$i18n{backButton}">
+  </cr-icon-button>
+  <h1 id="header">[[header_]]</h1>
 </div>
+<cr-grid columns="3">
+  <template is="dom-repeat" id="themesRepeat" items="[[themes_]]">
+    <div class="tile theme" tabindex="0" role="button"
+        on-click="onSelectTheme_">
+      <div class="image-container">
+        <img is="cr-auto-img"
+            auto-src="[[item.previewImageUrl.url]]"
+            draggable="false">
+        </img>
+      </div>
+      <div class="label">[[item.attribution1]]</div>
+    </div>
+  </template>
+</cr-grid>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.ts b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
index afa1645..6007368 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/themes.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
@@ -8,7 +8,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 
 import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BackgroundCollection, CollectionImage, CustomizeChromePageHandlerInterface} from './customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
@@ -72,7 +72,16 @@
     this.dispatchEvent(new Event('back-click'));
   }
 
-  private onSelectTheme_() {
+  private onSelectTheme_(e: DomRepeatEvent<CollectionImage>) {
+    const {
+      attribution1,
+      attribution2,
+      attributionUrl,
+      imageUrl,
+      previewImageUrl,
+    } = e.model.item;
+    this.pageHandler_.setBackgroundImage(
+        attribution1, attribution2, attributionUrl, imageUrl, previewImageUrl);
     this.dispatchEvent(new Event('theme-select'));
   }
 }
diff --git a/chrome/browser/search/background/ntp_custom_background_service.h b/chrome/browser/search/background/ntp_custom_background_service.h
index 25c73bae..6253e98 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.h
+++ b/chrome/browser/search/background/ntp_custom_background_service.h
@@ -57,12 +57,13 @@
   virtual void ResetCustomBackgroundInfo();
 
   // Invoked when a custom background is configured on the NTP.
-  void SetCustomBackgroundInfo(const GURL& background_url,
-                               const GURL& thumbnail_url,
-                               const std::string& attribution_line_1,
-                               const std::string& attribution_line_2,
-                               const GURL& action_url,
-                               const std::string& collection_id);
+  // Virtual for testing.
+  virtual void SetCustomBackgroundInfo(const GURL& background_url,
+                                       const GURL& thumbnail_url,
+                                       const std::string& attribution_line_1,
+                                       const std::string& attribution_line_2,
+                                       const GURL& action_url,
+                                       const std::string& collection_id);
 
   // Invoked when a user selected the "Upload an image" option on the NTP.
   // Virtual for testing.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e115c4d..46aec62 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5497,7 +5497,7 @@
     }
   }
 
-  if (enable_lens_desktop_side_panel) {
+  if (enable_lens_desktop_google_branded_features) {
     sources += [ "lens/lens_side_panel_helper.h" ]
   }
 
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.cc b/chrome/browser/ui/tab_contents/core_tab_helper.cc
index 09d229e..f97addb 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.cc
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.cc
@@ -58,7 +58,7 @@
 #include "components/guest_view/browser/guest_view_manager.h"
 #endif
 
-#if BUILDFLAG(ENABLE_LENS_DESKTOP_SIDE_PANEL)
+#if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
 #include "chrome/browser/ui/lens/lens_side_panel_helper.h"
 #endif
 
@@ -502,12 +502,12 @@
         content_type.c_str());
   }
   if (use_side_panel) {
-#if BUILDFLAG(ENABLE_LENS_DESKTOP_SIDE_PANEL)
+#if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
     lens::OpenLensSidePanel(chrome::FindBrowserWithWebContents(web_contents()),
                             open_url_params);
 #else
     web_contents()->OpenURL(open_url_params);
-#endif  // BUILDFLAG(ENABLE_LENS_DESKTOP_SIDE_PANEL)
+#endif  // BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
   } else {
     web_contents()->OpenURL(open_url_params);
   }
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index e2898958..22d09142 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -217,8 +217,9 @@
 }
 
 gfx::Size SidePanel::GetMinimumSize() const {
+  const int min_side_panel_contents_width = 320;
   const int min_height = 0;
-  return gfx::Size(min_side_panel_contents_width_ + kBorderInsets.width(),
+  return gfx::Size(min_side_panel_contents_width + kBorderInsets.width(),
                    min_height);
 }
 
@@ -256,21 +257,8 @@
     starting_width_on_resize_ = -1;
   }
   const int minimum_width = GetMinimumSize().width();
-  // The side panel may be resized up to leaving the main contents at
-  // kMainBrowserContentsMinimumWidth.
-  DCHECK_EQ(browser_view_->GetMinimumSize().width(),
-            BrowserViewLayout::kMainBrowserContentsMinimumWidth);
-  const int maximum_width = width() +
-                            browser_view_->contents_container()->width() -
-                            BrowserViewLayout::kMainBrowserContentsMinimumWidth;
-  // Side panel must stay at minimum width if either:
-  // a) The proposed width is less than the minimum.
-  // b) There is not enough room for the side panel to be wider than the
-  //    minimum.
-  if (proposed_width < minimum_width || maximum_width < minimum_width) {
+  if (proposed_width < minimum_width) {
     proposed_width = minimum_width;
-  } else if (proposed_width > maximum_width) {
-    proposed_width = maximum_width;
   }
   if (width() != proposed_width) {
     SetPanelWidth(proposed_width);
diff --git a/chrome/browser/ui/views/side_panel/side_panel.h b/chrome/browser/ui/views/side_panel/side_panel.h
index 0a83a106..fc411e9b 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.h
+++ b/chrome/browser/ui/views/side_panel/side_panel.h
@@ -37,9 +37,6 @@
   HorizontalAlignment GetHorizontalAlignment();
   bool IsRightAligned();
   gfx::Size GetMinimumSize() const override;
-  void SetMinimumSidePanelContentsWidthForTesting(int width) {
-    min_side_panel_contents_width_ = width;
-  }
 
   // views::ResizeAreaDelegate:
   void OnResize(int resize_amount, bool done_resizing) override;
@@ -75,8 +72,6 @@
 
   // Observes and listens to side panel alignment changes.
   PrefChangeRegistrar pref_change_registrar_;
-
-  int min_side_panel_contents_width_ = 320;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_H_
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
index 4ed81c2b..c64df23 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
@@ -115,19 +115,6 @@
     return coordinator_->header_combobox_ != nullptr;
   }
 
-  void SetBrowserViewWidth(const int width) {
-    // If browser window is maximized then explicitly restore it, as otherwise
-    // the SetBounds call would be a no-op.
-    // TODO(crbug.com/1393153): Fix this on lacros builds and remove buildflag.
-#if !BUILDFLAG(IS_CHROMEOS_LACROS)
-    if (browser_view()->IsMaximized())
-      browser_view()->Restore();
-#endif
-
-    browser_view()->SetBounds(
-        gfx::Rect(width, browser_view()->GetBounds().height()));
-  }
-
  protected:
   raw_ptr<SidePanelCoordinator> coordinator_;
   raw_ptr<SidePanelRegistry> global_registry_;
@@ -151,22 +138,8 @@
   // Set side panel to right-aligned
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
-
-  // The side panel can only be widened when the browser main web contents width
-  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
-  // which is 500. Therefore the side panel can only be widened when the total
-  // browser view width is greater than the minimum side panel contents width +
-  // 500. The browser view maximum width is constrained by the display maximum,
-  // which is 800 on win32 and ChromeOS.
-  // Therefore, we set the browser view width to be 800 and we reduce the side
-  // panel width to 100 so that it can be widened and shrunk.
-  SetBrowserViewWidth(800);
-  browser_view()
-      ->unified_side_panel()
-      ->SetMinimumSidePanelContentsWidthForTesting(100);
-
   coordinator_->Toggle();
-  const int starting_width = 200;
+  const int starting_width = 500;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -191,21 +164,8 @@
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthMaxMin) {
-  // The side panel can only be widened when the browser main web contents width
-  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
-  // which is 500. Therefore the side panel can only be widened when the total
-  // browser view width is greater than the minimum side panel contents width +
-  // 500. The browser view maximum width is constrained by the display maximum,
-  // which is 800 on win32 and ChromeOS.
-  // Therefore, we set the browser view width to be 800 and we reduce the side
-  // panel width to 100 so that it can be widened and shrunk.
-  SetBrowserViewWidth(800);
-  browser_view()
-      ->unified_side_panel()
-      ->SetMinimumSidePanelContentsWidthForTesting(100);
-
   coordinator_->Toggle();
-  const int starting_width = 200;
+  const int starting_width = 500;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -220,32 +180,22 @@
 
   browser_view()->unified_side_panel()->OnResize(-large_increment, true);
   views::test::RunScheduledLayout(browser_view());
-  EXPECT_GT(browser_view()->unified_side_panel()->width(), starting_width);
-  EXPECT_EQ(browser_view()->contents_container()->width(),
-            BrowserViewLayout::kMainBrowserContentsMinimumWidth);
+  BrowserViewLayout* layout_manager =
+      static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
+  const int min_web_contents_width =
+      layout_manager->GetMinWebContentsWidthForTesting();
+  EXPECT_EQ(browser_view()->contents_web_view()->width(),
+            min_web_contents_width);
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthRTL) {
-  // The side panel can only be widened when the browser main web contents width
-  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
-  // which is 500. Therefore the side panel can only be widened when the total
-  // browser view width is greater than the minimum side panel contents width +
-  // 500. The browser view maximum width is constrained by the display maximum,
-  // which is 800 on win32 and ChromeOS.
-  // Therefore, we set the browser view width to be 800 and we reduce the side
-  // panel width to 100 so that it can be widened and shrunk.
-  SetBrowserViewWidth(800);
-  browser_view()
-      ->unified_side_panel()
-      ->SetMinimumSidePanelContentsWidthForTesting(100);
-
   // Set side panel to right-aligned
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
   // Set UI direction to LTR
   base::i18n::SetRTLForTesting(false);
   coordinator_->Toggle();
-  const int starting_width = 200;
+  const int starting_width = 500;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
@@ -269,79 +219,38 @@
 }
 
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthWindowResize) {
-  // The side panel can only be widened when the browser main web contents width
-  // is greater than |BrowserViewLayout::kMainBrowserContentsMinimumWidth|,
-  // which is 500. Therefore the side panel can only be widened when the total
-  // browser view width is greater than the minimum side panel contents width +
-  // 500. The browser view maximum width is constrained by the display maximum,
-  // which is 800 on win32 and ChromeOS.
-  // Therefore, we set the browser view width to be 800 and we reduce the side
-  // panel width to 100 so that it can be widened and shrunk.
-  SetBrowserViewWidth(800);
-  browser_view()
-      ->unified_side_panel()
-      ->SetMinimumSidePanelContentsWidthForTesting(100);
-
   coordinator_->Toggle();
-  const int starting_width = 200;
+  const int starting_width = 500;
   browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
   views::test::RunScheduledLayout(browser_view());
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
 
   // Shrink browser window enough that side panel should also shrink in
   // observance of web contents minimum width.
-  const int original_width = browser_view()->GetBounds().width();
-  SetBrowserViewWidth(starting_width);
-  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
-            browser_view()->unified_side_panel()->GetMinimumSize().width());
-  BrowserViewLayout* const layout_manager =
+  gfx::Rect original_bounds(browser_view()->GetBounds());
+  gfx::Size new_size(starting_width, starting_width);
+  gfx::Rect new_bounds(original_bounds);
+  new_bounds.set_size(new_size);
+  // Explicitly restore the browser window on ChromeOS, as it would otherwise
+  // be maximized and the SetBounds call would be a no-op.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  browser_view()->Restore();
+#endif
+  browser_view()->SetBounds(new_bounds);
+  EXPECT_LT(browser_view()->unified_side_panel()->width(), starting_width);
+  BrowserViewLayout* layout_manager =
       static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
   const int min_web_contents_width =
       layout_manager->GetMinWebContentsWidthForTesting();
-  EXPECT_EQ(browser_view()->contents_container()->width(),
+  EXPECT_EQ(browser_view()->contents_web_view()->width(),
             min_web_contents_width);
 
   // Return browser window to original size, side panel should also return to
   // size prior to window resize.
-  SetBrowserViewWidth(original_width);
+  browser_view()->SetBounds(original_bounds);
   EXPECT_EQ(browser_view()->unified_side_panel()->width(), starting_width);
 }
 
-TEST_F(SidePanelCoordinatorTest, ChangeSidePanelWidthSmallWindow) {
-  SetBrowserViewWidth(500);
-  browser_view()
-      ->unified_side_panel()
-      ->SetMinimumSidePanelContentsWidthForTesting(100);
-  coordinator_->Toggle();
-  const int starting_width = 200;
-  const int min_side_panel_width =
-      browser_view()->unified_side_panel()->GetMinimumSize().width();
-  browser_view()->unified_side_panel()->SetPanelWidth(starting_width);
-  views::test::RunScheduledLayout(browser_view());
-  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
-            min_side_panel_width);
-
-  // Attempt to resize the side panel, side panel should not be able to resized.
-  const int increment = 50;
-  browser_view()->unified_side_panel()->OnResize(increment, true);
-  views::test::RunScheduledLayout(browser_view());
-  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
-            min_side_panel_width);
-  BrowserViewLayout* const layout_manager =
-      static_cast<BrowserViewLayout*>(browser_view()->GetLayoutManager());
-  const int min_web_contents_width =
-      layout_manager->GetMinWebContentsWidthForTesting();
-  EXPECT_EQ(browser_view()->contents_container()->width(),
-            min_web_contents_width);
-
-  browser_view()->unified_side_panel()->OnResize(-increment, true);
-  views::test::RunScheduledLayout(browser_view());
-  EXPECT_EQ(browser_view()->unified_side_panel()->width(),
-            min_side_panel_width);
-  EXPECT_EQ(browser_view()->contents_container()->width(),
-            min_web_contents_width);
-}
-
 TEST_F(SidePanelCoordinatorTest, ChangeSidePanelAlignment) {
   browser_view()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kSidePanelHorizontalAlignment, true);
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index d967f845..e406f279 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -287,7 +287,7 @@
   const bool is_oobe_flow = display_type == OobeUI::kOobeDisplay;
   source->AddBoolean("isOsInstallAllowed", switches::IsOsInstallAllowed());
   source->AddBoolean("isOobeFlow", is_oobe_flow);
-  source->AddBoolean("isMaterialNext", features::IsOobeMaterialNextEnabled());
+  source->AddBoolean("isOobeJelly", features::IsOobeJellyEnabled());
   source->AddBoolean("isChoobeEnabled", features::IsOobeChoobeEnabled());
 
   // Configure shared resources
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index d18898e6..29e011b 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -126,6 +126,11 @@
   // Choose custom background from local file system.
   ChooseLocalCustomBackground() => (bool success);
 
+  // Sets the background image and notifies all NTPs of the change.
+  SetBackgroundImage(string attribution_1, string attribution_2,
+      url.mojom.Url attribution_url, url.mojom.Url image_url,
+      url.mojom.Url thumbnail_url);
+
   // Open Chrome Web Store in a new tab.
   OpenChromeWebStore();
 
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index b33a408..66e4935 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -247,6 +247,19 @@
       nullptr);
 }
 
+void CustomizeChromePageHandler::SetBackgroundImage(
+    const std::string& attribution_1,
+    const std::string& attribution_2,
+    const GURL& attribution_url,
+    const GURL& image_url,
+    const GURL& thumbnail_url) {
+  // Populating the |collection_id| turns on refresh daily which overrides the
+  // the selected image.
+  ntp_custom_background_service_->SetCustomBackgroundInfo(
+      image_url, thumbnail_url, attribution_1, attribution_2, attribution_url,
+      /* collection_id= */ "");
+}
+
 void CustomizeChromePageHandler::OpenChromeWebStore() {
   NavigateParams navigate_params(
       profile_, GURL("https://chrome.google.com/webstore?category=theme"),
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
index 2c01b64e..483697e 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
@@ -64,6 +64,11 @@
   void SetClassicChromeDefaultTheme() override;
   void ChooseLocalCustomBackground(
       ChooseLocalCustomBackgroundCallback callback) override;
+  void SetBackgroundImage(const std::string& attribution_1,
+                          const std::string& attribution_2,
+                          const GURL& attribution_url,
+                          const GURL& image_url,
+                          const GURL& thumbnail_url) override;
   void OpenChromeWebStore() override;
   void SetModulesVisible(bool visible) override;
   void SetModuleDisabled(const std::string& module_id, bool disabled) override;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index 755b501d..95b960e 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -157,6 +157,13 @@
   MOCK_METHOD0(ResetCustomBackgroundInfo, void());
   MOCK_METHOD1(SelectLocalBackgroundImage, void(const base::FilePath&));
   MOCK_METHOD1(AddObserver, void(NtpCustomBackgroundServiceObserver*));
+  MOCK_METHOD6(SetCustomBackgroundInfo,
+               void(const GURL&,
+                    const GURL&,
+                    const std::string&,
+                    const std::string&,
+                    const GURL&,
+                    const std::string&));
 };
 
 class MockNtpBackgroundService : public NtpBackgroundService {
@@ -590,6 +597,14 @@
             browser().tab_strip_model()->GetWebContentsAt(0)->GetURL());
 }
 
+TEST_F(CustomizeChromePageHandlerTest, SetBackgroundImage) {
+  EXPECT_CALL(mock_ntp_custom_background_service_, SetCustomBackgroundInfo)
+      .Times(1);
+  handler().SetBackgroundImage(
+      "attribution1", "attribution2", GURL("https://attribution.com"),
+      GURL("https://image.jpg"), GURL("https://thumbnail.jpg"));
+}
+
 class CustomizeChromePageHandlerWithModulesTest
     : public CustomizeChromePageHandlerTest {
  public:
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 8772e63..3b8d436 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -110,6 +110,8 @@
     "os_integration/os_integration_sub_manager.h",
     "os_integration/protocol_handling_sub_manager.cc",
     "os_integration/protocol_handling_sub_manager.h",
+    "os_integration/run_on_os_login_sub_manager.cc",
+    "os_integration/run_on_os_login_sub_manager.h",
     "os_integration/shortcut_handling_sub_manager.cc",
     "os_integration/shortcut_handling_sub_manager.h",
     "os_integration/url_handler_manager.cc",
@@ -585,6 +587,7 @@
     "isolation_prefs_utils_unittest.cc",
     "os_integration/os_integration_manager_unittest.cc",
     "os_integration/protocol_handling_sub_manager_unittest.cc",
+    "os_integration/run_on_os_login_sub_manager_unittest.cc",
     "os_integration/web_app_file_handler_manager_unittest.cc",
     "os_integration/web_app_protocol_handler_manager_unittest.cc",
     "os_integration/web_app_shortcut_unittest.cc",
diff --git a/chrome/browser/web_applications/commands/run_on_os_login_command.cc b/chrome/browser/web_applications/commands/run_on_os_login_command.cc
index c192884..cc556934 100644
--- a/chrome/browser/web_applications/commands/run_on_os_login_command.cc
+++ b/chrome/browser/web_applications/commands/run_on_os_login_command.cc
@@ -145,8 +145,7 @@
   if (login_mode_.value() == current_mode.value) {
     RecordCompletionState(
         RunOnOsLoginCommandCompletionState::kRunOnOsLoginModeAlreadyMatched);
-    SignalCompletionAndSelfDestruct(CommandResult::kSuccess,
-                                    std::move(callback_));
+    OnOsHooksSet(OsHooksErrors());
     return;
   }
 
@@ -188,8 +187,7 @@
   if (os_integration_state && login_mode_.value() == *os_integration_state) {
     RecordCompletionState(
         RunOnOsLoginCommandCompletionState::kRunOnOsLoginModeAlreadyMatched);
-    SignalCompletionAndSelfDestruct(CommandResult::kSuccess,
-                                    std::move(callback_));
+    std::move(os_hooks_callback).Run(OsHooksErrors());
     return;
   }
 
@@ -197,13 +195,13 @@
   // once OS integration
   // sub managers have been implemented.
   if (login_mode_.value() == RunOnOsLoginMode::kNotRun) {
-    web_app::OsHooksOptions os_hooks;
-    os_hooks[web_app::OsHookType::kRunOnOsLogin] = true;
+    OsHooksOptions os_hooks;
+    os_hooks[OsHookType::kRunOnOsLogin] = true;
     lock_->os_integration_manager().UninstallOsHooks(
         app_id_, os_hooks, std::move(os_hooks_callback));
   } else {
-    web_app::InstallOsHooksOptions install_options;
-    install_options.os_hooks[web_app::OsHookType::kRunOnOsLogin] = true;
+    InstallOsHooksOptions install_options;
+    install_options.os_hooks[OsHookType::kRunOnOsLogin] = true;
     install_options.reason = SHORTCUT_CREATION_AUTOMATED;
     lock_->os_integration_manager().InstallOsHooks(
         app_id_, std::move(os_hooks_callback), nullptr,
@@ -211,19 +209,24 @@
   }
 }
 
-void RunOnOsLoginCommand::OnOsHooksSet(web_app::OsHooksErrors errors) {
-  if (errors[web_app::OsHookType::kRunOnOsLogin] == true) {
+void RunOnOsLoginCommand::OnOsHooksSet(OsHooksErrors errors) {
+  if (errors[OsHookType::kRunOnOsLogin] == true) {
     Abort(RunOnOsLoginCommandCompletionState::kOSHooksNotProperlySet);
     return;
   }
-  RecordCompletionState(
-      RunOnOsLoginCommandCompletionState::kSuccessfulCompletion);
+
+  if (!completion_state_set_) {
+    RecordCompletionState(
+        RunOnOsLoginCommandCompletionState::kSuccessfulCompletion);
+  }
+
   SignalCompletionAndSelfDestruct(CommandResult::kSuccess,
                                   std::move(callback_));
 }
 
 void RunOnOsLoginCommand::RecordCompletionState(
     RunOnOsLoginCommandCompletionState completed_state) {
+  completion_state_set_ = true;
   base::UmaHistogramEnumeration("WebApp.RunOnOsLogin.CommandCompletionState",
                                 completed_state);
 }
diff --git a/chrome/browser/web_applications/commands/run_on_os_login_command.h b/chrome/browser/web_applications/commands/run_on_os_login_command.h
index 1b27d498..9ce0ef7 100644
--- a/chrome/browser/web_applications/commands/run_on_os_login_command.h
+++ b/chrome/browser/web_applications/commands/run_on_os_login_command.h
@@ -87,6 +87,7 @@
   RunOnOsLoginAction set_or_sync_mode_;
   std::string stop_reason_;
   base::OnceClosure callback_ = base::DoNothing();
+  bool completion_state_set_ = false;
 
   base::WeakPtrFactory<RunOnOsLoginCommand> weak_factory_{this};
 };
diff --git a/chrome/browser/web_applications/os_integration/os_integration_manager.cc b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
index 751fcd82..14cd9f34 100644
--- a/chrome/browser/web_applications/os_integration/os_integration_manager.cc
+++ b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/protocol_handling_sub_manager.h"
+#include "chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/shortcut_handling_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
 #include "chrome/browser/web_applications/os_integration/web_app_uninstallation_via_os_settings_registration.h"
@@ -189,8 +190,12 @@
       std::make_unique<ShortcutHandlingSubManager>(*icon_manager, *registrar);
   auto protocol_handling_sub_manager =
       std::make_unique<ProtocolHandlingSubManager>(*registrar);
+
+  auto run_on_os_login_sub_manager =
+      std::make_unique<RunOnOsLoginSubManager>(*registrar);
   sub_managers_.push_back(std::move(shortcut_handling_sub_manager));
   sub_managers_.push_back(std::move(protocol_handling_sub_manager));
+  sub_managers_.push_back(std::move(run_on_os_login_sub_manager));
 }
 
 void OsIntegrationManager::Start() {
diff --git a/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.cc b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.cc
new file mode 100644
index 0000000..701e4740
--- /dev/null
+++ b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.cc
@@ -0,0 +1,72 @@
+
+// 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.
+
+#include "chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h"
+
+#include <utility>
+
+#include "base/functional/callback.h"
+#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace web_app {
+
+namespace {
+
+proto::RunOnOsLoginMode ConvertWebAppRunOnOsLoginModeToProto(
+    RunOnOsLoginMode mode) {
+  switch (mode) {
+    case RunOnOsLoginMode::kMinimized:
+      return proto::RunOnOsLoginMode::MINIMIZED;
+    case RunOnOsLoginMode::kWindowed:
+      return proto::RunOnOsLoginMode::WINDOWED;
+    case RunOnOsLoginMode::kNotRun:
+      return proto::RunOnOsLoginMode::NOT_RUN;
+  }
+}
+
+}  // namespace
+
+RunOnOsLoginSubManager::RunOnOsLoginSubManager(WebAppRegistrar& registrar)
+    : registrar_(registrar) {}
+
+RunOnOsLoginSubManager::~RunOnOsLoginSubManager() = default;
+
+void RunOnOsLoginSubManager::Configure(
+    const AppId& app_id,
+    proto::WebAppOsIntegrationState& desired_state,
+    base::OnceClosure configure_done) {
+  if (!registrar_->GetAppById(app_id)) {
+    std::move(configure_done).Run();
+    return;
+  }
+
+  DCHECK(!desired_state.has_run_on_os_login());
+
+  proto::RunOnOsLogin* run_on_os_login =
+      desired_state.mutable_run_on_os_login();
+
+  const auto login_mode = registrar_->GetAppRunOnOsLoginMode(app_id);
+  run_on_os_login->set_run_on_os_login_mode(
+      ConvertWebAppRunOnOsLoginModeToProto(login_mode.value));
+
+  std::move(configure_done).Run();
+}
+
+void RunOnOsLoginSubManager::Start() {}
+
+void RunOnOsLoginSubManager::Shutdown() {}
+
+void RunOnOsLoginSubManager::Execute(
+    const AppId& app_id,
+    const proto::WebAppOsIntegrationState& desired_state,
+    const absl::optional<proto::WebAppOsIntegrationState>& current_state,
+    base::OnceClosure callback) {
+  NOTREACHED() << "Not yet implemented";
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h
new file mode 100644
index 0000000..e1fb598
--- /dev/null
+++ b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager.h
@@ -0,0 +1,41 @@
+// 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 CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_RUN_ON_OS_LOGIN_SUB_MANAGER_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_RUN_ON_OS_LOGIN_SUB_MANAGER_H_
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ref.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
+#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace web_app {
+
+class WebAppRegistrar;
+
+class RunOnOsLoginSubManager : public OsIntegrationSubManager {
+ public:
+  explicit RunOnOsLoginSubManager(WebAppRegistrar& registrar);
+  ~RunOnOsLoginSubManager() override;
+  void Start() override;
+  void Shutdown() override;
+
+  void Configure(const AppId& app_id,
+                 proto::WebAppOsIntegrationState& desired_state,
+                 base::OnceClosure configure_done) override;
+  void Execute(
+      const AppId& app_id,
+      const proto::WebAppOsIntegrationState& desired_state,
+      const absl::optional<proto::WebAppOsIntegrationState>& current_state,
+      base::OnceClosure callback) override;
+
+ private:
+  const raw_ref<WebAppRegistrar> registrar_;
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_RUN_ON_OS_LOGIN_SUB_MANAGER_H_
diff --git a/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager_unittest.cc b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager_unittest.cc
new file mode 100644
index 0000000..dd2f22f
--- /dev/null
+++ b/chrome/browser/web_applications/os_integration/run_on_os_login_sub_manager_unittest.cc
@@ -0,0 +1,287 @@
+// 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.
+
+#include <memory>
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_piece_forward.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
+#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
+#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
+#include "chrome/browser/web_applications/test/web_app_test_utils.h"
+#include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+#include "chrome/browser/web_applications/web_app_install_params.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "components/webapps/browser/install_result_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace web_app {
+
+namespace {
+
+class RunOnOsLoginSubManagerTest
+    : public WebAppTest,
+      public ::testing::WithParamInterface<OsIntegrationSubManagersState> {
+ public:
+  const GURL kWebAppUrl = GURL("https://example.com/path/index.html");
+
+  RunOnOsLoginSubManagerTest() = default;
+  ~RunOnOsLoginSubManagerTest() override = default;
+
+  void SetUp() override {
+    WebAppTest::SetUp();
+    {
+      base::ScopedAllowBlockingForTesting allow_blocking;
+      shortcut_override_ =
+          ShortcutOverrideForTesting::OverrideForTesting(base::GetHomeDir());
+    }
+    if (EnableOsIntegrationSubManager()) {
+      scoped_feature_list_.InitAndEnableFeatureWithParameters(
+          features::kOsIntegrationSubManagers, {{"stage", "write_config"}});
+    } else {
+      scoped_feature_list_.InitWithFeatures(
+          /*enabled_features=*/{},
+          /*disabled_features=*/{features::kOsIntegrationSubManagers});
+    }
+
+    provider_ = FakeWebAppProvider::Get(profile());
+
+    auto file_handler_manager =
+        std::make_unique<WebAppFileHandlerManager>(profile());
+    auto protocol_handler_manager =
+        std::make_unique<WebAppProtocolHandlerManager>(profile());
+    auto shortcut_manager = std::make_unique<WebAppShortcutManager>(
+        profile(), /*icon_manager=*/nullptr, file_handler_manager.get(),
+        protocol_handler_manager.get());
+    auto os_integration_manager = std::make_unique<OsIntegrationManager>(
+        profile(), std::move(shortcut_manager), std::move(file_handler_manager),
+        std::move(protocol_handler_manager), /*url_handler_manager=*/nullptr);
+
+    provider_->SetOsIntegrationManager(std::move(os_integration_manager));
+    test::AwaitStartWebAppProviderAndSubsystems(profile());
+  }
+
+  void TearDown() override {
+    test::UninstallAllWebApps(profile());
+    {
+      // Blocking required due to file operations in the shortcut override
+      // destructor.
+      base::ScopedAllowBlockingForTesting allow_blocking;
+      shortcut_override_.reset();
+    }
+    WebAppTest::TearDown();
+  }
+
+  bool EnableOsIntegrationSubManager() {
+    return GetParam() == OsIntegrationSubManagersState::kEnabled;
+  }
+
+  AppId InstallWebApp() {
+    std::unique_ptr<WebAppInstallInfo> info =
+        std::make_unique<WebAppInstallInfo>();
+    info->start_url = kWebAppUrl;
+    info->title = u"Test App";
+    info->user_display_mode = web_app::UserDisplayMode::kStandalone;
+    base::test::TestFuture<const AppId&, webapps::InstallResultCode> result;
+    // InstallFromInfoWithParams is used instead of InstallFromInfo, because
+    // InstallFromInfo doesn't register OS integration.
+    provider().scheduler().InstallFromInfoWithParams(
+        std::move(info), /*overwrite_existing_manifest_fields=*/true,
+        webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
+        result.GetCallback(), WebAppInstallParams());
+    bool success = result.Wait();
+    EXPECT_TRUE(success);
+    if (!success) {
+      return AppId();
+    }
+    EXPECT_EQ(result.Get<webapps::InstallResultCode>(),
+              webapps::InstallResultCode::kSuccessNewInstall);
+    return result.Get<AppId>();
+  }
+
+  void SetWebAppSettingsListPref(const base::StringPiece pref) {
+    auto result = base::JSONReader::ReadAndReturnValueWithError(
+        pref, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+    ASSERT_TRUE(result.has_value()) << result.error().message;
+    ASSERT_TRUE(result->is_list());
+    profile()->GetPrefs()->Set(prefs::kWebAppSettings, std::move(*result));
+  }
+
+ protected:
+  WebAppProvider& provider() { return *provider_; }
+  WebAppRegistrar& registrar() { return provider().registrar_unsafe(); }
+
+ private:
+  raw_ptr<FakeWebAppProvider> provider_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<ShortcutOverrideForTesting::BlockingRegistration>
+      shortcut_override_;
+};
+
+TEST_P(RunOnOsLoginSubManagerTest, VerifyRunOnOsLoginSetProperlyOnInstall) {
+  const AppId& app_id = InstallWebApp();
+
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+  if (EnableOsIntegrationSubManager()) {
+    // on installation, both values are set to NOT_RUN.
+    ASSERT_TRUE(state.has_value());
+    const proto::WebAppOsIntegrationState& os_integration_state = state.value();
+    ASSERT_TRUE(os_integration_state.has_run_on_os_login());
+    const proto::RunOnOsLogin& run_on_os_login =
+        os_integration_state.run_on_os_login();
+    ASSERT_THAT(run_on_os_login.run_on_os_login_mode(),
+                testing::Eq(proto::RunOnOsLoginMode::NOT_RUN));
+  } else {
+    ASSERT_FALSE(state.has_value());
+  }
+}
+
+TEST_P(RunOnOsLoginSubManagerTest, VerifyRunOnOsLoginSetFromCommand) {
+  const AppId& app_id = InstallWebApp();
+
+  base::test::TestFuture<void> future;
+  provider().scheduler().SetRunOnOsLoginMode(
+      app_id, RunOnOsLoginMode::kWindowed, future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+
+  if (EnableOsIntegrationSubManager()) {
+    ASSERT_TRUE(state.has_value());
+    const proto::WebAppOsIntegrationState& os_integration_state = state.value();
+    ASSERT_TRUE(os_integration_state.has_run_on_os_login());
+    const proto::RunOnOsLogin& run_on_os_login =
+        os_integration_state.run_on_os_login();
+    ASSERT_THAT(run_on_os_login.run_on_os_login_mode(),
+                testing::Eq(proto::RunOnOsLoginMode::WINDOWED));
+  } else {
+    ASSERT_FALSE(state.has_value());
+  }
+}
+
+TEST_P(RunOnOsLoginSubManagerTest, VerifyPolicySettingBlocked) {
+  const AppId& app_id = InstallWebApp();
+
+  const char kWebAppSettingPolicyBlockedConfig[] = R"([{
+    "manifest_id" : "https://example.com/path/index.html",
+    "run_on_os_login": "blocked"
+  }])";
+
+  {
+    base::test::TestFuture<void> policy_future;
+    provider()
+        .policy_manager()
+        .SetRefreshPolicySettingsCompletedCallbackForTesting(
+            policy_future.GetCallback());
+    SetWebAppSettingsListPref(kWebAppSettingPolicyBlockedConfig);
+    EXPECT_TRUE(policy_future.Wait());
+  }
+
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+  if (EnableOsIntegrationSubManager()) {
+    ASSERT_TRUE(state.has_value());
+    const proto::WebAppOsIntegrationState& os_integration_state = state.value();
+    ASSERT_TRUE(os_integration_state.has_run_on_os_login());
+    const proto::RunOnOsLogin& run_on_os_login =
+        os_integration_state.run_on_os_login();
+    ASSERT_THAT(run_on_os_login.run_on_os_login_mode(),
+                testing::Eq(proto::RunOnOsLoginMode::NOT_RUN));
+  } else {
+    ASSERT_FALSE(state.has_value());
+  }
+}
+
+TEST_P(RunOnOsLoginSubManagerTest, VerifyPolicySettingWindowedMode) {
+  const AppId& app_id = InstallWebApp();
+
+  const char kWebAppSettingPolicyWindowedConfig[] = R"([{
+    "manifest_id" : "https://example.com/path/index.html",
+    "run_on_os_login": "run_windowed"
+  }])";
+
+  {
+    base::test::TestFuture<void> policy_future;
+    provider()
+        .policy_manager()
+        .SetRefreshPolicySettingsCompletedCallbackForTesting(
+            policy_future.GetCallback());
+    SetWebAppSettingsListPref(kWebAppSettingPolicyWindowedConfig);
+    EXPECT_TRUE(policy_future.Wait());
+  }
+
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+  if (EnableOsIntegrationSubManager()) {
+    ASSERT_TRUE(state.has_value());
+    const proto::WebAppOsIntegrationState& os_integration_state = state.value();
+    ASSERT_TRUE(os_integration_state.has_run_on_os_login());
+    const proto::RunOnOsLogin& run_on_os_login =
+        os_integration_state.run_on_os_login();
+    ASSERT_THAT(run_on_os_login.run_on_os_login_mode(),
+                testing::Eq(proto::RunOnOsLoginMode::WINDOWED));
+  } else {
+    ASSERT_FALSE(state.has_value());
+  }
+}
+
+TEST_P(RunOnOsLoginSubManagerTest, VerifyPolicySettingAllowedMode) {
+  const AppId& app_id = InstallWebApp();
+
+  const char kWebAppSettingPolicyAllowedConfig[] = R"([{
+    "manifest_id" : "https://example.com/path/index.html",
+    "run_on_os_login": "allowed"
+  }])";
+
+  {
+    base::test::TestFuture<void> policy_future;
+    provider()
+        .policy_manager()
+        .SetRefreshPolicySettingsCompletedCallbackForTesting(
+            policy_future.GetCallback());
+    SetWebAppSettingsListPref(kWebAppSettingPolicyAllowedConfig);
+    EXPECT_TRUE(policy_future.Wait());
+  }
+
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+  if (EnableOsIntegrationSubManager()) {
+    ASSERT_TRUE(state.has_value());
+    const proto::WebAppOsIntegrationState& os_integration_state = state.value();
+    ASSERT_TRUE(os_integration_state.has_run_on_os_login());
+    const proto::RunOnOsLogin& run_on_os_login =
+        os_integration_state.run_on_os_login();
+    ASSERT_THAT(run_on_os_login.run_on_os_login_mode(),
+                testing::Eq(proto::RunOnOsLoginMode::NOT_RUN));
+  } else {
+    ASSERT_FALSE(state.has_value());
+  }
+}
+
+TEST_P(RunOnOsLoginSubManagerTest, StatesEmptyOnUninstall) {
+  const AppId& app_id = InstallWebApp();
+  test::UninstallAllWebApps(profile());
+  auto state = registrar().GetAppCurrentOsIntegrationState(app_id);
+  ASSERT_FALSE(state.has_value());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    RunOnOsLoginSubManagerTest,
+    ::testing::Values(OsIntegrationSubManagersState::kEnabled,
+                      OsIntegrationSubManagersState::kDisabled),
+    test::GetOsIntegrationSubManagersTestName);
+
+}  // namespace
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 4ac2bff..8b9ab4c 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -239,6 +239,10 @@
 void WebAppPolicyManager::OnSyncCommandsComplete(
     std::vector<std::string> app_ids) {
   app_registrar_->NotifyWebAppSettingsPolicyChanged();
+
+  if (refresh_policy_settings_completed_) {
+    std::move(refresh_policy_settings_completed_).Run();
+  }
 }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -422,9 +426,6 @@
   }
 
   ApplyPolicySettings();
-
-  if (refresh_policy_settings_completed_)
-    std::move(refresh_policy_settings_completed_).Run();
 }
 
 void WebAppPolicyManager::ApplyPolicySettings() {
diff --git a/chrome/browser/web_applications/proto/web_app_os_integration_state.proto b/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
index b37db0a..fee5aa3 100644
--- a/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
+++ b/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
@@ -28,12 +28,27 @@
   repeated Protocol protocols = 1;
 }
 
+// Run on OS Login modes for web app.
+// See chrome/browser/web_applications/web_app_constants.h
+// for web_app::RunOnOsLoginMode definition, which this enum should be in sync
+// with.
+enum RunOnOsLoginMode {
+  RUN_ON_OS_LOGIN_MODE_UNSPECIFIED = 0;
+  NOT_RUN = 1;
+  WINDOWED = 2;
+  MINIMIZED = 3;
+}
+
+message RunOnOsLogin {
+  optional RunOnOsLoginMode run_on_os_login_mode = 1;
+}
+
 // Represents all the common OS Integration states to be stored in the web_app
 // DB that matches the values in the OS.
 message WebAppOsIntegrationState {
   reserved 2;
   optional ShortcutDescription shortcut = 1;
   optional ProtocolsHandled protocols_handled = 3;
-
+  optional RunOnOsLogin run_on_os_login = 4;
   // Add data states for other OS integration hooks here.
 }
\ No newline at end of file
diff --git a/chrome/browser/web_applications/web_app.h b/chrome/browser/web_applications/web_app.h
index c7a4f8e..b25353f 100644
--- a/chrome/browser/web_applications/web_app.h
+++ b/chrome/browser/web_applications/web_app.h
@@ -485,6 +485,8 @@
   // Tracks if the app run on os login mode has been registered with the OS.
   // This might go out of sync with actual OS integration status, as Chrome does
   // not actively monitor OS registries.
+  // TODO(crbug.com/1401125): Remove after all OS Integration sub managers have
+  // been implemented and Synchronize() is running fine.
   absl::optional<RunOnOsLoginMode> run_on_os_login_os_integration_state_;
   SyncFallbackData sync_fallback_data_;
   blink::mojom::CaptureLinks capture_links_ =
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 513c3a6..0645941 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1671645419-579bdd82a8e15162e690e78e3e3a84ba10ca5d53.profdata
+chrome-linux-main-1671709962-bf0d4c87ce7f67d80bfc0f8c21dff4fb7d49b87f.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 177b66e..024515f 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1671709962-db445d8b6e7b0fcb69b7fe803bc275df9c5d6ef5.profdata
+chrome-mac-main-1671731872-e64a2792eafde6af466e698a236a817d2fcb14c8.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 69f4608..d1e38d61 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1671685452-ccde62eeadefd5bba36a6878de84f064cc8e801f.profdata
+chrome-win32-main-1671721064-189024d4b81230d96d5ab1605f78c6009bc20cd6.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 291f4a9..106a9d9 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1671677985-53a355963172030f44c84218d4e693cb8b05f46a.profdata
+chrome-win64-main-1671709962-86e5c2f479991b3710dd2b796a8a5ecc2dbf3c7c.profdata
diff --git a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
index e556386d..35b06c4 100644
--- a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
@@ -195,4 +195,37 @@
             user_scripts[6]->match_origin_as_fallback());
 }
 
+TEST_F(ContentScriptsManifestTest, ExecutionWorld) {
+  scoped_refptr<const Extension> extension =
+      LoadAndExpectSuccess("content_script_execution_world.json");
+  const UserScriptList& user_scripts =
+      ContentScriptsInfo::GetContentScripts(extension.get());
+  ASSERT_EQ(3u, user_scripts.size());
+
+  // Content scripts which don't specify an execution world will default to the
+  // isolated world.
+  EXPECT_EQ(mojom::ExecutionWorld::kIsolated,
+            user_scripts[0]->execution_world());
+
+  // Content scripts which specify an execution world will run on the world that
+  // was specified.
+  EXPECT_EQ(mojom::ExecutionWorld::kMain, user_scripts[1]->execution_world());
+  EXPECT_EQ(mojom::ExecutionWorld::kIsolated,
+            user_scripts[2]->execution_world());
+}
+
+TEST_F(ContentScriptsManifestTest, ExecutionWorld_InvalidForMV2) {
+  scoped_refptr<const Extension> extension = LoadAndExpectWarning(
+      "content_script_execution_world_warning_for_mv2.json",
+      errors::kExecutionWorldRestrictedToMV3);
+  const UserScriptList& user_scripts =
+      ContentScriptsInfo::GetContentScripts(extension.get());
+  ASSERT_EQ(1u, user_scripts.size());
+
+  // The content script parsed from the manifest should be executing in the
+  // isolated world.
+  EXPECT_EQ(mojom::ExecutionWorld::kIsolated,
+            user_scripts[0]->execution_world());
+}
+
 }  // namespace extensions
diff --git a/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/change_title.js b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/change_title.js
new file mode 100644
index 0000000..51e33ebd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/change_title.js
@@ -0,0 +1,8 @@
+// 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.
+
+// Changes the document's title based on the existence/value of
+// window.mainWorldFlag, which is set by a script that's part of a web page.
+document.title = window.mainWorldFlag === 'from main world' ? 'MAIN_WORLD' :
+                                                              'ISOLATED_WORLD';
diff --git a/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/manifest.json
new file mode 100644
index 0000000..0994de46
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/manifest.json
@@ -0,0 +1,25 @@
+{
+  "manifest_version": 3,
+  "name": "Main world content script test extension",
+  "version": "0.1",
+  "description": "Tests that scripts can be injected into the main world",
+  "background": {
+    "service_worker": "worker.js",
+    "type": "module"
+  },
+  "permissions": ["scripting", "tabs"],
+  "host_permissions": ["*://main.example/*", "*://isolated.example/*"],
+  "content_scripts": [
+    {
+      "matches": ["*://main.example/*"],
+      "js": ["change_title.js"],
+      "world": "MAIN",
+      "runAt": "document_end"
+    }, {
+      "matches": ["*://isolated.example/*"],
+      "js": ["change_title.js"],
+      "world": "ISOLATED",
+      "runAt": "document_end"
+    }
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/worker.js b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/worker.js
new file mode 100644
index 0000000..0e6e8e1
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/main_world_injections/worker.js
@@ -0,0 +1,40 @@
+// 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 {openTab} from '/_test_resources/test_util/tabs_util.js';
+
+chrome.test.runTests([
+  async function contentScriptsInMainWorld() {
+    const config = await chrome.test.getConfig();
+
+    // Opens a tab where a script may be injected that changes the title based
+    // on the execution world it's running in, then call executeScript which
+    // checks the title.
+    const exampleUrl = `http://main.example:${
+        config.testServer.port}/extensions/main_world_script_flag.html`;
+    let tab = await openTab(exampleUrl);
+    let results = await chrome.scripting.executeScript({
+      target: {tabId: tab.id},
+      func: () => document.title,
+    });
+    chrome.test.assertEq('MAIN_WORLD', results[0].result);
+    chrome.test.succeed();
+  },
+
+  async function contentScriptsInIsolatedWorld() {
+    const config = await chrome.test.getConfig();
+
+    // Repeat the test above except the script should be running in the isolated
+    // world.
+    const hostPermsUrl = `http://isolated.example:${
+        config.testServer.port}/extensions/main_world_script_flag.html`;
+    const tab = await openTab(hostPermsUrl);
+    const results = await chrome.scripting.executeScript({
+      target: {tabId: tab.id},
+      func: () => document.title,
+    });
+    chrome.test.assertEq('ISOLATED_WORLD', results[0].result);
+    chrome.test.succeed();
+  },
+]);
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_1.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_1.js
new file mode 100644
index 0000000..be62d18
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_1.js
@@ -0,0 +1,7 @@
+// 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.
+
+var div = document.createElement('div');
+div.id = 'dynamic-1';
+document.body.appendChild(div);
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_2.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_2.js
new file mode 100644
index 0000000..00cf7df3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_2.js
@@ -0,0 +1,7 @@
+// 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.
+
+var div = document.createElement('div');
+div.id = 'dynamic-2';
+document.body.appendChild(div);
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_slightly_big.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_slightly_big.js
new file mode 100644
index 0000000..392bd6cf
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_slightly_big.js
@@ -0,0 +1,19 @@
+// 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.
+
+var div = document.createElement('div');
+div.id = 'dynamic-slightly-big';
+document.body.appendChild(div);
+/*
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE TO BE OVER 650 BYTES
+PADDING OUT THE FILE
+*/
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_too_big.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_too_big.js
new file mode 100644
index 0000000..eec3bfa
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_dynamic_too_big.js
@@ -0,0 +1,20 @@
+// 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.
+
+var div = document.createElement('div');
+div.id = 'dynamic-too-big';
+document.body.appendChild(div);
+/*
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+PADDING OUT THE FILE TO BE OVER 700 BYTES
+*/
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_manifest.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_manifest.js
new file mode 100644
index 0000000..580020c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/inject_manifest.js
@@ -0,0 +1,13 @@
+// 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.
+
+var div = document.createElement('div');
+div.id = 'manifest';
+document.body.appendChild(div);
+/*
+PADDING OUT THE FILE TO BE OVER 400 BYTES
+PADDING OUT THE FILE TO BE OVER 400 BYTES
+PADDING OUT THE FILE TO BE OVER 400 BYTES
+PADDING OUT THE FILE TO BE OVER 400 BYTES
+*/
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/manifest.json b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/manifest.json
new file mode 100644
index 0000000..7f54139
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Large dynamic content scripts",
+  "version": "1.0",
+  "manifest_version": 3,
+  "description": "Dynamic scripts that surpass the size limit are not loaded.",
+  "background": {
+    "service_worker": "worker.js",
+    "type": "module"
+  },
+  "permissions": ["scripting", "tabs"],
+  "host_permissions": ["*://example.com/*"],
+  "content_scripts": [
+    {
+      "matches": ["<all_urls>"],
+      "js": ["inject_manifest.js"],
+      "run_at": "document_end"
+    }
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/worker.js b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/worker.js
new file mode 100644
index 0000000..ffc2848
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/dynamic_scripts_size_limits/worker.js
@@ -0,0 +1,112 @@
+// 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 {openTab} from '/_test_resources/test_util/tabs_util.js';
+
+function getInjectedElementIds() {
+  let childIds = [];
+  for (const child of document.body.children)
+    childIds.push(child.id);
+  return childIds.sort();
+};
+
+function getFileTooLargeError(fileName) {
+  return `Error: Scripts could not be loaded because '${fileName}' exceeds ` +
+      `the maximum script size or the extension's maximum total script size.`;
+}
+
+chrome.test.runTests([
+  async function exceedsPerScriptLimitSingleCall() {
+    await chrome.scripting.unregisterContentScripts();
+    const scriptFile = 'inject_dynamic_too_big.js';
+    const scripts = [{
+      id: 'too_big',
+      matches: ['*://example.com/*'],
+      js: [scriptFile],
+    }];
+
+    await chrome.test.assertPromiseRejects(
+        chrome.scripting.registerContentScripts(scripts),
+        getFileTooLargeError(scriptFile));
+
+    const registeredScripts =
+        await chrome.scripting.getRegisteredContentScripts();
+    chrome.test.assertEq([], registeredScripts);
+
+    chrome.test.succeed();
+  },
+
+  async function exceedsPerExtensionLimitSingleCall() {
+    await chrome.scripting.unregisterContentScripts();
+    const bigScriptFile = 'inject_dynamic_slightly_big.js';
+    const scripts = [
+      {
+        id: 'too_big',
+        matches: ['http://example.com/*'],
+        js: [
+          'inject_dynamic_1.js',
+          'inject_dynamic_2.js',
+          bigScriptFile,
+        ],
+      },
+      {
+        id: 'too_big_part2',
+        matches: ['https://example.com/*'],
+        js: [bigScriptFile],
+      }
+    ];
+
+    await chrome.test.assertPromiseRejects(
+        chrome.scripting.registerContentScripts(scripts),
+        getFileTooLargeError(bigScriptFile));
+
+    const registeredScripts =
+        await chrome.scripting.getRegisteredContentScripts();
+    chrome.test.assertEq([], registeredScripts);
+
+    chrome.test.succeed();
+  },
+
+  async function exceedsPerExtensionLimitOverall() {
+    await chrome.scripting.unregisterContentScripts();
+    const bigScriptFile = 'inject_dynamic_slightly_big.js';
+    const scripts = [{
+      id: 'too_big',
+      matches: ['http://example.com/*'],
+      js: [
+        'inject_dynamic_1.js',
+        bigScriptFile,
+        'inject_dynamic_2.js',
+      ],
+    }];
+
+    // Since the total length of files specified in the script above does not
+    // exceed the per-extension limit, the script is registered successfully.
+    await chrome.scripting.registerContentScripts(scripts);
+
+    // However, the total length of all scripts for the extension:
+    // inject_manifest.js and `scripts` exceed the extension limit, as these
+    // scripts are being loaded (manifest first, then dynamic), any file that
+    // would exceed the per-extension limit at load-time will not have its
+    // contents loaded and will not do anything once injected. In this case,
+    // the files that have any actual effect are: inject_manifest.js,
+    // inject_dynamic_1.js and inject_dynamic_1.js.
+    // inject_dynamic_slightly_big.js will not have its contents loaded and thus
+    // will not affect the tab's contents.
+    const config = await chrome.test.getConfig();
+    const url = `http://example.com:${config.testServer.port}/simple.html`;
+    let tab = await openTab(url);
+    let results = await chrome.scripting.executeScript(
+        {target: {tabId: tab.id}, func: getInjectedElementIds});
+
+    chrome.test.assertEq(1, results.length);
+    chrome.test.assertEq(
+        ['dynamic-1', 'dynamic-2', 'manifest'], results[0].result);
+    const registeredScripts =
+        await chrome.scripting.getRegisteredContentScripts();
+    chrome.test.assertEq(1, registeredScripts.length);
+
+    chrome.test.succeed();
+  },
+]);
diff --git a/chrome/test/data/extensions/manifest_tests/content_script_execution_world.json b/chrome/test/data/extensions/manifest_tests/content_script_execution_world.json
new file mode 100644
index 0000000..e8f9577f
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/content_script_execution_world.json
@@ -0,0 +1,17 @@
+{
+  "name": "ContentScriptsManifestTest.ExecutionWorld",
+  "version": "0.1",
+  "manifest_version": 3,
+  "content_scripts": [{
+    "matches": ["https://example.com/*"],
+    "js": ["file.js"]
+  }, {
+    "matches": ["https://google.com/*"],
+    "js": ["file.js"],
+    "world": "MAIN"
+  }, {
+    "matches": ["https://gmail.com/*"],
+    "js": ["file.js"],
+    "world": "ISOLATED"
+  }]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/content_script_execution_world_warning_for_mv2.json b/chrome/test/data/extensions/manifest_tests/content_script_execution_world_warning_for_mv2.json
new file mode 100644
index 0000000..d637ce23
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/content_script_execution_world_warning_for_mv2.json
@@ -0,0 +1,10 @@
+{
+  "name": "ContentScriptsManifestTest.ExecutionWorld_InvalidForMV2",
+  "version": "0.1",
+  "manifest_version": 2,
+  "content_scripts": [{
+    "matches": ["https://example.com/*"],
+    "js": ["file.js"],
+    "world": "MAIN"
+  }]
+}
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 125f596..e9578c0 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -21642,5 +21642,8 @@
         }
       }
     ]
+  },
+  "AppStoreRatingEnabled" : {
+    "reason_for_missing_test": "Maps into an iOS-specific pref"
   }
 }
diff --git a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_app_test.js b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_app_test.js
index ac91d125..a92f9a1 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_app_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_app_test.js
@@ -178,60 +178,40 @@
     return flushTasks();
   }
 
-  if (window.loadTimeData.getBoolean('isNetworkingEnabled') ||
-      window.loadTimeData.getBoolean('isInputEnabled')) {
-    test('SystemPagePopulated', () => {
-      return initializeDiagnosticsApp(
-                 fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
-                 fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
-          .then(() => {
-            const systemPage =
-                dx_utils.getNavigationViewPanelElement(page, 'system');
-            assertTrue(!!systemPage);
-            assertTrue(isVisible(systemPage));
-            assertFalse(isVisible(getCautionBanner()));
-            assertFalse(isVisible(getBottomNavContentDrawer()));
-            assertTrue(isVisible(getBottomNavContentPanel()));
-            assertTrue(isVisible(getSessionLogButton()));
-          });
-    });
+  test('SystemPagePopulated', () => {
+    return initializeDiagnosticsApp(
+               fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
+               fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
+        .then(() => {
+          const systemPage =
+              dx_utils.getNavigationViewPanelElement(page, 'system');
+          assertTrue(!!systemPage);
+          assertTrue(isVisible(systemPage));
+          assertFalse(isVisible(getCautionBanner()));
+          assertFalse(isVisible(getBottomNavContentDrawer()));
+          assertTrue(isVisible(getBottomNavContentPanel()));
+          assertTrue(isVisible(getSessionLogButton()));
+        });
+  });
 
-    test('BannerVisibliblityTogglesWithEvents', () => {
-      const bannerMessage = 'Diagnostics Banner Message';
-      return initializeDiagnosticsApp(
-                 fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
-                 fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
-          .then(() => {
-            assertFalse(isVisible(getCautionBanner()));
+  test('BannerVisibliblityTogglesWithEvents', () => {
+    const bannerMessage = 'Diagnostics Banner Message';
+    return initializeDiagnosticsApp(
+               fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
+               fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
+        .then(() => {
+          assertFalse(isVisible(getCautionBanner()));
 
-            return triggerShowBannerEvent(bannerMessage);
-          })
-          .then(() => {
-            assertTrue(isVisible(getCautionBanner()));
-            dx_utils.assertElementContainsText(
-                getCautionBannerMessage(), bannerMessage);
+          return triggerShowBannerEvent(bannerMessage);
+        })
+        .then(() => {
+          assertTrue(isVisible(getCautionBanner()));
+          dx_utils.assertElementContainsText(
+              getCautionBannerMessage(), bannerMessage);
 
-            return triggerDismissBannerEvent();
-          })
-          .then(() => assertFalse(isVisible(getCautionBanner())));
-    });
+          return triggerDismissBannerEvent();
+        })
+        .then(() => assertFalse(isVisible(getCautionBanner())));
+  });
 
-    test('SaveSessionLogDisabledUntilResolved', () => {
-      return initializeDiagnosticsApp(
-                 fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
-                 fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
-          .then(() => {
-            assertFalse(getSessionLogButton().disabled);
-
-            DiagnosticsBrowserProxy.setSuccess(true);
-            getSessionLogButton().click();
-            assertTrue(getSessionLogButton().disabled);
-
-            return flushTasks();
-          })
-          .then(() => {
-            assertFalse(getSessionLogButton().disabled);
-          });
-    });
-  }
 });
diff --git a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js
index ab89424..7c73287a 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js
@@ -9,8 +9,6 @@
 import {NetworkType} from 'chrome://diagnostics/network_health_provider.mojom-webui.js';
 import {RoutineGroup} from 'chrome://diagnostics/routine_group.js';
 import {RoutineType} from 'chrome://diagnostics/system_routine_controller.mojom-webui.js';
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
-
 import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
 suite('diagnosticsUtilsTestSuite', function() {
@@ -65,9 +63,7 @@
   });
 
   test('AllRoutineGroupsPresent', () => {
-    loadTimeData.overrideValues({enableArcNetworkDiagnostics: true});
-    const isArcEnabled = loadTimeData.getBoolean('enableArcNetworkDiagnostics');
-    const routineGroups = getRoutineGroups(NetworkType.kWiFi, isArcEnabled);
+    const routineGroups = getRoutineGroups(NetworkType.kWiFi);
     const [
       localNetworkGroup,
        nameResolutionGroup,
@@ -93,28 +89,13 @@
   });
 
   test('NetworkTypeIsNotWiFi', () => {
-    const isArcEnabled = loadTimeData.getBoolean('enableArcNetworkDiagnostics');
-    const routineGroups = getRoutineGroups(NetworkType.kEthernet, isArcEnabled);
+    const routineGroups = getRoutineGroups(NetworkType.kEthernet);
     // WiFi group should be missing.
     assertEquals(routineGroups.length, 3);
     const groupNames = routineGroups.map(group => group.groupName);
     assertFalse(groupNames.includes('wifiGroupLabel'));
   });
 
-  test('ArcRoutinesDisabled', () => {
-    loadTimeData.overrideValues({enableArcNetworkDiagnostics: false});
-    const isArcEnabled = loadTimeData.getBoolean('enableArcNetworkDiagnostics');
-    const routineGroups = getRoutineGroups(NetworkType.kEthernet, isArcEnabled);
-    const [localNetworkGroup, nameResolutionGroup, internetConnectivityGroup] =
-        routineGroups;
-    assertFalse(
-        nameResolutionGroup.routines.includes(RoutineType.kArcDnsResolution));
-
-    assertFalse(localNetworkGroup.routines.includes(RoutineType.kArcPing));
-    assertFalse(
-        internetConnectivityGroup.routines.includes(RoutineType.kArcHttp));
-  });
-
   test('GetNetworkCardTitle', () => {
     // Force connection state into title by setting displayStateInTitle to true.
     setDisplayStateInTitleForTesting(true);
diff --git a/chrome/test/data/webui/chromeos/diagnostics/system_page_test.js b/chrome/test/data/webui/chromeos/diagnostics/system_page_test.js
index ae6d49e..e54a2fe 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/system_page_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/system_page_test.js
@@ -137,7 +137,6 @@
             document.createElement('system-page'));
     assertTrue(!!page);
     document.body.appendChild(page);
-    page.isNetworkingEnabled = false;
     return flushTasks();
   }
 
@@ -316,73 +315,6 @@
         .then(() => assertTrue(isVisible(getSessionLogButton())));
   });
 
-  // System page is only responsible for banner display when in stand-alone
-  // view.
-  if (!window.loadTimeData.getBoolean('isNetworkingEnabled')) {
-    test('RunningCpuTestsShowsBanner', () => {
-      /** @type {?RoutineSectionElement} */
-      let routineSection;
-      /** @type {!Array<!RoutineType>} */
-      const routines = [
-        RoutineType.kCpuCache,
-      ];
-      routineController.setFakeStandardRoutineResult(
-          RoutineType.kCpuCache, StandardRoutineResult.kTestPassed);
-      return initializeSystemPage(
-                 fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
-                 fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
-          .then(() => {
-            routineSection = dx_utils.getRoutineSection(
-                page.shadowRoot.querySelector('cpu-card'));
-            routineSection.routines = routines;
-            assertFalse(isVisible(getCautionBanner()));
-            return flushTasks();
-          })
-          .then(() => {
-            dx_utils.getRunTestsButtonFromSection(routineSection).click();
-            return flushTasks();
-          })
-          .then(() => {
-            assertTrue(isVisible(getCautionBanner()));
-            return routineController.resolveRoutineForTesting();
-          })
-          .then(() => flushTasks())
-          .then(() => assertFalse(isVisible(getCautionBanner())));
-    });
-
-    test('RunningMemoryTestsShowsBanner', () => {
-      /** @type {?RoutineSectionElement} */
-      let routineSection;
-      /** @type {!Array<!RoutineType>} */
-      const routines = [RoutineType.kMemory];
-      routineController.setFakeStandardRoutineResult(
-          RoutineType.kMemory, StandardRoutineResult.kTestPassed);
-      return initializeSystemPage(
-                 fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
-                 fakeBatteryInfo, fakeCpuUsage, fakeMemoryUsage)
-          .then(() => {
-            routineSection = dx_utils.getRoutineSection(
-                page.shadowRoot.querySelector('memory-card'));
-            routineSection.routines = routines;
-            assertFalse(isVisible(getCautionBanner()));
-            return flushTasks();
-          })
-          .then(() => {
-            dx_utils.getRunTestsButtonFromSection(routineSection).click();
-            return flushTasks();
-          })
-          .then(() => {
-            dx_utils.assertElementContainsText(
-                page.shadowRoot.querySelector('#banner > #bannerMsg'),
-                loadTimeData.getString('memoryBannerMessage'));
-            assertTrue(isVisible(getCautionBanner()));
-            return routineController.resolveRoutineForTesting();
-          })
-          .then(() => flushTasks())
-          .then(() => assertFalse(isVisible(getCautionBanner())));
-    });
-  }
-
   test('RecordNavigationCalled', () => {
     return initializeSystemPage(
                fakeSystemInfo, fakeBatteryChargeStatus, fakeBatteryHealth,
diff --git a/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
index db35891..09cc30b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
@@ -48,10 +48,7 @@
   async function showStaticColorButtons() {
     const toggleButton = getToggleButton();
     if (toggleButton.checked) {
-      personalizationStore.expectAction(ThemeActionName.SET_COLOR_SCHEME);
-      toggleButton.click();
-      await personalizationStore.waitForAction(
-          ThemeActionName.SET_COLOR_SCHEME);
+      await clickToggleButton();
     }
     assertFalse(getStaticColorSelector().hidden);
   }
@@ -59,14 +56,19 @@
   async function showColorSchemeButtons() {
     const toggleButton = getToggleButton();
     if (!toggleButton.checked) {
-      personalizationStore.expectAction(ThemeActionName.SET_COLOR_SCHEME);
-      toggleButton.click();
-      await personalizationStore.waitForAction(
-          ThemeActionName.SET_COLOR_SCHEME);
+      await clickToggleButton();
     }
     assertFalse(getColorSchemeSelector().hidden);
   }
 
+  async function clickToggleButton() {
+    // Any time the toggle button is clicked, the color scheme is set (if
+    // dynamic colors are disabled, then it is set to ColorScheme.kStatic).
+    personalizationStore.expectAction(ThemeActionName.SET_COLOR_SCHEME);
+    getToggleButton().click();
+    await personalizationStore.waitForAction(ThemeActionName.SET_COLOR_SCHEME);
+  }
+
   setup(() => {
     const mocks = baseSetup();
     themeProvider = mocks.themeProvider;
@@ -183,9 +185,7 @@
     const staticColorSelector = getStaticColorSelector();
     await showColorSchemeButtons();
 
-    personalizationStore.expectAction(ThemeActionName.SET_COLOR_SCHEME);
-    toggleButton.click();
-    await personalizationStore.waitForAction(ThemeActionName.SET_COLOR_SCHEME);
+    await clickToggleButton();
 
     assertFalse(
         toggleButton.checked, 'after clicking toggle, toggle should be off');
@@ -196,9 +196,7 @@
         staticColorSelector.hidden,
         'when the toggle is off, the static color buttons should be visible.');
 
-    personalizationStore.expectAction(ThemeActionName.SET_COLOR_SCHEME);
-    toggleButton.click();
-    await personalizationStore.waitForAction(ThemeActionName.SET_COLOR_SCHEME);
+    await clickToggleButton();
 
     assertFalse(
         colorSchemeSelector.hidden,
@@ -296,4 +294,35 @@
         personalizationStore.data.theme.staticColorSelected);
     assertEquals(button.getAttribute('aria-checked'), 'true');
   });
+
+  test('store previous color scheme selection locally', async () => {
+    const colorScheme = ColorScheme.kExpressive;
+    themeProvider.setColorScheme(colorScheme);
+    await initDynamicColorElement();
+
+    // Toggle to show static color buttons.
+    await clickToggleButton();
+    // Toggle to show color scheme buttons again.
+    await clickToggleButton();
+
+    // The same color scheme button should be selected.
+    assertDeepEquals(
+        colorScheme, personalizationStore.data.theme.colorSchemeSelected);
+  });
+
+  test('store previous static color selection locally', async () => {
+    const staticColorHex = '#edd0e4';
+    themeProvider.setStaticColor(hexColorToSkColor(staticColorHex));
+    await initDynamicColorElement();
+
+    // Toggle to show color scheme buttons.
+    await clickToggleButton();
+    // Toggle to show static color buttons again.
+    await clickToggleButton();
+
+    // The same static color button should be selected.
+    assertDeepEquals(
+        hexColorToSkColor(staticColorHex),
+        personalizationStore.data.theme.staticColorSelected);
+  });
 });
diff --git a/chrome/test/data/webui/settings/site_data_test.ts b/chrome/test/data/webui/settings/site_data_test.ts
index 55cf84f..3af8d4d 100644
--- a/chrome/test/data/webui/settings/site_data_test.ts
+++ b/chrome/test/data/webui/settings/site_data_test.ts
@@ -14,6 +14,9 @@
 
 // clang-format on
 
+// Name of the cookie default content setting pref.
+const PREF_NAME = 'generated.cookie_default_content_setting';
+
 suite('SiteDataTest', function() {
   let page: SettingsSiteDataElement;
   let settingsPrefs: SettingsPrefsElement;
@@ -39,11 +42,44 @@
     page.remove();
   });
 
+  test('DefaultSettingChangesUpdatePref', function() {
+    // Default is 'allow'.
+    assertEquals(page.getPref(PREF_NAME + '.value'), ContentSetting.ALLOW);
+
+    page.$.defaultSessionOnly.click();
+    assertEquals(
+        page.getPref(PREF_NAME + '.value'), ContentSetting.SESSION_ONLY);
+
+    page.$.defaultBlock.click();
+    assertEquals(page.getPref(PREF_NAME + '.value'), ContentSetting.BLOCK);
+
+    page.$.defaultAllow.click();
+    assertEquals(page.getPref(PREF_NAME + '.value'), ContentSetting.ALLOW);
+  });
+
+  test('PrefChangesUpdateDefaultSetting', function() {
+    // Default is 'allow'.
+    assertEquals(page.$.defaultGroup.selected, ContentSetting.ALLOW);
+
+    page.set('prefs.' + PREF_NAME + '.value', ContentSetting.SESSION_ONLY);
+    flush();
+    assertEquals(page.$.defaultGroup.selected, ContentSetting.SESSION_ONLY);
+
+    page.set('prefs.' + PREF_NAME + '.value', ContentSetting.BLOCK);
+    flush();
+    assertEquals(page.$.defaultGroup.selected, ContentSetting.BLOCK);
+
+    page.set('prefs.' + PREF_NAME + '.value', ContentSetting.ALLOW);
+    flush();
+    assertEquals(page.$.defaultGroup.selected, ContentSetting.ALLOW);
+  });
+
   test('ExceptionListsReadOnly', function() {
     // Check all exception lists are read only when the preference
     // reports as managed.
-    page.set('prefs.generated.cookie_default_content_setting', {
+    page.set('prefs.' + PREF_NAME, {
       value: ContentSetting.ALLOW,
+      type: chrome.settingsPrivate.PrefType.STRING,
       enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
     });
     let exceptionLists = page.shadowRoot!.querySelectorAll('site-list');
@@ -54,8 +90,9 @@
 
     // Return preference to unmanaged state and check all exception lists
     // are no longer read only.
-    page.set('prefs.generated.cookie_default_content_setting', {
+    page.set('prefs.' + PREF_NAME, {
       value: ContentSetting.ALLOW,
+      type: chrome.settingsPrivate.PrefType.STRING,
     });
     exceptionLists = page.shadowRoot!.querySelectorAll('site-list');
     assertEquals(exceptionLists.length, 3);
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
index 94129b47..7f8c2d5 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
@@ -62,19 +62,25 @@
 
   test('themes buttons create events', async () => {
     // Check that clicking the back button produces a back-click event.
-    let eventPromise = eventToPromise('back-click', themesElement);
+    const eventPromise = eventToPromise('back-click', themesElement);
     themesElement.$.backButton.click();
-    let event = await eventPromise;
+    const event = await eventPromise;
     assertTrue(!!event);
+  });
+
+  test('set theme and send event on theme click', async () => {
+    await setCollection('test', 2);
 
     // Check that clicking a theme produces a theme-select event.
-    await setCollection('test', 2);
-    eventPromise = eventToPromise('theme-select', themesElement);
+    const eventPromise = eventToPromise('theme-select', themesElement);
     const theme =
         themesElement.shadowRoot!.querySelector('.theme')! as HTMLButtonElement;
     theme.click();
-    event = await eventPromise;
+    const event = await eventPromise;
     assertTrue(!!event);
+
+    // Check that setBackgroundImage was called on click.
+    assertEquals(1, handler.getCallCount('setBackgroundImage'));
   });
 
   test('get collection images when collection changes', async () => {
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index ffc8dd7..7ba293a 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15277.0.0
\ No newline at end of file
+15288.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
index 81fef7a..50c7193 100644
--- a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
+++ b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
@@ -101,6 +101,52 @@
                                 apn_types_enum.value());
 }
 
+// static
+void CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+    bool success,
+    std::vector<chromeos::network_config::mojom::ApnType> old_apn_types,
+    absl::optional<chromeos::network_config::mojom::ApnState> apn_state,
+    absl::optional<chromeos::network_config::mojom::ApnState> old_apn_state) {
+  using ApnState = chromeos::network_config::mojom::ApnState;
+  base::UmaHistogramBoolean(kModifyCustomApnResultHistogram, success);
+
+  bool has_apn_state = old_apn_state.has_value() && apn_state.has_value();
+  bool was_apn_disabled = has_apn_state &&
+                          old_apn_state == ApnState::kEnabled &&
+                          apn_state == ApnState::kDisabled;
+  bool was_apn_enabled = has_apn_state &&
+                         old_apn_state == ApnState::kDisabled &&
+                         apn_state == ApnState::kEnabled;
+
+  if (was_apn_enabled) {
+    base::UmaHistogramBoolean(kEnableCustomApnResultHistogram, success);
+  } else if (was_apn_disabled) {
+    base::UmaHistogramBoolean(kDisableCustomApnResultHistogram, success);
+  }
+
+  // Only emit APN property metrics if the APN was successfully modified.
+  if (!success) {
+    return;
+  }
+
+  absl::optional<CellularNetworkMetricsLogger::ApnTypes> apn_types_enum =
+      GetApnTypes(old_apn_types);
+  if (!apn_types_enum.has_value()) {
+    NET_LOG(DEBUG) << "ApnTypes not logged for APN because it "
+                   << "doesn't have any APN types.";
+    return;
+  }
+  base::UmaHistogramEnumeration(kModifyCustomApnApnTypesHistogram,
+                                apn_types_enum.value());
+  if (was_apn_enabled) {
+    base::UmaHistogramEnumeration(kEnableCustomApnApnTypesHistogram,
+                                  apn_types_enum.value());
+  } else if (was_apn_disabled) {
+    base::UmaHistogramEnumeration(kDisableCustomApnApnTypesHistogram,
+                                  apn_types_enum.value());
+  }
+}
+
 void CellularNetworkMetricsLogger::OnConnectionResult(
     const std::string& guid,
     const absl::optional<std::string>& shill_error) {
diff --git a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
index 0d6dc18..3235e91d 100644
--- a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
+++ b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
@@ -41,6 +41,18 @@
       "Network.Ash.Cellular.Apn.RemoveCustomApn.Result";
   static constexpr char kRemoveCustomApnApnTypesHistogram[] =
       "Network.Ash.Cellular.Apn.RemoveCustomApn.ApnTypes";
+  static constexpr char kModifyCustomApnResultHistogram[] =
+      "Network.Ash.Cellular.Apn.ModifyCustomApn.Result";
+  static constexpr char kModifyCustomApnApnTypesHistogram[] =
+      "Network.Ash.Cellular.Apn.ModifyCustomApn.ApnTypes";
+  static constexpr char kEnableCustomApnResultHistogram[] =
+      "Network.Ash.Cellular.Apn.EnableCustomApn.Result";
+  static constexpr char kEnableCustomApnApnTypesHistogram[] =
+      "Network.Ash.Cellular.Apn.EnableCustomApn.ApnTypes";
+  static constexpr char kDisableCustomApnResultHistogram[] =
+      "Network.Ash.Cellular.Apn.DisableCustomApn.Result";
+  static constexpr char kDisableCustomApnApnTypesHistogram[] =
+      "Network.Ash.Cellular.Apn.DisableCustomApn.ApnTypes";
 
   CellularNetworkMetricsLogger(
       NetworkStateHandler* network_state_handler,
@@ -58,6 +70,11 @@
   static void LogRemoveCustomApnResult(
       bool success,
       std::vector<chromeos::network_config::mojom::ApnType> apn_types);
+  static void LogModifyCustomApnResult(
+      bool success,
+      std::vector<chromeos::network_config::mojom::ApnType> old_apn_types,
+      absl::optional<chromeos::network_config::mojom::ApnState> apn_state,
+      absl::optional<chromeos::network_config::mojom::ApnState> old_apn_state);
 
  private:
   // ConnectionInfoMetricsLogger::Observer:
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index d72ad94..1d10533 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -3758,6 +3758,9 @@
   if (!network || network->profile_path().empty()) {
     NET_LOG(ERROR) << "ModifyCustomApn: Called with unconfigured network: "
                    << network_guid << ".";
+    ash::CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/absl::nullopt,
+        /*old_apn_state=*/absl::nullopt);
     return;
   }
 
@@ -3765,6 +3768,9 @@
     NET_LOG(ERROR)
         << "ModifyCustomApn: Called with an APN without ID for network: "
         << network_guid << '.';
+    ash::CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/absl::nullopt,
+        /*old_apn_state=*/absl::nullopt);
     return;
   }
 
@@ -3777,17 +3783,28 @@
   if (!old_custom_apns || old_custom_apns->empty()) {
     NET_LOG(ERROR) << "ModifyCustomApn: Called for network: " << network_guid
                    << " that does not have any user APNs.";
+    ash::CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/absl::nullopt,
+        /*old_apn_state=*/absl::nullopt);
     return;
   }
 
   base::Value::List new_custom_apns;
+  std::vector<mojom::ApnType> modified_apn_old_apn_types;
+  mojom::ApnState modified_apn_old_apn_state;
+
   bool was_value_replaced = false;
   for (const base::Value& old_apn : *old_custom_apns) {
     const std::string* old_apn_id =
         old_apn.GetDict().FindString(::onc::cellular_apn::kId);
     DCHECK(old_apn_id);
+
     if (*apn->id == *old_apn_id) {
       new_custom_apns.Append(MojoApnToOnc(*apn));
+      modified_apn_old_apn_types = OncApnTypesToMojo(
+          GetRequiredStringList(&old_apn, ::onc::cellular_apn::kApnTypes));
+      modified_apn_old_apn_state = OncApnStateTypeToMojo(
+          old_apn.GetDict().FindString(::onc::cellular_apn::kState));
       was_value_replaced = true;
     } else {
       new_custom_apns.Append(old_apn.Clone());
@@ -3798,6 +3815,9 @@
     NET_LOG(ERROR) << "ModifyCustomApn: Called for network: " << network_guid
                    << " that does have an user APNs with id: " << *apn->id
                    << '.';
+    ash::CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/absl::nullopt,
+        /*old_apn_state=*/absl::nullopt);
     return;
   }
   NET_LOG(USER) << "ModifyCustomApn: Setting user APNs for: " << network_guid
@@ -3808,7 +3828,9 @@
   SetPropertiesInternal(
       network_guid, *network, UserApnListToOnc(network_guid, &new_custom_apns),
       base::BindOnce(
-          [](const std::string& guid, bool success,
+          [](const std::string& guid, std::vector<mojom::ApnType> old_apn_types,
+             const mojom::ApnState apn_state,
+             const mojom::ApnState old_apn_state, bool success,
              const std::string& message) {
             if (!success) {
               NET_LOG(ERROR)
@@ -3816,8 +3838,13 @@
                      "list in Shill for network: "
                   << guid << ": [" << message << ']';
             }
+            // TODO(b/162365553) Add test coverage for the case when there is a
+            // failure from shill.
+            ash::CellularNetworkMetricsLogger::LogModifyCustomApnResult(
+                success, old_apn_types, apn_state, old_apn_state);
           },
-          network_guid));
+          network_guid, std::move(modified_apn_old_apn_types), apn->state,
+          modified_apn_old_apn_state));
 }
 
 // static
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index e220aed..51476ab 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -966,6 +966,66 @@
         apn_types, apn_types_count);
   }
 
+  void AssertModifyCustomApnResultBucketCount(size_t num_success,
+                                              size_t num_failure) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kModifyCustomApnResultHistogram,
+        true, num_success);
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kModifyCustomApnResultHistogram,
+        false, num_failure);
+    histogram_tester_.ExpectTotalCount(
+        ash::CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
+        num_success);
+  }
+
+  void AssertModifyCustomApnPropertiesBucketCount(ApnTypes apn_types,
+                                                  size_t apn_types_count) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
+        apn_types, apn_types_count);
+  }
+
+  void AssertEnableCustomApnResultBucketCount(size_t num_success,
+                                              size_t num_failure) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kEnableCustomApnResultHistogram,
+        true, num_success);
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kEnableCustomApnResultHistogram,
+        false, num_failure);
+    histogram_tester_.ExpectTotalCount(
+        ash::CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
+        num_success);
+  }
+
+  void AssertEnableCustomApnPropertiesBucketCount(ApnTypes apn_types,
+                                                  size_t apn_types_count) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
+        apn_types, apn_types_count);
+  }
+
+  void AssertDisableCustomApnResultBucketCount(size_t num_success,
+                                               size_t num_failure) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kDisableCustomApnResultHistogram,
+        true, num_success);
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kDisableCustomApnResultHistogram,
+        false, num_failure);
+    histogram_tester_.ExpectTotalCount(
+        ash::CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
+        num_success);
+  }
+
+  void AssertDisableCustomApnPropertiesBucketCount(ApnTypes apn_types,
+                                                   size_t apn_types_count) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
+        apn_types, apn_types_count);
+  }
+
   NetworkHandlerTestHelper* helper() { return helper_.get(); }
   CrosNetworkConfigTestObserver* observer() { return observer_.get(); }
   CrosNetworkConfig* cros_network_config() {
@@ -2086,12 +2146,19 @@
                               mojom::ApnType::kAttach};
   test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                              ::onc::cellular_apn::kApnTypeAttach};
+  test_apn1.mojo_state = mojom::ApnState::kEnabled;
+  test_apn1.onc_state = ::onc::cellular_apn::kStateEnabled;
+
   test_apn1.id = "apn_id_1";
   ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
   EXPECT_EQ(expected_network_config_calls,
             network_config_observer.GetOnConfigurationModifiedCallCount());
   ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));
-
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/0,
+                                          /*num_failure=*/0);
+  AssertEnableCustomApnResultBucketCount(/*num_success=*/0,
+                                         /*num_failure=*/0);
   // Try to replace an APN when the custom APN list is empty, it should do
   // nothing
   network_metadata_store()->SetCustomApnList(kCellularGuid,
@@ -2100,6 +2167,11 @@
   EXPECT_EQ(expected_network_config_calls,
             network_config_observer.GetOnConfigurationModifiedCallCount());
   EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(kCellularGuid, {}));
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/2);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/0,
+                                          /*num_failure=*/0);
+  AssertEnableCustomApnResultBucketCount(/*num_success=*/0,
+                                         /*num_failure=*/0);
 
   TestApnData test_apn2;
   test_apn2.access_point_name = kCellularTestApn2;
@@ -2111,6 +2183,8 @@
                               mojom::ApnType::kAttach};
   test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                              ::onc::cellular_apn::kApnTypeAttach};
+  test_apn2.mojo_state = mojom::ApnState::kEnabled;
+  test_apn2.onc_state = ::onc::cellular_apn::kStateEnabled;
 
   // Add two custom APNs using the official API
   {
@@ -2127,9 +2201,8 @@
       network_metadata_store()->GetCustomApnList(kCellularGuid);
   ASSERT_TRUE(custom_apns);
   ASSERT_EQ(2u, custom_apns->size());
-  const std::string* first_apn_id =
-      custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);
-  ASSERT_TRUE(first_apn_id);
+  const std::string first_apn_id =
+      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);
 
   TestApnData test_apn3;
   test_apn3.access_point_name = kCellularTestApn3;
@@ -2139,6 +2212,8 @@
   test_apn3.attach = kCellularTestApnAttach3;
   test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
   test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
+  test_apn3.mojo_state = mojom::ApnState::kEnabled;
+  test_apn3.onc_state = ::onc::cellular_apn::kStateEnabled;
 
   // Verify that ModifyCustomApn does nothing if the input APN does not have an
   // ID.
@@ -2153,8 +2228,14 @@
                                               network_config_observer));
     EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
-
-  test_apn3.id = *first_apn_id;
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/0,
+                                         /*num_failure=*/3);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/0,
+                                          /*num_failure=*/0);
+  AssertEnableCustomApnResultBucketCount(/*num_success=*/0,
+                                         /*num_failure=*/0);
+  // Verify that ModifyCustomApn replaces the first custom APN
+  test_apn3.id = first_apn_id;
   ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
   EXPECT_EQ(++expected_network_config_calls,
             network_config_observer.GetOnConfigurationModifiedCallCount());
@@ -2165,6 +2246,17 @@
     EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                               network_config_observer));
     EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
+
+    AssertModifyCustomApnResultBucketCount(/*num_success=*/1,
+                                           /*num_failure=*/3);
+    // Note: The old APN types should be logged
+    AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
+                                               /*apn_types_count=*/1);
+    // Enable/Disable metrics are not logged if the ApnState doesn't change.
+    AssertDisableCustomApnResultBucketCount(/*num_success=*/0,
+                                            /*num_failure=*/0);
+    AssertEnableCustomApnResultBucketCount(/*num_success=*/0,
+                                           /*num_failure=*/0);
   }
 
   // Try to update an ID not found in the list, API should do nothing
@@ -2180,6 +2272,109 @@
                                               network_config_observer));
     EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/1,
+                                         /*num_failure=*/4);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
+                                             /*apn_types_count=*/1);
+
+  // Verify that disabling a custom APN changes its ApnState to disabled and
+  // logs metrics
+  test_apn3.id = first_apn_id;
+  test_apn3.mojo_state = mojom::ApnState::kDisabled;
+  test_apn3.onc_state = ::onc::cellular_apn::kStateDisabled;
+  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
+  EXPECT_EQ(++expected_network_config_calls,
+            network_config_observer.GetOnConfigurationModifiedCallCount());
+  {
+    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
+    EXPECT_TRUE(
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
+  }
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/2,
+                                         /*num_failure=*/4);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                             /*apn_types_count=*/1);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
+                                             /*apn_types_count=*/1);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/1,
+                                          /*num_failure=*/0);
+  AssertDisableCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                              /*apn_types_count=*/1);
+  AssertEnableCustomApnResultBucketCount(/*num_success=*/0,
+                                         /*num_failure=*/0);
+  AssertEnableCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                             /*apn_types_count=*/0);
+
+  // Verify that enabling a custom APN changes its ApnState to enabled and
+  // logs metrics
+  test_apn3.id = first_apn_id;
+  test_apn3.mojo_state = mojom::ApnState::kEnabled;
+  test_apn3.onc_state = ::onc::cellular_apn::kStateEnabled;
+  test_apn3.mojo_apn_types = {mojom::ApnType::kDefault};
+  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
+  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
+  EXPECT_EQ(++expected_network_config_calls,
+            network_config_observer.GetOnConfigurationModifiedCallCount());
+  {
+    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
+    EXPECT_TRUE(
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
+  }
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/3,
+                                         /*num_failure=*/4);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                             /*apn_types_count=*/2);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
+                                             /*apn_types_count=*/1);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefault,
+                                             /*apn_types_count=*/0);
+  AssertEnableCustomApnResultBucketCount(/*num_success=*/1,
+                                         /*num_failure=*/0);
+  AssertEnableCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                             /*apn_types_count=*/1);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/1,
+                                          /*num_failure=*/0);
+  AssertDisableCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                              /*apn_types_count=*/1);
+
+  // Verify that changing the APN type logs an event when changing from default
+  // APN type to a different type
+  test_apn3.id = first_apn_id;
+  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
+  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
+  test_apn3.mojo_state = mojom::ApnState::kDisabled;
+  test_apn3.onc_state = ::onc::cellular_apn::kStateDisabled;
+  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
+  EXPECT_EQ(++expected_network_config_calls,
+            network_config_observer.GetOnConfigurationModifiedCallCount());
+  {
+    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
+    EXPECT_TRUE(
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
+  }
+  AssertModifyCustomApnResultBucketCount(/*num_success=*/4,
+                                         /*num_failure=*/4);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefault,
+                                             /*apn_types_count=*/1);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                             /*apn_types_count=*/2);
+  AssertModifyCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
+                                             /*apn_types_count=*/1);
+  AssertDisableCustomApnResultBucketCount(/*num_success=*/2,
+                                          /*num_failure=*/0);
+  AssertDisableCustomApnPropertiesBucketCount(ApnTypes::kAttach,
+                                              /*apn_types_count=*/1);
+  AssertDisableCustomApnPropertiesBucketCount(ApnTypes::kDefault,
+                                              /*apn_types_count=*/1);
 }
 
 TEST_F(CrosNetworkConfigTest, ConnectedAPN_ApnRevampEnabled) {
diff --git a/chromeos/services/network_config/test_apn_data.cc b/chromeos/services/network_config/test_apn_data.cc
index 5245d38d..825ddfd 100644
--- a/chromeos/services/network_config/test_apn_data.cc
+++ b/chromeos/services/network_config/test_apn_data.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "chromeos/ash/components/network/policy_util.h"
 #include "components/onc/onc_constants.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
 namespace chromeos::network_config {
@@ -75,10 +76,11 @@
   apn->password = password;
   apn->attach = attach;
   if (ash::features::IsApnRevampEnabled()) {
-    apn->id = id;
+    apn->id = id.empty() ? absl::nullopt : absl::optional<std::string>(id);
     apn->authentication_type = mojo_authentication_type;
     apn->ip_type = mojo_ip_type;
     apn->apn_types = mojo_apn_types;
+    apn->state = mojo_state;
   }
   return apn;
 }
diff --git a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
index 465e184..30a2a366 100644
--- a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
+++ b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
@@ -149,27 +149,6 @@
         return frame_->widget_delegate()->ShouldShowCloseButton();
       case views::CAPTION_BUTTON_ICON_CUSTOM:
         return true;
-      case views::CAPTION_BUTTON_ICON_FLOAT: {
-        if (!chromeos::wm::features::IsFloatWindowEnabled() ||
-            !frame_->IsNativeWidgetInitialized()) {
-          return false;
-        }
-        // In tablet mode, only show the float/unfloat button if the window is
-        // floated.
-        if (chromeos::TabletState::Get()->InTabletMode()) {
-          return frame_->GetNativeWindow()->GetProperty(kWindowStateTypeKey) ==
-                 WindowStateType::kFloated;
-        }
-        // In clamshell mode, its harder to differentiate a non floated window
-        // with a floated window sometimes. Keep the pin button around for
-        // floatable windows if the developer flag is present. This is temporary
-        // until the float feature is launched, so instead of modifying build
-        // dependencies so lacros will depend on ash/constants, we use the
-        // string directly.
-        return base::CommandLine::ForCurrentProcess()->HasSwitch(
-                   "ash-dev-shortcuts") &&
-               chromeos::wm::CanFloatWindow(frame_->GetNativeWindow());
-      }
       // No back or menu button by default.
       case views::CAPTION_BUTTON_ICON_BACK:
       case views::CAPTION_BUTTON_ICON_MENU:
@@ -232,16 +211,6 @@
       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
   AddChildView(minimize_button_.get());
 
-  if (chromeos::wm::features::IsFloatWindowEnabled()) {
-    float_button_ = AddChildView(std::make_unique<views::FrameCaptionButton>(
-        base::BindRepeating(
-            &FrameCaptionButtonContainerView::FloatButtonPressed,
-            base::Unretained(this)),
-        views::CAPTION_BUTTON_ICON_FLOAT, HTMENU));
-    float_button_->SetTooltipText(
-        l10n_util::GetStringUTF16(IDS_MULTITASK_MENU_FLOAT_BUTTON_NAME));
-  }
-
   size_button_ = new FrameSizeButton(
       base::BindRepeating(&FrameCaptionButtonContainerView::SizeButtonPressed,
                           base::Unretained(this)),
@@ -258,22 +227,13 @@
       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
   AddChildView(close_button_.get());
 
-  SetButtonImage(views::CAPTION_BUTTON_ICON_FLOAT, chromeos::kFloatButtonIcon);
   SetButtonImage(views::CAPTION_BUTTON_ICON_MENU, chromeos::kFloatWindowIcon);
   SetButtonImage(views::CAPTION_BUTTON_ICON_MINIMIZE,
                  views::kWindowControlMinimizeIcon);
   SetButtonImage(views::CAPTION_BUTTON_ICON_CLOSE,
                  views::kWindowControlCloseIcon);
 
-  // The float button relies on minimum size to know if it can be floated, which
-  // can only be checked after the widget has been initialized.
-  if (frame->IsNativeWidgetInitialized()) {
-    UpdateCaptionButtonState(/*animate=*/false);
-  } else {
-    frame->widget_delegate()->RegisterWidgetInitializedCallback(base::BindOnce(
-        &FrameCaptionButtonContainerView::UpdateCaptionButtonState,
-        base::Unretained(this), /*animate=*/false));
-  }
+  UpdateCaptionButtonState(/*animate=*/false);
 }
 
 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() = default;
@@ -285,8 +245,6 @@
 void FrameCaptionButtonContainerView::SetPaintAsActive(bool paint_as_active) {
   if (custom_button_)
     custom_button_->SetPaintAsActive(paint_as_active);
-  if (float_button_)
-    float_button_->SetPaintAsActive(paint_as_active);
   menu_button_->SetPaintAsActive(paint_as_active);
   minimize_button_->SetPaintAsActive(paint_as_active);
   size_button_->SetPaintAsActive(paint_as_active);
@@ -298,13 +256,13 @@
     views::CaptionButtonIcon icon,
     const gfx::VectorIcon& icon_definition) {
   button_icon_map_[icon] = &icon_definition;
-  views::FrameCaptionButton* buttons[] = {float_button_, menu_button_,
-                                          minimize_button_, size_button_,
-                                          close_button_};
-  for (size_t i = 0; i < std::size(buttons); ++i) {
-    if (buttons[i] && buttons[i]->GetIcon() == icon)
-      buttons[i]->SetImage(icon, views::FrameCaptionButton::Animate::kNo,
-                           icon_definition);
+  views::FrameCaptionButton* buttons[] = {menu_button_, minimize_button_,
+                                          size_button_, close_button_};
+  for (views::FrameCaptionButton* button : buttons) {
+    if (button && button->GetIcon() == icon) {
+      button->SetImage(icon, views::FrameCaptionButton::Animate::kNo,
+                       icon_definition);
+    }
   }
 }
 
@@ -312,8 +270,6 @@
     SkColor background_color) {
   if (custom_button_)
     custom_button_->SetBackgroundColor(background_color);
-  if (float_button_)
-    float_button_->SetBackgroundColor(background_color);
   menu_button_->SetBackgroundColor(background_color);
   minimize_button_->SetBackgroundColor(background_color);
   size_button_->SetBackgroundColor(background_color);
@@ -381,12 +337,6 @@
     custom_button_->SetVisible(
         model_->IsVisible(views::CAPTION_BUTTON_ICON_CUSTOM));
   }
-  if (float_button_) {
-    float_button_->SetEnabled(
-        model_->IsEnabled(views::CAPTION_BUTTON_ICON_FLOAT));
-    float_button_->SetVisible(
-        model_->IsVisible(views::CAPTION_BUTTON_ICON_FLOAT));
-  }
   size_button_->SetEnabled(
       (model_->IsEnabled(views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE) ||
        model_->InZoomMode()));
@@ -403,14 +353,11 @@
 void FrameCaptionButtonContainerView::UpdateButtonsImageAndTooltip() {
   UpdateSizeButton();
   UpdateSnapButtons();
-  UpdateFloatButton();
 }
 
 void FrameCaptionButtonContainerView::SetButtonSize(const gfx::Size& size) {
   if (custom_button_)
     custom_button_->SetPreferredSize(size);
-  if (float_button_)
-    float_button_->SetPreferredSize(size);
   menu_button_->SetPreferredSize(size);
   minimize_button_->SetPreferredSize(size);
   size_button_->SetPreferredSize(size);
@@ -583,26 +530,6 @@
                      : chromeos::kWindowControlBottomSnappedIcon);
 }
 
-void FrameCaptionButtonContainerView::UpdateFloatButton() {
-  if (!float_button_)
-    return;
-
-  const bool floated = frame_->GetNativeWindow()->GetProperty(
-                           kWindowStateTypeKey) == WindowStateType::kFloated;
-  SetButtonImage(
-      views::CAPTION_BUTTON_ICON_FLOAT,
-      floated ? chromeos::kUnfloatButtonIcon : chromeos::kFloatButtonIcon);
-  float_button_->SetTooltipText(l10n_util::GetStringUTF16(
-      floated ? IDS_MULTITASK_MENU_EXIT_FLOAT_BUTTON_NAME
-              : IDS_MULTITASK_MENU_FLOAT_BUTTON_NAME));
-
-  // Float button also needs to update its visibility when float state changes.
-  float_button_->SetEnabled(
-      model_->IsEnabled(views::CAPTION_BUTTON_ICON_FLOAT));
-  float_button_->SetVisible(
-      model_->IsVisible(views::CAPTION_BUTTON_ICON_FLOAT));
-}
-
 void FrameCaptionButtonContainerView::MinimizeButtonPressed() {
   // Abort any animations of the button icons.
   SetButtonsToNormal(Animate::kNo);
@@ -663,15 +590,6 @@
   // TODO(oshima): Add metrics
 }
 
-void FrameCaptionButtonContainerView::FloatButtonPressed() {
-  // Abort any animations of the button icons.
-  SetButtonsToNormal(Animate::kNo);
-  DCHECK(chromeos::wm::features::IsFloatWindowEnabled());
-
-  // Toggle float current window.
-  FloatControllerBase::Get()->ToggleFloat(GetWidget()->GetNativeWindow());
-}
-
 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
   return minimize_button_->GetVisible();
 }
@@ -681,8 +599,6 @@
                  views::CAPTION_BUTTON_ICON_CLOSE, animate);
   if (custom_button_)
     custom_button_->SetState(views::Button::STATE_NORMAL);
-  if (float_button_)
-    float_button_->SetState(views::Button::STATE_NORMAL);
   menu_button_->SetState(views::Button::STATE_NORMAL);
   minimize_button_->SetState(views::Button::STATE_NORMAL);
   size_button_->SetState(views::Button::STATE_NORMAL);
@@ -706,9 +622,9 @@
   gfx::Point position(position_in_screen);
   views::View::ConvertPointFromScreen(this, &position);
 
-  views::FrameCaptionButton* buttons[] = {custom_button_, float_button_,
-                                          menu_button_,   minimize_button_,
-                                          size_button_,   close_button_};
+  views::FrameCaptionButton* buttons[] = {custom_button_, menu_button_,
+                                          minimize_button_, size_button_,
+                                          close_button_};
   int min_squared_distance = INT_MAX;
   views::FrameCaptionButton* closest_button = nullptr;
   for (size_t i = 0; i < std::size(buttons); ++i) {
@@ -732,11 +648,10 @@
 void FrameCaptionButtonContainerView::SetHoveredAndPressedButtons(
     const views::FrameCaptionButton* to_hover,
     const views::FrameCaptionButton* to_press) {
-  views::FrameCaptionButton* buttons[] = {custom_button_, float_button_,
-                                          menu_button_,   minimize_button_,
-                                          size_button_,   close_button_};
-  for (size_t i = 0; i < std::size(buttons); ++i) {
-    views::FrameCaptionButton* button = buttons[i];
+  views::FrameCaptionButton* buttons[] = {custom_button_, menu_button_,
+                                          minimize_button_, size_button_,
+                                          close_button_};
+  for (views::FrameCaptionButton* button : buttons) {
     if (!button)
       continue;
     views::Button::ButtonState new_state = views::Button::STATE_NORMAL;
diff --git a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
index b75ad43..686d48d0 100644
--- a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
+++ b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
@@ -86,11 +86,6 @@
       return container_view_->custom_button_;
     }
 
-    views::FrameCaptionButton* float_button() const {
-      DCHECK(chromeos::wm::features::IsFloatWindowEnabled());
-      return container_view_->float_button_;
-    }
-
    private:
     raw_ptr<FrameCaptionButtonContainerView> container_view_;
   };
@@ -128,8 +123,8 @@
   // state. A parent view should relayout to reflect the change in states.
   void UpdateCaptionButtonState(bool animate);
 
-  // Updates the image and tooltips of the size, float and snap buttons. These
-  // can change on state change or display orientation change.
+  // Updates the image and tooltips of the size and snap buttons. These can
+  // change on state change or display orientation change.
   void UpdateButtonsImageAndTooltip();
 
   // Sets the size of the buttons in this container.
@@ -169,9 +164,7 @@
   // well.
   void UpdateSizeButton();
   void UpdateSnapButtons();
-  void UpdateFloatButton();
 
-  void FloatButtonPressed();
   void MinimizeButtonPressed();
   void SizeButtonPressed();
   void CloseButtonPressed();
@@ -198,7 +191,6 @@
   // The buttons. In the normal button style, at most one of |minimize_button_|
   // and |size_button_| is visible.
   raw_ptr<views::FrameCaptionButton> custom_button_ = nullptr;
-  raw_ptr<views::FrameCaptionButton> float_button_ = nullptr;
   raw_ptr<views::FrameCaptionButton> menu_button_ = nullptr;
   raw_ptr<views::FrameCaptionButton> minimize_button_ = nullptr;
   raw_ptr<views::FrameCaptionButton> size_button_ = nullptr;
diff --git a/chromeos/ui/frame/caption_buttons/frame_size_button.cc b/chromeos/ui/frame/caption_buttons/frame_size_button.cc
index 3a7f3cc..1971c60 100644
--- a/chromeos/ui/frame/caption_buttons/frame_size_button.cc
+++ b/chromeos/ui/frame/caption_buttons/frame_size_button.cc
@@ -71,7 +71,6 @@
       return GetSnapDirectionForWindow(window, /*left_top=*/true);
     case views::CAPTION_BUTTON_ICON_RIGHT_BOTTOM_SNAPPED:
       return GetSnapDirectionForWindow(window, /*left_top=*/false);
-    case views::CAPTION_BUTTON_ICON_FLOAT:
     case views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
     case views::CAPTION_BUTTON_ICON_MINIMIZE:
     case views::CAPTION_BUTTON_ICON_CLOSE:
diff --git a/chromeos/ui/frame/multitask_menu/DIR_METADATA b/chromeos/ui/frame/multitask_menu/DIR_METADATA
new file mode 100644
index 0000000..f57ce1e
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/DIR_METADATA
@@ -0,0 +1 @@
+mixins: "//ash/wm/float/COMMON_METADATA"
\ No newline at end of file
diff --git a/chromeos/ui/frame/multitask_menu/OWNERS b/chromeos/ui/frame/multitask_menu/OWNERS
new file mode 100644
index 0000000..5639e5e
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/OWNERS
@@ -0,0 +1 @@
+file://ash/wm/float/OWNERS
diff --git a/chromeos/ui/vector_icons/BUILD.gn b/chromeos/ui/vector_icons/BUILD.gn
index 683ce729..eb62907 100644
--- a/chromeos/ui/vector_icons/BUILD.gn
+++ b/chromeos/ui/vector_icons/BUILD.gn
@@ -40,7 +40,6 @@
     "filetype_tini.icon",
     "filetype_video.icon",
     "filetype_word.icon",
-    "float_button.icon",
     "float_window.icon",
     "keyboard_shortcuts.icon",
     "notification_assistant.icon",
diff --git a/chromeos/ui/vector_icons/float_button.icon b/chromeos/ui/vector_icons/float_button.icon
deleted file mode 100644
index 51d43e1..0000000
--- a/chromeos/ui/vector_icons/float_button.icon
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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.
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 14, 3,
-V_LINE_TO, 5,
-H_LINE_TO, 13,
-V_LINE_TO, 9,
-LINE_TO, 15, 11,
-V_LINE_TO, 13,
-H_LINE_TO, 11,
-V_LINE_TO, 18,
-LINE_TO, 10, 19,
-LINE_TO, 9, 18,
-V_LINE_TO, 13,
-H_LINE_TO, 5,
-V_LINE_TO, 11,
-LINE_TO, 7, 9,
-V_LINE_TO, 5,
-H_LINE_TO, 6,
-V_LINE_TO, 3,
-H_LINE_TO, 14,
-CLOSE,
-MOVE_TO, 13, 11,
-LINE_TO, 11, 9,
-V_LINE_TO, 5,
-H_LINE_TO, 9,
-V_LINE_TO, 9,
-LINE_TO, 7, 11,
-H_LINE_TO, 13,
-CLOSE
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index aea8407..d7b96db 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1430,15 +1430,13 @@
       views::CAPTION_BUTTON_ICON_CLOSE,
       views::CAPTION_BUTTON_ICON_BACK,
       views::CAPTION_BUTTON_ICON_MENU,
-      views::CAPTION_BUTTON_ICON_FLOAT,
   };
   constexpr uint32_t kAllButtonMask =
       1 << views::CAPTION_BUTTON_ICON_MINIMIZE |
       1 << views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE |
       1 << views::CAPTION_BUTTON_ICON_CLOSE |
       1 << views::CAPTION_BUTTON_ICON_BACK |
-      1 << views::CAPTION_BUTTON_ICON_MENU |
-      1 << views::CAPTION_BUTTON_ICON_FLOAT;
+      1 << views::CAPTION_BUTTON_ICON_MENU;
 
   ash::NonClientFrameViewAsh* frame_view =
       static_cast<ash::NonClientFrameViewAsh*>(
diff --git a/components/history_clusters/core/history_clusters_util.cc b/components/history_clusters/core/history_clusters_util.cc
index b495c1b..09d02936 100644
--- a/components/history_clusters/core/history_clusters_util.cc
+++ b/components/history_clusters/core/history_clusters_util.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
 #include "base/i18n/case_conversion.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_piece.h"
@@ -255,29 +256,23 @@
     // For the empty-query state, only show clusters with
     // `should_show_on_prominent_ui_surfaces` set to true. This restriction is
     // NOT applied when the user is searching for a specific keyword.
-    clusters.erase(base::ranges::remove_if(
-                       clusters,
-                       [](const history::Cluster& cluster) {
-                         return !cluster.should_show_on_prominent_ui_surfaces;
-                       }),
-                   clusters.end());
+    base::EraseIf(clusters, [](const history::Cluster& cluster) {
+      return !cluster.should_show_on_prominent_ui_surfaces;
+    });
   } else {
-    clusters.erase(base::ranges::remove_if(
-                       clusters,
-                       [&](const history::Cluster& cluster) {
-                         // Erase all duplicate single-visit non-prominent
-                         // clusters.
-                         if (!cluster.should_show_on_prominent_ui_surfaces &&
-                             cluster.visits.size() == 1) {
-                           auto [unused_iterator, newly_inserted] =
-                               seen_single_visit_cluster_urls->insert(
-                                   cluster.visits[0].url_for_deduping);
-                           return !newly_inserted;
-                         }
+    base::EraseIf(clusters, [&](const history::Cluster& cluster) {
+      // Erase all duplicate single-visit non-prominent
+      // clusters.
+      if (!cluster.should_show_on_prominent_ui_surfaces &&
+          cluster.visits.size() == 1) {
+        auto [unused_iterator, newly_inserted] =
+            seen_single_visit_cluster_urls->insert(
+                cluster.visits[0].url_for_deduping);
+        return !newly_inserted;
+      }
 
-                         return false;
-                       }),
-                   clusters.end());
+      return false;
+    });
   }
 }
 
@@ -297,10 +292,8 @@
     }
 
     if (GetConfig().drop_hidden_visits) {
-      cluster.visits.erase(
-          base::ranges::remove_if(
-              cluster.visits, [](const auto& visit) { return visit.hidden; }),
-          cluster.visits.end());
+      base::EraseIf(cluster.visits,
+                    [](const auto& visit) { return visit.hidden; });
     }
   }
 }
diff --git a/components/js_injection/common/interfaces.mojom b/components/js_injection/common/interfaces.mojom
index 533d9684..c451e6a 100644
--- a/components/js_injection/common/interfaces.mojom
+++ b/components/js_injection/common/interfaces.mojom
@@ -29,11 +29,23 @@
   js_injection.mojom.OriginMatcher origin_matcher;
 };
 
+// The ArrayBuffer arm of the JsWebMessage union below, which contains the
+// ArrayBuffer payload sent between JavaScript and browser.
+struct JsWebMessageArrayBufferValue {
+  mojo_base.mojom.BigBuffer array_buffer_value;
+  // If is_resizable_by_user_javascript is false, then max_byte_length is
+  // unused.
+  //
+  // TODO(crbug.com/657632): Use a wrapped uint64 or uint64? once supported.
+  bool is_resizable_by_user_javascript = false;
+  uint64 max_byte_length = 0;
+};
+
 // JsWebMessage struct contains the message payload sent between
 // JavaScript and Browser.
 union JsWebMessage {
   mojo_base.mojom.String16 string_value;
-  mojo_base.mojom.BigBuffer array_buffer_value;
+  JsWebMessageArrayBufferValue array_buffer_value;
 };
 
 // For JavaScript postMessage() API, implemented by browser.
diff --git a/components/js_injection/common/web_message_mojom_traits.cc b/components/js_injection/common/web_message_mojom_traits.cc
index b5d17a1..70a83c8 100644
--- a/components/js_injection/common/web_message_mojom_traits.cc
+++ b/components/js_injection/common/web_message_mojom_traits.cc
@@ -16,6 +16,25 @@
 namespace mojo {
 
 // static
+bool StructTraits<js_injection::mojom::JsWebMessageArrayBufferValueDataView,
+                  std::unique_ptr<blink::WebMessageArrayBufferPayload>>::
+    Read(js_injection::mojom::JsWebMessageArrayBufferValueDataView r,
+         std::unique_ptr<blink::WebMessageArrayBufferPayload>* out) {
+  mojo_base::BigBufferView big_buffer_view;
+  if (!r.ReadArrayBufferValue(&big_buffer_view)) {
+    return false;
+  }
+  absl::optional<size_t> max_byte_length;
+  if (r.is_resizable_by_user_javascript()) {
+    max_byte_length = base::checked_cast<size_t>(r.max_byte_length());
+  }
+  *out = blink::WebMessageArrayBufferPayload::CreateFromBigBuffer(
+      mojo_base::BigBufferView::ToBigBuffer(std::move(big_buffer_view)),
+      max_byte_length);
+  return true;
+}
+
+// static
 js_injection::mojom::JsWebMessageDataView::Tag UnionTraits<
     js_injection::mojom::JsWebMessageDataView,
     blink::WebMessagePayload>::GetTag(const blink::WebMessagePayload& payload) {
@@ -42,12 +61,12 @@
       return false;
     out->emplace<std::u16string>(std::move(string_value));
   } else if (r.is_array_buffer_value()) {
-    mojo_base::BigBufferView big_buffer_view;
-    if (!r.ReadArrayBufferValue(&big_buffer_view))
+    std::unique_ptr<blink::WebMessageArrayBufferPayload> array_buffer_value;
+    if (!r.ReadArrayBufferValue(&array_buffer_value)) {
       return false;
+    }
     out->emplace<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(
-        blink::WebMessageArrayBufferPayload::CreateFromBigBuffer(
-            mojo_base::BigBufferView::ToBigBuffer(std::move(big_buffer_view))));
+        std::move(array_buffer_value));
   } else {
     return false;
   }
diff --git a/components/js_injection/common/web_message_mojom_traits.h b/components/js_injection/common/web_message_mojom_traits.h
index f5a5a7a..c378a22 100644
--- a/components/js_injection/common/web_message_mojom_traits.h
+++ b/components/js_injection/common/web_message_mojom_traits.h
@@ -16,6 +16,33 @@
 namespace mojo {
 
 template <>
+struct StructTraits<js_injection::mojom::JsWebMessageArrayBufferValueDataView,
+                    std::unique_ptr<blink::WebMessageArrayBufferPayload>> {
+  static mojo_base::BigBuffer array_buffer_value(
+      const std::unique_ptr<blink::WebMessageArrayBufferPayload>&
+          array_buffer) {
+    auto big_buffer = mojo_base::BigBuffer(array_buffer->GetLength());
+    array_buffer->CopyInto(big_buffer);
+    return big_buffer;
+  }
+
+  static bool is_resizable_by_user_javascript(
+      const std::unique_ptr<blink::WebMessageArrayBufferPayload>&
+          array_buffer) {
+    return array_buffer->GetIsResizableByUserJavaScript();
+  }
+
+  static size_t max_byte_length(
+      const std::unique_ptr<blink::WebMessageArrayBufferPayload>&
+          array_buffer) {
+    return array_buffer->GetMaxByteLength();
+  }
+
+  static bool Read(js_injection::mojom::JsWebMessageArrayBufferValueDataView r,
+                   std::unique_ptr<blink::WebMessageArrayBufferPayload>* out);
+};
+
+template <>
 struct UnionTraits<js_injection::mojom::JsWebMessageDataView,
                    blink::WebMessagePayload> {
   static const std::u16string& string_value(
@@ -23,14 +50,10 @@
     return absl::get<std::u16string>(payload);
   }
 
-  static mojo_base::BigBuffer array_buffer_value(
-      const blink::WebMessagePayload& payload) {
-    auto& array_buffer =
-        absl::get<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(
-            payload);
-    auto big_buffer = mojo_base::BigBuffer(array_buffer->GetLength());
-    array_buffer->CopyInto(big_buffer);
-    return big_buffer;
+  static const std::unique_ptr<blink::WebMessageArrayBufferPayload>&
+  array_buffer_value(const blink::WebMessagePayload& payload) {
+    return absl::get<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(
+        payload);
   }
 
   static js_injection::mojom::JsWebMessageDataView::Tag GetTag(
diff --git a/components/keyed_service/ios/browser_state_keyed_service_factory.cc b/components/keyed_service/ios/browser_state_keyed_service_factory.cc
index 2ce7095..3b559667 100644
--- a/components/keyed_service/ios/browser_state_keyed_service_factory.cc
+++ b/components/keyed_service/ios/browser_state_keyed_service_factory.cc
@@ -24,20 +24,6 @@
   KeyedServiceFactory::SetTestingFactory(context, std::move(wrapped_factory));
 }
 
-KeyedService* BrowserStateKeyedServiceFactory::SetTestingFactoryAndUse(
-    web::BrowserState* context,
-    TestingFactory testing_factory) {
-  DCHECK(testing_factory);
-  return KeyedServiceFactory::SetTestingFactoryAndUse(
-      context,
-      base::BindRepeating(
-          [](const TestingFactory& testing_factory, void* context) {
-            return testing_factory.Run(
-                static_cast<web::BrowserState*>(context));
-          },
-          std::move(testing_factory)));
-}
-
 BrowserStateKeyedServiceFactory::BrowserStateKeyedServiceFactory(
     const char* name,
     BrowserStateDependencyManager* manager)
diff --git a/components/keyed_service/ios/browser_state_keyed_service_factory.h b/components/keyed_service/ios/browser_state_keyed_service_factory.h
index baaf7cb..4a6f9ff 100644
--- a/components/keyed_service/ios/browser_state_keyed_service_factory.h
+++ b/components/keyed_service/ios/browser_state_keyed_service_factory.h
@@ -46,12 +46,6 @@
   void SetTestingFactory(web::BrowserState* context,
                          TestingFactory testing_factory);
 
-  // Associates |testing_factory| with |context| and immediately returns the
-  // created KeyedService. Since the factory will be used immediately, it may
-  // not be empty.
-  KeyedService* SetTestingFactoryAndUse(web::BrowserState* context,
-                                        TestingFactory testing_factory);
-
  protected:
   // BrowserStateKeyedServiceFactories must communicate with a
   // BrowserStateDependencyManager. For all non-test code, write your subclass
diff --git a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
index afd1ccd..fe8123a 100644
--- a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
+++ b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.cc
@@ -25,21 +25,6 @@
                                                    std::move(wrapped_factory));
 }
 
-scoped_refptr<RefcountedKeyedService>
-RefcountedBrowserStateKeyedServiceFactory::SetTestingFactoryAndUse(
-    web::BrowserState* context,
-    TestingFactory testing_factory) {
-  DCHECK(testing_factory);
-  return RefcountedKeyedServiceFactory::SetTestingFactoryAndUse(
-      context,
-      base::BindRepeating(
-          [](const TestingFactory& testing_factory, void* context) {
-            return testing_factory.Run(
-                static_cast<web::BrowserState*>(context));
-          },
-          std::move(testing_factory)));
-}
-
 RefcountedBrowserStateKeyedServiceFactory::
     RefcountedBrowserStateKeyedServiceFactory(
         const char* name,
diff --git a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
index dcc7792..3f3bd96 100644
--- a/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
+++ b/components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h
@@ -48,13 +48,6 @@
   void SetTestingFactory(web::BrowserState* context,
                          TestingFactory testing_factory);
 
-  // Associates |testing_factory| with |context| and immediately returns the
-  // created KeyedService. Since the factory will be used immediately, it may
-  // not be empty.
-  scoped_refptr<RefcountedKeyedService> SetTestingFactoryAndUse(
-      web::BrowserState* context,
-      TestingFactory testing_factory);
-
  protected:
   // RefcountedBrowserStateKeyedServiceFactories must communicate with a
   // BrowserStateDependencyManager. For all non-test code, write your subclass
diff --git a/components/lens/BUILD.gn b/components/lens/BUILD.gn
index b2fccd7..1fd7aa7 100644
--- a/components/lens/BUILD.gn
+++ b/components/lens/BUILD.gn
@@ -11,7 +11,7 @@
   header = "buildflags.h"
   flags = [
     "ENABLE_LENS_DESKTOP=$enable_lens_desktop",
-    "ENABLE_LENS_DESKTOP_SIDE_PANEL=$enable_lens_desktop_side_panel",
+    "ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES=$enable_lens_desktop_google_branded_features",
   ]
 }
 
diff --git a/components/lens/features.gni b/components/lens/features.gni
index 4a9c6ec..7ac60ea9 100644
--- a/components/lens/features.gni
+++ b/components/lens/features.gni
@@ -16,6 +16,11 @@
 }
 
 declare_args() {
-  # Lens in the side panel is a Chrome specific feature
-  enable_lens_desktop_side_panel = enable_lens_desktop && is_chrome_branded
+  # Lens features that are specific to the Google Chrome branded browser.
+  # Features include:
+  #  - Region Search
+  #  - Side Panel
+  #  - PDF Support
+  enable_lens_desktop_google_branded_features =
+      enable_lens_desktop && is_chrome_branded
 }
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 9fac07da..9c0074c 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -14,7 +14,6 @@
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/debug/stack_trace.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
@@ -53,6 +52,20 @@
 
 namespace {
 
+constexpr bool is_android =
+#if BUILDFLAG(IS_ANDROID)
+    true;
+#else
+    false;
+#endif
+
+constexpr bool is_ios =
+#if BUILDFLAG(IS_IOS)
+    true;
+#else
+    false;
+#endif
+
 // Rotates |it| to be in the front of |matches|.
 // |it| must be a valid iterator of |matches| or equal to |matches->end()|.
 void RotateMatchToFront(ACMatches::iterator it, ACMatches* matches) {
@@ -63,17 +76,13 @@
   std::rotate(matches->begin(), it, next);
 }
 
-#if BUILDFLAG(IS_IOS)
 // Maximum number of pedals to show.
 // On iOS, the UI for pedals gets too visually cluttered with too many pedals.
-constexpr size_t kMaxPedalCount = 1;
+constexpr size_t kMaxPedalCount =
+    is_ios ? 1 : std::numeric_limits<size_t>::max();
 // Maximum index of a match in a result for which the pedal should be displayed.
-// On iOS, the UI for pedals gets too visually cluttered with too many pedals.
-constexpr size_t kMaxPedalMatchIndex = 3;
-#else
-constexpr size_t kMaxPedalCount = std::numeric_limits<size_t>::max();
-constexpr size_t kMaxPedalMatchIndex = std::numeric_limits<size_t>::max();
-#endif
+constexpr size_t kMaxPedalMatchIndex =
+    is_ios ? 3 : std::numeric_limits<size_t>::max();
 
 enum class DontCopyDoneProviders { kFalse, kTrue, kUnknown };
 
@@ -84,25 +93,21 @@
 
 // static
 size_t AutocompleteResult::GetMaxMatches(bool is_zero_suggest) {
-#if BUILDFLAG(IS_ANDROID)
-  constexpr size_t kDefaultMaxAutocompleteMatches = 10;
-  constexpr size_t kDefaultMaxZeroSuggestMatches = 15;
-#elif BUILDFLAG(IS_IOS)
-  constexpr size_t kDefaultMaxAutocompleteMatches = 6;
+  constexpr size_t kDefaultMaxAutocompleteMatches =
+      is_android ? 10 : (is_ios ? 6 : 8);
+  constexpr size_t kDefaultMaxZeroSuggestMatches =
+      is_android ? 15 : (is_ios ? 20 : 8);
+#if BUILDFLAG(IS_IOS)
   // By default, iPad has the same max as iPhone.
   // `kDefaultMaxAutocompleteMatches` defines a hard limit on the number of
   // autocomplete suggestions on iPad, so if an experiment defines
   // MaxZeroSuggestMatches to 15, it would be 15 on iPhone and 10 on iPad.
   constexpr size_t kMaxAutocompleteMatchesOnIPad = 10;
-  constexpr size_t kDefaultMaxZeroSuggestMatches = 20;
   // By default, iPad has the same max as iPhone. `kMaxZeroSuggestMatchesOnIPad`
   // defines a hard limit on the number of ZPS suggestions on iPad, so if an
   // experiment defines MaxZeroSuggestMatches to 15, it would be 15 on iPhone
   // and 10 on iPad.
   constexpr size_t kMaxZeroSuggestMatchesOnIPad = 10;
-#else
-  constexpr size_t kDefaultMaxAutocompleteMatches = 8;
-  constexpr size_t kDefaultMaxZeroSuggestMatches = 8;
 #endif
   static_assert(kMaxAutocompletePositionValue > kDefaultMaxAutocompleteMatches,
                 "kMaxAutocompletePositionValue must be larger than the largest "
@@ -150,11 +155,7 @@
 
 // static
 size_t AutocompleteResult::GetDynamicMaxMatches() {
-#if BUILDFLAG(IS_ANDROID)
-  constexpr const int kDynamicMaxMatchesLimit = 15;
-#else
-  constexpr const int kDynamicMaxMatchesLimit = 10;
-#endif
+  constexpr const int kDynamicMaxMatchesLimit = is_android ? 15 : 10;
   if (!base::FeatureList::IsEnabled(omnibox::kDynamicMaxAutocomplete))
     return AutocompleteResult::GetMaxMatches();
   return base::GetFieldTrialParamByFeatureAsInt(
@@ -189,13 +190,9 @@
             : DontCopyDoneProviders::kFalse;
   }
   if (g_dont_copy_done_providers == DontCopyDoneProviders::kTrue) {
-    old_matches->matches_.erase(
-        base::ranges::remove_if(*old_matches,
-                                [](const auto& old_match) {
-                                  return old_match.provider &&
-                                         old_match.provider->done();
-                                }),
-        old_matches->matches_.end());
+    base::EraseIf(old_matches->matches_, [](const auto& old_match) {
+      return old_match.provider && old_match.provider->done();
+    });
   }
 
   if (old_matches->empty())
@@ -203,14 +200,10 @@
 
   // Exclude specialized suggestion types from being transferred to prevent
   // user-visible artifacts.
-  old_matches->matches_.erase(
-      std::remove_if(
-          old_matches->begin(), old_matches->end(),
-          [](const auto& match) {
-            return match.type == AutocompleteMatchType::TILE_NAVSUGGEST ||
-                   match.type == AutocompleteMatchType::TILE_SUGGESTION;
-          }),
-      old_matches->matches_.end());
+  base::EraseIf(old_matches->matches_, [](const auto& match) {
+    return match.type == AutocompleteMatchType::TILE_NAVSUGGEST ||
+           match.type == AutocompleteMatchType::TILE_SUGGESTION;
+  });
 
   if (empty()) {
     // If we've got no matches we can copy everything from the last result.
@@ -300,19 +293,17 @@
   for (auto& match : matches_)
     match.ComputeStrippedDestinationURL(input, template_url_service);
 
-#if !BUILDFLAG(IS_IOS)
-  DemoteOnDeviceSearchSuggestions();
-#endif  // !BUILDFLAG(IS_IOS)
+  if (!is_ios)
+    DemoteOnDeviceSearchSuggestions();
 
   const auto& page_classification = input.current_page_classification();
   CompareWithDemoteByType<AutocompleteMatch> comparing_object(
       page_classification);
 
-#if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
   // Because tail suggestions are a "last resort", we cull the tail suggestions
   // if there are any non-default, non-tail suggestions.
-  MaybeCullTailSuggestions(&matches_, comparing_object);
-#endif
+  if (!is_android && !is_ios)
+    MaybeCullTailSuggestions(&matches_, comparing_object);
 
   DeduplicateMatches(&matches_);
 
@@ -1254,15 +1245,11 @@
   size_t url_count = 0;
   // Erase URL suggestions past the count of allowed ones, or anything past
   // maximum.
-  matches_.erase(
-      std::remove_if(matches_.begin(), matches_.end(),
-                     [&url_count, max_url_count](const AutocompleteMatch& m) {
-                       if (!AutocompleteMatch::IsSearchType(m.type) &&
-                           ++url_count > max_url_count)
-                         return true;
-                       return false;
-                     }),
-      matches_.end());
+  base::EraseIf(matches_,
+                [&url_count, max_url_count](const AutocompleteMatch& m) {
+                  return !AutocompleteMatch::IsSearchType(m.type) &&
+                         ++url_count > max_url_count;
+                });
 }
 
 // static
diff --git a/components/omnibox/browser/search_suggestion_parser_unittest.cc b/components/omnibox/browser/search_suggestion_parser_unittest.cc
index 6e4d12a..5aae6c5fb 100644
--- a/components/omnibox/browser/search_suggestion_parser_unittest.cc
+++ b/components/omnibox/browser/search_suggestion_parser_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/json/json_reader.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/test_scheme_classifier.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -1661,3 +1662,100 @@
         /*is_keyword_result=*/false, &results));
   }
 }
+
+TEST(SearchSuggestionParserTest, ParseCalculatorSuggestion) {
+  TestSchemeClassifier scheme_classifier;
+  AutocompleteInput input(u"1 + 1", metrics::OmniboxEventProto::NTP_REALBOX,
+                          scheme_classifier);
+
+  const std::string json_data = R"([
+    "1 + 1",
+    [
+      "1 + 1",
+      "= 2",
+      "1 + 1"
+    ],
+    ["", "Calculator", ""],
+    [],
+    {
+      "google:clientdata": {
+        "bpc": false,
+        "tlw": false
+      },
+      "google:suggestdetail": [
+        {},
+        {},
+        {
+          "a": "Song",
+          "dc": "#424242",
+          "i": "https://encrypted-tbn0.gstatic.com/images?q=song",
+          "q": "gs_ssp=eJzj4tFP1zcsNjAzMykwKDZg9GI1VNBWMAQAOlEEsA",
+          "t": "1+1",
+          "zae": "/g/1s0664p0s"
+        }
+      ],
+      "google:suggestrelevance": [1300, 1252, 1250],
+      "google:suggestsubtypes": [
+        [512, 355],
+        [],
+        [512]
+      ],
+      "google:suggesttype": [
+        "QUERY",
+        "CALCULATOR",
+        "ENTITY"
+      ],
+      "google:verbatimrelevance": 1300
+    }
+  ])";
+
+  absl::optional<base::Value> root_val = base::JSONReader::Read(json_data);
+  ASSERT_TRUE(root_val);
+
+  SearchSuggestionParser::Results results;
+  ASSERT_TRUE(SearchSuggestionParser::ParseSuggestResults(
+      *root_val, input, scheme_classifier, /*default_result_relevance=*/400,
+      /*is_keyword_result=*/false, &results));
+
+  ASSERT_EQ(3U, results.suggest_results.size());
+
+  // Most fields for a verbatim suggestion should be empty.
+  ASSERT_EQ(u"1 + 1", results.suggest_results[0].suggestion());
+  ASSERT_EQ(u"", results.suggest_results[0].annotation());
+  ASSERT_EQ("", results.suggest_results[0].image_dominant_color());
+  ASSERT_EQ("", results.suggest_results[0].image_url().spec());
+  ASSERT_EQ("", results.suggest_results[0].additional_query_params());
+  ASSERT_EQ(u"1 + 1", results.suggest_results[0].match_contents());
+  ASSERT_EQ("", results.suggest_results[0].entity_id());
+
+  // Calculator suggestions should have specific values for the |suggestion|,
+  // |match_contents|, and |annotation| fields.
+#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
+  ASSERT_EQ(u"2", results.suggest_results[1].suggestion());
+  ASSERT_EQ(u"2", results.suggest_results[1].annotation());
+  ASSERT_EQ("", results.suggest_results[1].image_dominant_color());
+  ASSERT_EQ("", results.suggest_results[1].image_url().spec());
+  ASSERT_EQ("", results.suggest_results[1].additional_query_params());
+  ASSERT_EQ(u"1 + 1", results.suggest_results[1].match_contents());
+  ASSERT_EQ("", results.suggest_results[1].entity_id());
+#else
+  ASSERT_EQ(u"2", results.suggest_results[1].suggestion());
+  ASSERT_EQ(u"", results.suggest_results[1].annotation());
+  ASSERT_EQ("", results.suggest_results[1].image_dominant_color());
+  ASSERT_EQ("", results.suggest_results[1].image_url().spec());
+  ASSERT_EQ("", results.suggest_results[1].additional_query_params());
+  ASSERT_EQ(u"= 2", results.suggest_results[1].match_contents());
+  ASSERT_EQ("", results.suggest_results[1].entity_id());
+#endif
+
+  // Entity data should be correctly sourced as usual.
+  ASSERT_EQ(u"1 + 1", results.suggest_results[2].suggestion());
+  ASSERT_EQ(u"Song", results.suggest_results[2].annotation());
+  ASSERT_EQ("#424242", results.suggest_results[2].image_dominant_color());
+  ASSERT_EQ("https://encrypted-tbn0.gstatic.com/images?q=song",
+            results.suggest_results[2].image_url().spec());
+  ASSERT_EQ("gs_ssp=eJzj4tFP1zcsNjAzMykwKDZg9GI1VNBWMAQAOlEEsA",
+            results.suggest_results[2].additional_query_params());
+  ASSERT_EQ(u"1+1", results.suggest_results[2].match_contents());
+  ASSERT_EQ("/g/1s0664p0s", results.suggest_results[2].entity_id());
+}
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index 9065e8a..29291a6 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -245,6 +245,11 @@
 BASE_FEATURE(kUnifiedPasswordManagerAndroidBranding,
              "UnifiedPasswordManagerAndroidBranding",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Enables new exploratory strings for the save/update password prompts.
+BASE_FEATURE(kExploratorySaveUpdatePasswordStrings,
+             "ExploratorySaveUpdatePasswordStrings",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
 // Enables support of sending additional votes on username first flow. The votes
@@ -287,6 +292,11 @@
 extern const base::FeatureParam<int> kMaxShownUPMErrorsBeforeEviction = {
     &kUnifiedPasswordManagerErrorMessages,
     "max_shown_auth_errors_before_eviction", -1};
+
+// The string version to use for the save/update password prompts when the user
+// is syncing passwords. The only supported versions currently are 1 and 2.
+extern const base::FeatureParam<int> kSaveUpdatePromptSyncingStringVersion = {
+    &kExploratorySaveUpdatePasswordStrings, "syncing_string_version", 1};
 #endif
 
 // Field trial identifier for password generation requirements.
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index f544e3d..1dd53bf6 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -74,6 +74,7 @@
 BASE_DECLARE_FEATURE(kUnifiedPasswordManagerSyncUsingAndroidBackendOnly);
 BASE_DECLARE_FEATURE(kUnifiedPasswordManagerReenrollment);
 BASE_DECLARE_FEATURE(kUnifiedPasswordManagerAndroidBranding);
+BASE_DECLARE_FEATURE(kExploratorySaveUpdatePasswordStrings);
 #endif
 BASE_DECLARE_FEATURE(kUsernameFirstFlowFallbackCrowdsourcing);
 
@@ -155,6 +156,8 @@
 
 extern const base::FeatureParam<bool> kIgnoreAuthErrorMessageTimeouts;
 extern const base::FeatureParam<int> kMaxShownUPMErrorsBeforeEviction;
+
+extern const base::FeatureParam<int> kSaveUpdatePromptSyncingStringVersion;
 #endif
 
 // Field trial and corresponding parameters.
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index ba754a0..9eb2f8c 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1050,6 +1050,7 @@
   1049: PrivacySandboxAdTopicsEnabled
   1050: PrivacySandboxSiteEnabledAdsEnabled
   1051: PrivacySandboxAdMeasurementEnabled
+  1052: AppStoreRatingEnabled
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/AppStoreRatingEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/AppStoreRatingEnabled.yaml
new file mode 100644
index 0000000..505aa27
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/AppStoreRatingEnabled.yaml
@@ -0,0 +1,23 @@
+caption: Allows users to be shown the <ph name="IOS_NAME">iOS</ph> App Store Rating promo
+desc: |-
+  When the policy is not set or set to Enabled, the App Store Rating promo may be shown to the user, at most once per year.
+  When the policy is set to Disabled, the App Store Rating promo will not be shown to the user.
+future_on:
+- ios
+features:
+  dynamic_refresh: true
+  per_profile: true
+type: main
+schema:
+  type: boolean
+items:
+- caption: Allow the App Store Rating promo to be displayed
+  value: true
+- caption: Do not allow the App Store Rating promo to be displayed
+  value: false
+owners:
+- hiramahmood@google.com
+- file://components/policy/OWNERS
+default: true
+example_value: false
+tags: []
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index a0c734f..47c9d5b 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -561,12 +561,9 @@
     // Newly arrived keystore keys could resolve pending encryption state in
     // keystore mode.
     DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE);
-    const absl::optional<sync_pb::NigoriKey> keystore_decryptor_key =
-        TryDecryptPendingKeystoreDecryptorToken(
-            sync_pb::EncryptedData(*state_.pending_keystore_decryptor_token));
 
-    absl::optional<ModelError> error = TryDecryptPendingKeysWith(
-        BuildDecryptionKeyBagForRemoteKeybag(keystore_decryptor_key));
+    absl::optional<ModelError> error =
+        TryDecryptPendingKeysWith(BuildDecryptionKeyBagForRemoteKeybag());
     if (error.has_value()) {
       processor_->ReportError(*error);
       return false;
@@ -686,21 +683,21 @@
 
   state_.trusted_vault_debug_info = specifics.trusted_vault_debug_info();
 
+  if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) {
+    state_.custom_passphrase_key_derivation_params =
+        GetKeyDerivationParamsFromSpecifics(specifics);
+  }
+
   absl::optional<sync_pb::NigoriKey> keystore_decryptor_key;
   if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) {
-    keystore_decryptor_key = TryDecryptPendingKeystoreDecryptorToken(
-        specifics.keystore_decryptor_token());
+    state_.pending_keystore_decryptor_token =
+        specifics.keystore_decryptor_token();
   } else {
     state_.pending_keystore_decryptor_token.reset();
   }
 
   const NigoriKeyBag decryption_key_bag_for_remote_update =
-      BuildDecryptionKeyBagForRemoteKeybag(keystore_decryptor_key);
-
-  if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) {
-    state_.custom_passphrase_key_derivation_params =
-        GetKeyDerivationParamsFromSpecifics(specifics);
-  }
+      BuildDecryptionKeyBagForRemoteKeybag();
 
   // Set incoming encrypted keys as pending, so they are processed in
   // TryDecryptPendingKeysWith(). If the keybag is not immediately decryptable,
@@ -747,27 +744,20 @@
   return absl::nullopt;
 }
 
-absl::optional<sync_pb::NigoriKey>
-NigoriSyncBridgeImpl::TryDecryptPendingKeystoreDecryptorToken(
-    const sync_pb::EncryptedData& keystore_decryptor_token) {
-  DCHECK(!keystore_decryptor_token.blob().empty());
-  sync_pb::NigoriKey keystore_decryptor_key;
-  if (state_.keystore_keys_cryptographer->DecryptKeystoreDecryptorToken(
-          keystore_decryptor_token, &keystore_decryptor_key)) {
-    state_.pending_keystore_decryptor_token.reset();
-    return keystore_decryptor_key;
-  }
-  state_.pending_keystore_decryptor_token = keystore_decryptor_token;
-  return absl::nullopt;
-}
-
-NigoriKeyBag NigoriSyncBridgeImpl::BuildDecryptionKeyBagForRemoteKeybag(
-    const absl::optional<sync_pb::NigoriKey>& keystore_decryptor_key) const {
+NigoriKeyBag NigoriSyncBridgeImpl::BuildDecryptionKeyBagForRemoteKeybag()
+    const {
   NigoriKeyBag decryption_key_bag = NigoriKeyBag::CreateEmpty();
 
-  if (keystore_decryptor_key.has_value()) {
+  if (state_.pending_keystore_decryptor_token.has_value()) {
     DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE);
-    decryption_key_bag.AddKeyFromProto(*keystore_decryptor_key);
+    sync_pb::NigoriKey keystore_decryptor_key;
+    if (state_.keystore_keys_cryptographer->DecryptKeystoreDecryptorToken(
+            *state_.pending_keystore_decryptor_token,
+            &keystore_decryptor_key)) {
+      // Note: |pending_keystore_decryptor_token| will be cleared upon
+      // successful decryption of |pending_keys|.
+      decryption_key_bag.AddKeyFromProto(keystore_decryptor_key);
+    }
   }
 
   if (state_.cryptographer->CanEncrypt()) {
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index 3cdac89..1d303c9 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -92,17 +92,12 @@
   absl::optional<ModelError> UpdateLocalState(
       const sync_pb::NigoriSpecifics& specifics);
 
-  absl::optional<sync_pb::NigoriKey> TryDecryptPendingKeystoreDecryptorToken(
-      const sync_pb::EncryptedData& keystore_decryptor_token);
-
   // Builds NigoriKeyBag, which contains keys acceptable for decryption of
   // |encryption_keybag| from remote NigoriSpecifics. Its content depends on
-  // current passphrase type and available keys: for KEYSTORE_PASSPHRASE it
-  // contains only |keystore_decryptor_key|, for all other passphrase types
-  // it contains deserialized |explicit_passphrase_key_| and current default
-  // encryption key.
-  NigoriKeyBag BuildDecryptionKeyBagForRemoteKeybag(
-      const absl::optional<sync_pb::NigoriKey>& keystore_decryptor_key) const;
+  // current passphrase type and available keys: it contains current default
+  // encryption key, for KEYSTORE_PASSPHRASE it additionally contains key
+  // obtained from |keystore_decryptor_token|.
+  NigoriKeyBag BuildDecryptionKeyBagForRemoteKeybag() const;
 
   // Uses |key_bag| to try to decrypt pending keys as represented in
   // |state_.pending_keys| (which must be set).
diff --git a/components/tracing/common/tracing_switches.cc b/components/tracing/common/tracing_switches.cc
index 42fb6a67..ba1efdc 100644
--- a/components/tracing/common/tracing_switches.cc
+++ b/components/tracing/common/tracing_switches.cc
@@ -107,4 +107,10 @@
 // (currently 32kB on Desktop or 4kB on Android).
 const char kTraceSmbSize[] = "trace-smb-size";
 
+// This is only used when we did not set buffer size in trace config and will be
+// used for all trace sessions. If not provided, we will use the default value
+// provided in perfetto_config.cc
+const char kDefaultTraceBufferSizeLimitInKb[] =
+    "default-trace-buffer-size-limit-in-kb";
+
 }  // namespace switches
diff --git a/components/tracing/common/tracing_switches.h b/components/tracing/common/tracing_switches.h
index e0baf33..7b951d9 100644
--- a/components/tracing/common/tracing_switches.h
+++ b/components/tracing/common/tracing_switches.h
@@ -25,6 +25,7 @@
 TRACING_EXPORT extern const char kTraceToConsole[];
 TRACING_EXPORT extern const char kBackgroundTracingOutputFile[];
 TRACING_EXPORT extern const char kTraceSmbSize[];
+TRACING_EXPORT extern const char kDefaultTraceBufferSizeLimitInKb[];
 
 }  // namespace switches
 
diff --git a/content/browser/android/message_payload.cc b/content/browser/android/message_payload.cc
index e2fc8cfe..4067411 100644
--- a/content/browser/android/message_payload.cc
+++ b/content/browser/android/message_payload.cc
@@ -33,6 +33,11 @@
 
   size_t GetLength() const override { return length_; }
 
+  // Java ArrayBuffers are always fixed-length.
+  bool GetIsResizableByUserJavaScript() const override { return false; }
+
+  size_t GetMaxByteLength() const override { return length_; }
+
   // Due to JNI limitation, Java ByteArray cannot be converted into base::span
   // trivially.
   absl::optional<base::span<const uint8_t>> GetAsSpanIfPossible()
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 03aa4e65..3f24cc6 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -292,8 +292,9 @@
 absl::optional<attribution_reporting::FilterData> DeserializeFilterData(
     const std::string& string) {
   proto::AttributionFilterData msg;
-  if (!msg.ParseFromString(string))
+  if (!msg.ParseFromString(string)) {
     return absl::nullopt;
+  }
 
   attribution_reporting::FilterValues::container_type filter_values;
   filter_values.reserve(msg.filter_values().size());
@@ -339,15 +340,17 @@
 absl::optional<attribution_reporting::AggregationKeys>
 DeserializeAggregationKeys(const std::string& str) {
   proto::AttributionAggregatableSource msg;
-  if (!msg.ParseFromString(str))
+  if (!msg.ParseFromString(str)) {
     return absl::nullopt;
+  }
 
   attribution_reporting::AggregationKeys::Keys::container_type keys;
   keys.reserve(msg.keys().size());
 
   for (const auto& [id, key] : msg.keys()) {
-    if (!key.has_high_bits() || !key.has_low_bits())
+    if (!key.has_high_bits() || !key.has_low_bits()) {
       return absl::nullopt;
+    }
 
     keys.emplace_back(id, absl::MakeUint128(key.high_bits(), key.low_bits()));
   }
@@ -358,14 +361,17 @@
 absl::optional<StoredSource::ActiveState> GetSourceActiveState(
     bool event_level_active,
     bool aggregatable_active) {
-  if (event_level_active && aggregatable_active)
+  if (event_level_active && aggregatable_active) {
     return StoredSource::ActiveState::kActive;
+  }
 
-  if (!event_level_active && !aggregatable_active)
+  if (!event_level_active && !aggregatable_active) {
     return StoredSource::ActiveState::kInactive;
+  }
 
-  if (!event_level_active)
+  if (!event_level_active) {
     return StoredSource::ActiveState::kReachedEventLevelAttributionLimit;
+  }
 
   // We haven't enforced aggregatable attribution limit yet.
   return absl::nullopt;
@@ -438,15 +444,17 @@
 
   absl::optional<attribution_reporting::FilterData> filter_data =
       DeserializeFilterData(statement.ColumnString(col++));
-  if (!filter_data)
+  if (!filter_data) {
     return absl::nullopt;
+  }
 
   bool event_level_active = statement.ColumnBool(col++);
   bool aggregatable_active = statement.ColumnBool(col++);
   absl::optional<StoredSource::ActiveState> active_state =
       GetSourceActiveState(event_level_active, aggregatable_active);
-  if (!active_state.has_value())
+  if (!active_state.has_value()) {
     return absl::nullopt;
+  }
 
   return StoredSourceData{
       .source = StoredSource(
@@ -475,8 +483,9 @@
   sql::Statement statement(
       db->GetCachedStatement(SQL_FROM_HERE, kReadSourceToAttributeSql));
   statement.BindInt64(0, *source_id);
-  if (!statement.Step())
+  if (!statement.Step()) {
     return absl::nullopt;
+  }
 
   return ReadSourceFromStatement(statement);
 }
@@ -511,8 +520,9 @@
 bool AttributionStorageSql::DeactivateSources(
     const std::vector<StoredSource::Id>& sources) {
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return false;
+  }
 
   static constexpr char kDeactivateSourcesSql[] =
       "UPDATE sources "
@@ -524,8 +534,9 @@
   for (StoredSource::Id id : sources) {
     statement.Reset(/*clear_bound_vars=*/true);
     statement.BindInt64(0, *id);
-    if (!statement.Run())
+    if (!statement.Run()) {
       return false;
+    }
   }
 
   return transaction.Commit();
@@ -536,8 +547,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Force the creation of the database if it doesn't exist, as we need to
   // persist the source.
-  if (!LazyInit(DbCreationPolicy::kCreateIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kCreateIfAbsent)) {
     return StoreSourceResult(StorableSource::Result::kInternalError);
+  }
 
   // Only delete expired impressions periodically to avoid excessive DB
   // operations.
@@ -546,8 +558,9 @@
   DCHECK_GE(delete_frequency, base::TimeDelta());
   const base::Time now = base::Time::Now();
   if (now - last_deleted_expired_sources_ >= delete_frequency) {
-    if (!DeleteExpiredSources())
+    if (!DeleteExpiredSources()) {
       return StoreSourceResult(StorableSource::Result::kInternalError);
+    }
     last_deleted_expired_sources_ = now;
   }
 
@@ -588,8 +601,9 @@
   }
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return StoreSourceResult(StorableSource::Result::kInternalError);
+  }
 
   AttributionStorageDelegate::RandomizedResponse randomized_response =
       delegate_->GetRandomizedResponse(common_info);
@@ -646,16 +660,18 @@
                      SerializeAggregationKeys(common_info.aggregation_keys()));
   statement.BindBlob(18, SerializeFilterData(common_info.filter_data()));
 
-  if (!statement.Run())
+  if (!statement.Run()) {
     return StoreSourceResult(StorableSource::Result::kInternalError);
+  }
 
   const StoredSource::Id source_id(db_->GetLastInsertRowId());
   const StoredSource stored_source(source.common_info(), attribution_logic,
                                    *active_state, source_id,
                                    /*aggregatable_budget_consumed=*/0);
 
-  if (!rate_limit_table_.AddRateLimitForSource(db_.get(), stored_source))
+  if (!rate_limit_table_.AddRateLimitForSource(db_.get(), stored_source)) {
     return StoreSourceResult(StorableSource::Result::kInternalError);
+  }
 
   absl::optional<base::Time> min_fake_report_time;
 
@@ -681,8 +697,9 @@
     }
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return StoreSourceResult(StorableSource::Result::kInternalError);
+  }
 
   return StoreSourceResult(
       attribution_logic == StoredSource::AttributionLogic::kTruthfully
@@ -733,8 +750,9 @@
   min_priority_statement.BindTime(1, report.report_time());
 
   const bool has_matching_report = min_priority_statement.Step();
-  if (!min_priority_statement.Succeeded())
+  if (!min_priority_statement.Succeeded()) {
     return MaybeReplaceLowerPriorityEventLevelReportResult::kError;
+  }
 
   // Deactivate the source at event-level as a new report will never be
   // generated in the future.
@@ -759,17 +777,20 @@
   // it. We could explicitly check the trigger time here, but it would only
   // be relevant in the case of an ill-behaved clock, in which case the rest of
   // the attribution functionality would probably also break.
-  if (conversion_priority <= min_priority)
+  if (conversion_priority <= min_priority) {
     return MaybeReplaceLowerPriorityEventLevelReportResult::kDropNewReport;
+  }
 
   absl::optional<AttributionReport> replaced =
       GetReport(conversion_id_with_min_priority);
-  if (!replaced.has_value())
+  if (!replaced.has_value()) {
     return MaybeReplaceLowerPriorityEventLevelReportResult::kError;
+  }
 
   // Otherwise, delete the existing report with the lowest priority.
-  if (!DeleteReportInternal(conversion_id_with_min_priority))
+  if (!DeleteReportInternal(conversion_id_with_min_priority)) {
     return MaybeReplaceLowerPriorityEventLevelReportResult::kError;
+  }
 
   replaced_report = std::move(replaced);
   return MaybeReplaceLowerPriorityEventLevelReportResult::kReplaceOldReport;
@@ -828,8 +849,9 @@
                                   : new_aggregatable_status;
         DCHECK(aggregatable_status.has_value());
 
-        if (!IsSuccessResult(*aggregatable_status))
+        if (!IsSuccessResult(*aggregatable_status)) {
           new_aggregatable_report = absl::nullopt;
+        }
 
         return CreateReportResult(
             trigger_time, *event_level_status, *aggregatable_status,
@@ -1069,8 +1091,9 @@
   statement.BindTime(2, base::Time::Now());
 
   // If there are no matching sources, return early.
-  if (!statement.Step())
+  if (!statement.Step()) {
     return statement.Succeeded();
+  }
 
   // The first one returned will be attributed; it has the highest priority.
   source_id_to_attribute = StoredSource::Id(statement.ColumnInt64(0));
@@ -1104,13 +1127,15 @@
     return EventLevelResult::kFalselyAttributedSource;
   }
 
-  if (!top_level_filters_match)
+  if (!top_level_filters_match) {
     return EventLevelResult::kNoMatchingSourceFilterData;
+  }
 
   const CommonSourceInfo& common_info = attribution_info.source.common_info();
 
-  if (attribution_info.time > common_info.event_report_window_time())
+  if (attribution_info.time > common_info.event_report_window_time()) {
     return EventLevelResult::kReportWindowPassed;
+  }
 
   const AttributionSourceType source_type = common_info.source_type();
 
@@ -1122,8 +1147,9 @@
                                        event_trigger.not_filters);
       });
 
-  if (event_trigger == trigger.registration().event_triggers.vec().end())
+  if (event_trigger == trigger.registration().event_triggers.vec().end()) {
     return EventLevelResult::kNoMatchingConfigurations;
+  }
 
   switch (ReportAlreadyStored(attribution_info.source.source_id(),
                               event_trigger->dedup_key,
@@ -1193,8 +1219,9 @@
   }
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return EventLevelResult::kInternalError;
+  }
 
   auto* event_level_data =
       absl::get_if<AttributionReport::EventLevelData>(&report.data());
@@ -1212,8 +1239,9 @@
       maybe_replace_lower_priority_report_result ==
           MaybeReplaceLowerPriorityEventLevelReportResult::
               kDropNewReportSourceDeactivated) {
-    if (!transaction.Commit())
+    if (!transaction.Commit()) {
       return EventLevelResult::kInternalError;
+    }
 
     dropped_report = std::move(report);
 
@@ -1239,8 +1267,9 @@
             attribution_info.time, report.report_time(),
             event_level_data->priority, report.external_report_id(),
             attribution_info.debug_key);
-    if (!id)
+    if (!id) {
       return EventLevelResult::kInternalError;
+    }
 
     event_level_data->id = *id;
   }
@@ -1267,15 +1296,18 @@
     // Update the attributed source.
     impression_update_statement.BindInt64(0,
                                           *attribution_info.source.source_id());
-    if (!impression_update_statement.Run())
+    if (!impression_update_statement.Run()) {
       return EventLevelResult::kInternalError;
+    }
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return EventLevelResult::kInternalError;
+  }
 
-  if (!create_report)
+  if (!create_report) {
     return EventLevelResult::kDroppedForNoise;
+  }
 
   return maybe_replace_lower_priority_report_result ==
                  MaybeReplaceLowerPriorityEventLevelReportResult::
@@ -1309,8 +1341,9 @@
   store_report_statement.BindInt64(4, priority);
   store_report_statement.BindString(5, external_report_id.AsLowercaseString());
   BindUint64OrNull(store_report_statement, 6, trigger_debug_key);
-  if (!store_report_statement.Run())
+  if (!store_report_statement.Run()) {
     return absl::nullopt;
+  }
 
   return AttributionReport::EventLevelData::Id(db_->GetLastInsertRowId());
 }
@@ -1362,8 +1395,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!report_types.Empty());
 
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return {};
+  }
 
   std::vector<AttributionReport> reports;
 
@@ -1417,12 +1451,14 @@
   while (statement.Step()) {
     absl::optional<AttributionReport> report =
         ReadReportFromStatement(statement);
-    if (report.has_value())
+    if (report.has_value()) {
       reports.push_back(std::move(*report));
+    }
   }
 
-  if (!statement.Succeeded())
+  if (!statement.Succeeded()) {
     return {};
+  }
 
   return reports;
 }
@@ -1430,8 +1466,9 @@
 absl::optional<base::Time> AttributionStorageSql::GetNextReportTime(
     base::Time time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return absl::nullopt;
+  }
 
   absl::optional<base::Time> next_event_level_report_time =
       GetNextEventLevelReportTime(time);
@@ -1467,8 +1504,9 @@
 std::vector<AttributionReport> AttributionStorageSql::GetReports(
     const std::vector<AttributionReport::Id>& ids) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return {};
+  }
 
   std::vector<AttributionReport> reports;
   for (AttributionReport::Id id : ids) {
@@ -1479,8 +1517,9 @@
         },
         id);
 
-    if (report.has_value())
+    if (report.has_value()) {
       reports.push_back(std::move(*report));
+    }
   }
   return reports;
 }
@@ -1494,8 +1533,9 @@
       db_->GetCachedStatement(SQL_FROM_HERE, kGetReportSql));
   statement.BindInt64(0, *conversion_id);
 
-  if (!statement.Step())
+  if (!statement.Step()) {
     return absl::nullopt;
+  }
 
   return ReadReportFromStatement(statement);
 }
@@ -1514,12 +1554,15 @@
         StoredSource::Id source_id(statement.ColumnInt64(0));
         source_ids.push_back(source_id);
       }
-      if (!statement.Succeeded())
+      if (!statement.Succeeded()) {
         return false;
-      if (source_ids.empty())
+      }
+      if (source_ids.empty()) {
         return true;
-      if (!DeleteSources(source_ids))
+      }
+      if (!DeleteSources(source_ids)) {
         return false;
+      }
       // Deliberately retain the existing bound vars so that the limit, etc are
       // the same.
       statement.Reset(/*clear_bound_vars=*/false);
@@ -1543,8 +1586,9 @@
       db_->GetCachedStatement(SQL_FROM_HERE, kSelectExpiredSourcesSql));
   select_expired_statement.BindTime(0, base::Time::Now());
   select_expired_statement.BindInt(1, kMaxDeletesPerBatch);
-  if (!delete_sources_from_paged_select(select_expired_statement))
+  if (!delete_sources_from_paged_select(select_expired_statement)) {
     return false;
+  }
 
   // Delete all sources that have no associated reports and are
   // inactive. This is done in a separate statement from
@@ -1569,8 +1613,9 @@
 
 bool AttributionStorageSql::DeleteReport(AttributionReport::Id report_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return true;
+  }
 
   return absl::visit(
       [&](auto id) {
@@ -1594,8 +1639,9 @@
     AttributionReport::Id report_id,
     base::Time new_report_time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return false;
+  }
 
   auto [statement_id, sql_query, report_id_int] = absl::visit(
       base::Overloaded{
@@ -1628,15 +1674,17 @@
 
   // If no delay is being applied (i.e. debug mode is active), return the
   // earliest report time nonetheless so that it is scheduled properly.
-  if (!delay.has_value())
+  if (!delay.has_value()) {
     return GetNextReportTime(base::Time::Min());
+  }
 
   DCHECK_GE(delay->min, base::TimeDelta());
   DCHECK_GE(delay->max, base::TimeDelta());
   DCHECK_LE(delay->min, delay->max);
 
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return absl::nullopt;
+  }
 
   base::Time now = base::Time::Now();
 
@@ -1682,8 +1730,9 @@
     StoragePartition::StorageKeyMatcherFunction filter,
     bool delete_rate_limit_data) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return;
+  }
 
   SCOPED_UMA_HISTOGRAM_TIMER("Conversions.ClearDataTime");
   if (filter.is_null() && (delete_begin.is_null() || delete_begin.is_min()) &&
@@ -1741,15 +1790,17 @@
 
   // TODO(csharrison, johnidel): Should we consider poisoning the DB if some of
   // the delete operations fail?
-  if (!statement.Succeeded())
+  if (!statement.Succeeded()) {
     return;
+  }
 
   // Delete the data in a transaction to avoid cases where the source part
   // of a report is deleted without deleting the associated report, or
   // vice versa.
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return;
+  }
 
   int aggregatable_maybe_deleted =
       ClearAggregatableAttributionsForOriginsInRange(
@@ -1767,8 +1818,9 @@
       base::flat_set<StoredSource::Id>(std::move(source_ids_to_delete))
           .extract();
 
-  if (!DeleteSources(source_ids_to_delete))
+  if (!DeleteSources(source_ids_to_delete)) {
     return;
+  }
 
   // Careful! At this point we can still have some vestigial entries in the DB.
   // For example, if a source has two reports, and one report is
@@ -1783,8 +1835,9 @@
   for (StoredSource::Id source_id : source_ids_to_delete) {
     delete_vestigial_statement.Reset(/*clear_bound_vars=*/true);
     delete_vestigial_statement.BindInt64(0, *source_id);
-    if (!delete_vestigial_statement.Run())
+    if (!delete_vestigial_statement.Run()) {
       return;
+    }
 
     num_event_reports_deleted += db_->GetLastChangeCount();
   }
@@ -1794,8 +1847,9 @@
   aggregatable_maybe_deleted =
       ClearAggregatableAttributionsForSourceIds(source_ids_to_delete);
 
-  if (aggregatable_maybe_deleted < 0)
+  if (aggregatable_maybe_deleted < 0) {
     return;
+  }
   num_aggregatable_reports_deleted += aggregatable_maybe_deleted;
 
   if (delete_rate_limit_data && !rate_limit_table_.ClearDataForSourceIds(
@@ -1809,8 +1863,9 @@
     return;
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return;
+  }
 
   RecordSourcesDeleted(static_cast<int>(source_ids_to_delete.size()));
   RecordReportsDeleted(num_event_reports_deleted,
@@ -1819,51 +1874,59 @@
 
 void AttributionStorageSql::ClearAllDataAllTime(bool delete_rate_limit_data) {
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return;
+  }
 
   static constexpr char kDeleteAllReportsSql[] =
       "DELETE FROM event_level_reports";
   sql::Statement delete_all_reports_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllReportsSql));
-  if (!delete_all_reports_statement.Run())
+  if (!delete_all_reports_statement.Run()) {
     return;
+  }
   int num_event_reports_deleted = db_->GetLastChangeCount();
 
   static constexpr char kDeleteAllSourcesSql[] = "DELETE FROM sources";
   sql::Statement delete_all_sources_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllSourcesSql));
-  if (!delete_all_sources_statement.Run())
+  if (!delete_all_sources_statement.Run()) {
     return;
+  }
   int num_sources_deleted = db_->GetLastChangeCount();
 
   static constexpr char kDeleteAllDedupKeysSql[] = "DELETE FROM dedup_keys";
   sql::Statement delete_all_dedup_keys_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllDedupKeysSql));
-  if (!delete_all_dedup_keys_statement.Run())
+  if (!delete_all_dedup_keys_statement.Run()) {
     return;
+  }
 
   static constexpr char kDeleteAllAggregationsSql[] =
       "DELETE FROM aggregatable_report_metadata";
   sql::Statement delete_all_aggregations_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllAggregationsSql));
-  if (!delete_all_aggregations_statement.Run())
+  if (!delete_all_aggregations_statement.Run()) {
     return;
+  }
 
   static constexpr char kDeleteAllContributionsSql[] =
       "DELETE FROM aggregatable_contributions";
   sql::Statement delete_all_contributions_statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllContributionsSql));
-  if (!delete_all_contributions_statement.Run())
+  if (!delete_all_contributions_statement.Run()) {
     return;
+  }
   int num_aggregatable_reports_deleted = db_->GetLastChangeCount();
 
   if (delete_rate_limit_data &&
-      !rate_limit_table_.ClearAllDataAllTime(db_.get()))
+      !rate_limit_table_.ClearAllDataAllTime(db_.get())) {
     return;
+  }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return;
+  }
 
   RecordSourcesDeleted(num_sources_deleted);
   RecordReportsDeleted(num_event_reports_deleted,
@@ -1882,8 +1945,9 @@
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kCountSourcesSql));
   statement.BindString(0, serialized_origin);
-  if (!statement.Step())
+  if (!statement.Step()) {
     return false;
+  }
   int64_t count = statement.ColumnInt64(0);
   return count < delegate_->GetMaxSourcesPerOrigin();
 }
@@ -1893,8 +1957,9 @@
     StoredSource::Id source_id,
     absl::optional<uint64_t> dedup_key,
     AttributionReport::Type report_type) {
-  if (!dedup_key.has_value())
+  if (!dedup_key.has_value()) {
     return ReportAlreadyStoredStatus::kNotStored;
+  }
 
   static constexpr char kCountReportsSql[] =
       "SELECT COUNT(*)FROM dedup_keys "
@@ -1907,8 +1972,9 @@
 
   // If there's an error, return true so `MaybeCreateAndStoreReport()`
   // returns early.
-  if (!statement.Step())
+  if (!statement.Step()) {
     return ReportAlreadyStoredStatus::kError;
+  }
 
   int64_t count = statement.ColumnInt64(0);
   return count > 0 ? ReportAlreadyStoredStatus::kStored
@@ -1935,8 +2001,9 @@
 
   statement.BindString(
       0, net::SchemefulSite(trigger.destination_origin()).Serialize());
-  if (!statement.Step())
+  if (!statement.Step()) {
     return ConversionCapacityStatus::kError;
+  }
   int64_t count = statement.ColumnInt64(0);
   int max = delegate_->GetMaxReportsPerDestination(report_type);
   DCHECK_GT(max, 0);
@@ -1946,8 +2013,9 @@
 
 std::vector<StoredSource> AttributionStorageSql::GetActiveSources(int limit) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent))
+  if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) {
     return {};
+  }
 
   // Negatives are treated as no limit
   // (https://sqlite.org/lang_select.html#limitoffset).
@@ -1967,24 +2035,28 @@
   while (statement.Step()) {
     absl::optional<StoredSourceData> source_data =
         ReadSourceFromStatement(statement);
-    if (source_data.has_value())
+    if (source_data.has_value()) {
       sources.push_back(std::move(source_data->source));
+    }
   }
-  if (!statement.Succeeded())
+  if (!statement.Succeeded()) {
     return {};
+  }
 
   for (auto& source : sources) {
     absl::optional<std::vector<uint64_t>> dedup_keys =
         ReadDedupKeys(source.source_id(), AttributionReport::Type::kEventLevel);
-    if (!dedup_keys.has_value())
+    if (!dedup_keys.has_value()) {
       return {};
+    }
     source.SetDedupKeys(std::move(*dedup_keys));
 
     absl::optional<std::vector<uint64_t>> aggregatable_dedup_keys =
         ReadDedupKeys(source.source_id(),
                       AttributionReport::Type::kAggregatableAttribution);
-    if (!aggregatable_dedup_keys.has_value())
+    if (!aggregatable_dedup_keys.has_value()) {
       return {};
+    }
     source.SetAggregatableDedupKeys(std::move(*aggregatable_dedup_keys));
   }
 
@@ -2005,8 +2077,9 @@
   while (statement.Step()) {
     dedup_keys.push_back(DeserializeUint64(statement.ColumnInt64(0)));
   }
-  if (!statement.Succeeded())
+  if (!statement.Succeeded()) {
     return absl ::nullopt;
+  }
 
   return dedup_keys;
 }
@@ -2047,8 +2120,9 @@
     // storage needs to be used for an operation which needs to operate even on
     // an empty database.
     case DbStatus::kDeferringCreation:
-      if (creation_policy == DbCreationPolicy::kIgnoreIfAbsent)
+      if (creation_policy == DbCreationPolicy::kIgnoreIfAbsent) {
         return false;
+      }
       break;
     case DbStatus::kDeferringOpen:
       break;
@@ -2099,19 +2173,22 @@
 }
 
 bool AttributionStorageSql::InitializeSchema(bool db_empty) {
-  if (db_empty)
+  if (db_empty) {
     return CreateSchema();
+  }
 
   sql::MetaTable meta_table;
 
   // Create the meta table if it doesn't already exist. The only version for
   // which this is the case is version 1.
-  if (!meta_table.Init(db_.get(), /*version=*/1, kCompatibleVersionNumber))
+  if (!meta_table.Init(db_.get(), /*version=*/1, kCompatibleVersionNumber)) {
     return false;
+  }
 
   int version = meta_table.GetVersionNumber();
-  if (version == kCurrentVersionNumber)
+  if (version == kCurrentVersionNumber) {
     return true;
+  }
 
   // Recreate the DB if the version is deprecated or too new. In the latter
   // case, the DB will never work until Chrome is re-upgraded. Assume the user
@@ -2130,12 +2207,14 @@
 
 bool AttributionStorageSql::CreateSchema() {
   base::ThreadTicks start_timestamp;
-  if (base::ThreadTicks::IsSupported())
+  if (base::ThreadTicks::IsSupported()) {
     start_timestamp = base::ThreadTicks::Now();
+  }
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return false;
+  }
 
   // TODO(johnidel, csharrison): Many sources will share a target origin and
   // a reporting origin, so it makes sense to make a "shared string" table for
@@ -2194,8 +2273,9 @@
       "aggregatable_budget_consumed INTEGER NOT NULL,"
       "aggregatable_source BLOB NOT NULL,"
       "filter_data BLOB NOT NULL)";
-  if (!db_->Execute(kImpressionTableSql))
+  if (!db_->Execute(kImpressionTableSql)) {
     return false;
+  }
 
   // Optimizes source lookup by conversion destination/reporting origin
   // during calls to `MaybeCreateAndStoreReport()`,
@@ -2207,8 +2287,9 @@
       "CREATE INDEX sources_by_active_destination_site_reporting_origin "
       "ON sources(event_level_active,aggregatable_active,"
       "destination_site,reporting_origin)";
-  if (!db_->Execute(kConversionDestinationIndexSql))
+  if (!db_->Execute(kConversionDestinationIndexSql)) {
     return false;
+  }
 
   // Optimizes calls to `DeleteExpiredSources()` and
   // `MaybeCreateAndStoreReport()` by indexing sources by expiry
@@ -2217,16 +2298,18 @@
   static constexpr char kImpressionExpiryIndexSql[] =
       "CREATE INDEX sources_by_expiry_time "
       "ON sources(expiry_time)";
-  if (!db_->Execute(kImpressionExpiryIndexSql))
+  if (!db_->Execute(kImpressionExpiryIndexSql)) {
     return false;
+  }
 
   // Optimizes counting active sources by source origin.
   static constexpr char kImpressionOriginIndexSql[] =
       "CREATE INDEX active_sources_by_source_origin "
       "ON sources(source_origin)"
       "WHERE event_level_active=1 OR aggregatable_active=1";
-  if (!db_->Execute(kImpressionOriginIndexSql))
+  if (!db_->Execute(kImpressionOriginIndexSql)) {
     return false;
+  }
 
   // TODO: Remove this during the next DB migration.
   static constexpr char kImpressionSiteReportingOriginIndexSql[] =
@@ -2234,8 +2317,9 @@
       "ON sources(source_site,reporting_origin)"
       "WHERE event_level_active=1 AND num_attributions=0 AND "
       "aggregatable_active=1 AND aggregatable_budget_consumed=0";
-  if (!db_->Execute(kImpressionSiteReportingOriginIndexSql))
+  if (!db_->Execute(kImpressionSiteReportingOriginIndexSql)) {
     return false;
+  }
 
   // All columns in this table are const except |report_time| and
   // |failed_send_attempts|,
@@ -2259,8 +2343,9 @@
       "failed_send_attempts INTEGER NOT NULL,"
       "external_report_id TEXT NOT NULL,"
       "debug_key INTEGER)";
-  if (!db_->Execute(kConversionTableSql))
+  if (!db_->Execute(kConversionTableSql)) {
     return false;
+  }
 
   // Optimize sorting reports by report time for calls to
   // `GetAttributionReports()`. The reports with the earliest report times are
@@ -2268,8 +2353,9 @@
   static constexpr char kConversionReportTimeIndexSql[] =
       "CREATE INDEX event_level_reports_by_report_time "
       "ON event_level_reports(report_time)";
-  if (!db_->Execute(kConversionReportTimeIndexSql))
+  if (!db_->Execute(kConversionReportTimeIndexSql)) {
     return false;
+  }
 
   // Want to optimize report look up by source id. This allows us to
   // quickly know if an expired source can be deleted safely if it has no
@@ -2278,11 +2364,13 @@
   static constexpr char kConversionImpressionIdIndexSql[] =
       "CREATE INDEX event_level_reports_by_source_id "
       "ON event_level_reports(source_id)";
-  if (!db_->Execute(kConversionImpressionIdIndexSql))
+  if (!db_->Execute(kConversionImpressionIdIndexSql)) {
     return false;
+  }
 
-  if (!rate_limit_table_.CreateTable(db_.get()))
+  if (!rate_limit_table_.CreateTable(db_.get())) {
     return false;
+  }
 
   static constexpr char kDedupKeyTableSql[] =
       "CREATE TABLE dedup_keys("
@@ -2290,8 +2378,9 @@
       "report_type INTEGER NOT NULL,"
       "dedup_key INTEGER NOT NULL,"
       "PRIMARY KEY(source_id,report_type,dedup_key))WITHOUT ROWID";
-  if (!db_->Execute(kDedupKeyTableSql))
+  if (!db_->Execute(kDedupKeyTableSql)) {
     return false;
+  }
 
   // ============================
   // AGGREGATE ATTRIBUTION SCHEMA
@@ -2324,16 +2413,18 @@
       "failed_send_attempts INTEGER NOT NULL,"
       "initial_report_time INTEGER NOT NULL,"
       "aggregation_coordinator INTEGER NOT NULL)";
-  if (!db_->Execute(kAggregatableReportMetadataTableSql))
+  if (!db_->Execute(kAggregatableReportMetadataTableSql)) {
     return false;
+  }
 
   // Optimizes aggregatable report look up by source id during calls to
   // `DeleteExpiredSources()`, `ClearAggregatableAttributionsForSourceIds()`.
   static constexpr char kAggregateSourceIdIndexSql[] =
       "CREATE INDEX aggregate_source_id_idx "
       "ON aggregatable_report_metadata(source_id)";
-  if (!db_->Execute(kAggregateSourceIdIndexSql))
+  if (!db_->Execute(kAggregateSourceIdIndexSql)) {
     return false;
+  }
 
   // Optimizes aggregatable report look up by trigger time for clearing site
   // data during calls to
@@ -2341,8 +2432,9 @@
   static constexpr char kAggregateTriggerTimeIndexSql[] =
       "CREATE INDEX aggregate_trigger_time_idx "
       "ON aggregatable_report_metadata(trigger_time)";
-  if (!db_->Execute(kAggregateTriggerTimeIndexSql))
+  if (!db_->Execute(kAggregateTriggerTimeIndexSql)) {
     return false;
+  }
 
   // Optimizes aggregatable report look up by report time to get reports in a
   // time range during calls to
@@ -2350,8 +2442,9 @@
   static constexpr char kAggregateReportTimeIndexSql[] =
       "CREATE INDEX aggregate_report_time_idx "
       "ON aggregatable_report_metadata(report_time)";
-  if (!db_->Execute(kAggregateReportTimeIndexSql))
+  if (!db_->Execute(kAggregateReportTimeIndexSql)) {
     return false;
+  }
 
   // All columns in this table are const.
   // `aggregation_id` is the primary key of a row in the
@@ -2366,24 +2459,27 @@
       "key_high_bits INTEGER NOT NULL,"
       "key_low_bits INTEGER NOT NULL,"
       "value INTEGER NOT NULL)";
-  if (!db_->Execute(kAggregatableContributionsTableSql))
+  if (!db_->Execute(kAggregatableContributionsTableSql)) {
     return false;
+  }
 
   // Optimizes contribution look up by aggregation id during calls to
   // `DeleteAggregatableContributions()`.
   static constexpr char kContributionAggregationIdIndexSql[] =
       "CREATE INDEX contribution_aggregation_id_idx "
       "ON aggregatable_contributions(aggregation_id)";
-  if (!db_->Execute(kContributionAggregationIdIndexSql))
+  if (!db_->Execute(kContributionAggregationIdIndexSql)) {
     return false;
+  }
 
   if (sql::MetaTable meta_table; !meta_table.Init(
           db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
     return false;
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return false;
+  }
 
   if (base::ThreadTicks::IsSupported()) {
     base::UmaHistogramMediumTimes("Conversions.Storage.CreationTime",
@@ -2429,8 +2525,9 @@
 bool AttributionStorageSql::DeleteSources(
     const std::vector<StoredSource::Id>& source_ids) {
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return false;
+  }
 
   static constexpr char kDeleteSourcesSql[] =
       "DELETE FROM sources WHERE source_id=?";
@@ -2440,8 +2537,9 @@
   for (StoredSource::Id source_id : source_ids) {
     delete_impression_statement.Reset(/*clear_bound_vars=*/true);
     delete_impression_statement.BindInt64(0, *source_id);
-    if (!delete_impression_statement.Run())
+    if (!delete_impression_statement.Run()) {
       return false;
+    }
   }
 
   static constexpr char kDeleteDedupKeySql[] =
@@ -2452,8 +2550,9 @@
   for (StoredSource::Id source_id : source_ids) {
     delete_dedup_key_statement.Reset(/*clear_bound_vars=*/true);
     delete_dedup_key_statement.BindInt64(0, *source_id);
-    if (!delete_dedup_key_statement.Run())
+    if (!delete_dedup_key_statement.Run()) {
       return false;
+    }
   }
 
   return transaction.Commit();
@@ -2467,8 +2566,9 @@
   DCHECK_LE(delete_begin, delete_end);
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return -1;
+  }
 
   // TODO(linnan): Considering optimizing SQL query by moving some logic to C++.
   // See the comment in crrev.com/c/3379484 for more information.
@@ -2506,8 +2606,9 @@
     }
   }
 
-  if (!statement.Succeeded() || !transaction.Commit())
+  if (!statement.Succeeded() || !transaction.Commit()) {
     return -1;
+  }
 
   return num_aggregate_reports_deleted;
 }
@@ -2515,19 +2616,22 @@
 bool AttributionStorageSql::DeleteReportInternal(
     AttributionReport::AggregatableAttributionData::Id aggregation_id) {
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return false;
+  }
 
   static constexpr char kDeleteAggregationSql[] =
       "DELETE FROM aggregatable_report_metadata WHERE aggregation_id=?";
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAggregationSql));
   statement.BindInt64(0, *aggregation_id);
-  if (!statement.Run())
+  if (!statement.Run()) {
     return false;
+  }
 
-  if (!DeleteAggregatableContributions(aggregation_id))
+  if (!DeleteAggregatableContributions(aggregation_id)) {
     return false;
+  }
 
   return transaction.Commit();
 }
@@ -2548,8 +2652,9 @@
 int AttributionStorageSql::ClearAggregatableAttributionsForSourceIds(
     const std::vector<StoredSource::Id>& source_ids) {
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return -1;
+  }
 
   static constexpr char kDeleteAggregationsSql[] =
       "DELETE FROM aggregatable_report_metadata "
@@ -2573,14 +2678,16 @@
       }
     }
 
-    if (!statement.Succeeded())
+    if (!statement.Succeeded()) {
       return -1;
+    }
 
     num_aggregatable_reports_deleted += db_->GetLastChangeCount();
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return -1;
+  }
 
   return num_aggregatable_reports_deleted;
 }
@@ -2601,12 +2708,14 @@
   while (statement.Step()) {
     absl::optional<AttributionReport> report =
         ReadAggregatableAttributionReportFromStatement(statement);
-    if (report.has_value())
+    if (report.has_value()) {
       reports.push_back(std::move(*report));
+    }
   }
 
-  if (!statement.Succeeded())
+  if (!statement.Succeeded()) {
     return {};
+  }
 
   return reports;
 }
@@ -2651,13 +2760,15 @@
                                ? budget - aggregatable_budget_consumed
                                : 0;
 
-  if (capacity == 0)
+  if (capacity == 0) {
     return RateLimitResult::kNotAllowed;
+  }
 
   const base::CheckedNumeric<int64_t> budget_required =
       aggregatable_attribution.BudgetRequired();
-  if (!budget_required.IsValid() || budget_required.ValueOrDie() > capacity)
+  if (!budget_required.IsValid() || budget_required.ValueOrDie() > capacity) {
     return RateLimitResult::kNotAllowed;
+  }
 
   return RateLimitResult::kAllowed;
 }
@@ -2708,16 +2819,18 @@
     bool top_level_filters_match,
     absl::optional<AttributionReport>& report,
     absl::optional<int>& max_aggregatable_reports_per_destination) {
-  if (!top_level_filters_match)
+  if (!top_level_filters_match) {
     return AggregatableResult::kNoMatchingSourceFilterData;
+  }
 
   const attribution_reporting::TriggerRegistration& trigger_registration =
       trigger.registration();
 
   const CommonSourceInfo& common_info = attribution_info.source.common_info();
 
-  if (attribution_info.time > common_info.aggregatable_report_window_time())
+  if (attribution_info.time > common_info.aggregatable_report_window_time()) {
     return AggregatableResult::kReportWindowPassed;
+  }
 
   std::vector<AggregatableHistogramContribution> contributions =
       CreateAggregatableHistogram(
@@ -2725,8 +2838,9 @@
           common_info.aggregation_keys(),
           trigger_registration.aggregatable_trigger_data,
           trigger_registration.aggregatable_values);
-  if (contributions.empty())
+  if (contributions.empty()) {
     return AggregatableResult::kNoHistograms;
+  }
 
   switch (
       ReportAlreadyStored(attribution_info.source.source_id(),
@@ -2775,8 +2889,9 @@
   DCHECK(aggregatable_attribution);
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return false;
+  }
 
   const AttributionInfo& attribution_info = report.attribution_info();
 
@@ -2798,8 +2913,9 @@
   insert_metadata_statement.BindInt(
       6, SerializeAggregationCoordinator(
              aggregatable_attribution->aggregation_coordinator));
-  if (!insert_metadata_statement.Run())
+  if (!insert_metadata_statement.Run()) {
     return false;
+  }
 
   aggregatable_attribution->id =
       AttributionReport::AggregatableAttributionData::Id(
@@ -2821,8 +2937,9 @@
         2, SerializeUint64(absl::Uint128Low64(contribution.key())));
     insert_contributions_statement.BindInt64(
         3, static_cast<int64_t>(contribution.value()));
-    if (!insert_contributions_statement.Run())
+    if (!insert_contributions_statement.Run()) {
       return false;
+    }
   }
 
   return transaction.Commit();
@@ -2852,11 +2969,13 @@
   }
 
   sql::Transaction transaction(db_.get());
-  if (!transaction.Begin())
+  if (!transaction.Begin()) {
     return AggregatableResult::kInternalError;
+  }
 
-  if (!StoreAggregatableAttributionReport(report))
+  if (!StoreAggregatableAttributionReport(report)) {
     return AggregatableResult::kInternalError;
+  }
 
   StoredSource::Id source_id = report.attribution_info().source.source_id();
 
@@ -2875,8 +2994,9 @@
     return AggregatableResult::kInternalError;
   }
 
-  if (!transaction.Commit())
+  if (!transaction.Commit()) {
     return AggregatableResult::kInternalError;
+  }
 
   return AggregatableResult::kSuccess;
 }
@@ -2890,8 +3010,9 @@
 
   absl::optional<StoredSourceData> source_data =
       ReadSourceFromStatement(statement);
-  if (!source_data.has_value())
+  if (!source_data.has_value()) {
     return absl::nullopt;
+  }
 
   int col = kSourceColumnCount;
   AttributionReport::AggregatableAttributionData::Id report_id(
@@ -2917,8 +3038,9 @@
 
   std::vector<AggregatableHistogramContribution> contributions =
       GetAggregatableContributions(report_id);
-  if (contributions.empty())
+  if (contributions.empty()) {
     return absl::nullopt;
+  }
 
   return AttributionReport(AttributionInfo(std::move(source_data->source),
                                            trigger_time, trigger_debug_key),
@@ -2938,8 +3060,9 @@
       db_->GetCachedStatement(SQL_FROM_HERE, kGetReportSql));
   statement.BindInt64(0, *report_id);
 
-  if (!statement.Step())
+  if (!statement.Step()) {
     return absl::nullopt;
+  }
 
   return ReadAggregatableAttributionReportFromStatement(statement);
 }
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index a63fe17..f0b21694 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -4619,6 +4619,27 @@
   }
 }
 
+TEST_F(AuthenticatorImplTest, PRFWithoutSupport) {
+  // This tests that the PRF extension doesn't trigger any DCHECKs or crashes
+  // when used with an authenticator doesn't doesn't support hmac-secret.
+  NavigateAndCommit(GURL(kTestOrigin1));
+
+  auto prf_value = blink::mojom::PRFValues::New();
+  const std::vector<uint8_t> salt1(32, 1);
+  prf_value->first = salt1;
+  std::vector<blink::mojom::PRFValuesPtr> prf_inputs;
+  prf_inputs.emplace_back(std::move(prf_value));
+
+  PublicKeyCredentialRequestOptionsPtr options =
+      GetTestPublicKeyCredentialRequestOptions();
+  options->prf = true;
+  options->prf_inputs = std::move(prf_inputs);
+
+  GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
+
+  EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
+}
+
 class AuthenticatorDevicePublicKeyTest : public AuthenticatorImplTest {
   void SetUp() override {
     AuthenticatorImplTest::SetUp();
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index ec152c3..16a8f13 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -133,14 +133,14 @@
 }
 
 absl::optional<content::IdentityRequestAccount> ParseAccount(
-    const base::Value& account,
+    const base::Value::Dict& account,
     const std::string& client_id) {
-  auto* id = account.FindStringKey(kAccountIdKey);
-  auto* email = account.FindStringKey(kAccountEmailKey);
-  auto* name = account.FindStringKey(kAccountNameKey);
-  auto* given_name = account.FindStringKey(kAccountGivenNameKey);
-  auto* picture = account.FindStringKey(kAccountPictureKey);
-  auto* approved_clients = account.FindListKey(kAccountApprovedClientsKey);
+  auto* id = account.FindString(kAccountIdKey);
+  auto* email = account.FindString(kAccountEmailKey);
+  auto* name = account.FindString(kAccountNameKey);
+  auto* given_name = account.FindString(kAccountGivenNameKey);
+  auto* picture = account.FindString(kAccountPictureKey);
+  auto* approved_clients = account.FindList(kAccountApprovedClientsKey);
 
   // required fields
   if (!(id && email && name))
@@ -150,7 +150,7 @@
 
   absl::optional<LoginState> approved_value;
   if (approved_clients) {
-    for (const base::Value& entry : approved_clients->GetList()) {
+    for (const base::Value& entry : *approved_clients) {
       if (entry.is_string() && entry.GetString() == client_id) {
         approved_value = LoginState::kSignIn;
         break;
@@ -162,7 +162,7 @@
       // kSignUp instead of leaving as nullopt.
       approved_value = LoginState::kSignUp;
     }
-    RecordApprovedClientsSize(approved_clients->GetList().size());
+    RecordApprovedClientsSize(approved_clients->size());
   }
 
   return content::IdentityRequestAccount(
@@ -172,19 +172,19 @@
 
 // Parses accounts from given Value. Returns true if parse is successful and
 // adds parsed accounts to the |account_list|.
-bool ParseAccounts(const base::Value* accounts,
+bool ParseAccounts(const base::Value::List& accounts,
                    AccountList& account_list,
                    const std::string& client_id) {
   DCHECK(account_list.empty());
-  if (!accounts->is_list())
-    return false;
 
   base::flat_set<std::string> account_ids;
-  for (auto& account : accounts->GetList()) {
-    if (!account.is_dict())
+  for (auto& account : accounts) {
+    const base::Value::Dict* account_dict = account.GetIfDict();
+    if (!account_dict) {
       return false;
+    }
 
-    auto parsed_account = ParseAccount(account, client_id);
+    auto parsed_account = ParseAccount(*account_dict, client_id);
     if (parsed_account) {
       if (account_ids.count(parsed_account->id))
         return false;
@@ -208,18 +208,15 @@
 
 // Parse IdentityProviderMetadata from given value. Overwrites |idp_metadata|
 // with the parsed value.
-void ParseIdentityProviderMetadata(const base::Value& idp_metadata_value,
+void ParseIdentityProviderMetadata(const base::Value::Dict& idp_metadata_value,
                                    int brand_icon_ideal_size,
                                    int brand_icon_minimum_size,
                                    IdentityProviderMetadata& idp_metadata) {
-  if (!idp_metadata_value.is_dict())
-    return;
-
-  idp_metadata.brand_background_color = ParseCssColor(
-      idp_metadata_value.FindStringKey(kIdpBrandingBackgroundColor));
+  idp_metadata.brand_background_color =
+      ParseCssColor(idp_metadata_value.FindString(kIdpBrandingBackgroundColor));
   if (idp_metadata.brand_background_color) {
     idp_metadata.brand_text_color = ParseCssColor(
-        idp_metadata_value.FindStringKey(kIdpBrandingForegroundColor));
+        idp_metadata_value.FindString(kIdpBrandingForegroundColor));
     if (idp_metadata.brand_text_color) {
       float text_contrast_ratio = color_utils::GetContrastRatio(
           *idp_metadata.brand_background_color, *idp_metadata.brand_text_color);
@@ -228,39 +225,45 @@
     }
   }
 
-  const base::Value* icons_value =
-      idp_metadata_value.FindKey(kIdpBrandingIcons);
-  if (icons_value != nullptr && icons_value->is_list()) {
-    std::vector<blink::Manifest::ImageResource> icons;
-    for (const base::Value& icon_value : icons_value->GetList()) {
-      if (!icon_value.is_dict())
-        continue;
+  const base::Value::List* icons_value =
+      idp_metadata_value.FindList(kIdpBrandingIcons);
+  if (!icons_value) {
+    return;
+  }
 
-      const std::string* icon_src =
-          icon_value.FindStringKey(kIdpBrandingIconUrl);
-      if (icon_src == nullptr)
-        continue;
-
-      blink::Manifest::ImageResource icon;
-      icon.src = GURL(*icon_src);
-      if (!icon.src.is_valid())
-        continue;
-
-      icon.purpose = {blink::mojom::ManifestImageResource_Purpose::MASKABLE};
-
-      absl::optional<int> icon_size =
-          icon_value.FindIntKey(kIdpBrandingIconSize);
-      int icon_size_int = icon_size ? icon_size.value() : 0;
-      icon.sizes.emplace_back(icon_size_int, icon_size_int);
-
-      icons.push_back(icon);
+  std::vector<blink::Manifest::ImageResource> icons;
+  for (const base::Value& icon_value : *icons_value) {
+    const base::Value::Dict* icon_value_dict = icon_value.GetIfDict();
+    if (!icon_value_dict) {
+      continue;
     }
 
-    idp_metadata.brand_icon_url =
-        blink::ManifestIconSelector::FindBestMatchingSquareIcon(
-            icons, brand_icon_ideal_size, brand_icon_minimum_size,
-            blink::mojom::ManifestImageResource_Purpose::MASKABLE);
+    const std::string* icon_src =
+        icon_value_dict->FindString(kIdpBrandingIconUrl);
+    if (!icon_src) {
+      continue;
+    }
+
+    blink::Manifest::ImageResource icon;
+    icon.src = GURL(*icon_src);
+    if (!icon.src.is_valid()) {
+      continue;
+    }
+
+    icon.purpose = {blink::mojom::ManifestImageResource_Purpose::MASKABLE};
+
+    absl::optional<int> icon_size =
+        icon_value_dict->FindInt(kIdpBrandingIconSize);
+    int icon_size_int = icon_size.value_or(0);
+    icon.sizes.emplace_back(icon_size_int, icon_size_int);
+
+    icons.push_back(icon);
   }
+
+  idp_metadata.brand_icon_url =
+      blink::ManifestIconSelector::FindBestMatchingSquareIcon(
+          icons, brand_icon_ideal_size, brand_icon_minimum_size,
+          blink::mojom::ManifestImageResource_Purpose::MASKABLE);
 }
 
 ParseStatus GetResponseError(std::string* response_body, int response_code) {
@@ -278,9 +281,10 @@
   if (!result.has_value())
     return ParseStatus::kInvalidResponseError;
 
-  auto& response = *result;
-  if (!response.is_dict())
+  const base::Value::Dict* response = result->GetIfDict();
+  if (!response) {
     return ParseStatus::kInvalidResponseError;
+  }
 
   return ParseStatus::kSuccess;
 }
@@ -374,13 +378,13 @@
     return;
   }
 
-  auto& response = *result;
+  const base::Value::Dict& response = result->GetDict();
   auto ExtractEndpoint = [&](const char* key) {
-    const base::Value* endpoint = response.FindKey(key);
-    if (!endpoint || !endpoint->is_string()) {
+    const std::string* endpoint = response.FindString(key);
+    if (!endpoint) {
       return GURL();
     }
-    return ResolveConfigUrl(provider, endpoint->GetString());
+    return ResolveConfigUrl(provider, *endpoint);
   };
 
   Endpoints endpoints;
@@ -389,7 +393,8 @@
   endpoints.client_metadata = ExtractEndpoint(kClientMetadataEndpointKey);
   endpoints.metrics = ExtractEndpoint(kMetricsEndpoint);
 
-  const base::Value* idp_metadata_value = response.FindKey(kIdpBrandingKey);
+  const base::Value::Dict* idp_metadata_value =
+      response.FindDict(kIdpBrandingKey);
   IdentityProviderMetadata idp_metadata;
   idp_metadata.config_url = provider;
   if (idp_metadata_value) {
@@ -412,13 +417,13 @@
     return;
   }
 
-  auto& response = *result;
+  const base::Value::Dict& response = result->GetDict();
   auto ExtractUrl = [&](const char* key) {
-    const base::Value* endpoint = response.FindKey(key);
-    if (!endpoint || !endpoint->is_string()) {
+    const std::string* endpoint = response.FindString(key);
+    if (!endpoint) {
       return std::string();
     }
-    return endpoint->GetString();
+    return *endpoint;
   };
 
   IdpNetworkRequestManager::ClientMetadata data;
@@ -440,10 +445,10 @@
   }
 
   AccountList account_list;
-  auto& response = *result;
-  const base::Value* accounts = response.FindKey(kAccountsKey);
+  const base::Value::Dict& response = result->GetDict();
+  const base::Value::List* accounts = response.FindList(kAccountsKey);
   bool accounts_present =
-      accounts && ParseAccounts(accounts, account_list, client_id);
+      accounts && ParseAccounts(*accounts, account_list, client_id);
 
   if (!accounts_present) {
     std::move(callback).Run(
@@ -465,18 +470,17 @@
     return;
   }
 
-  auto& response = *result;
-  const base::Value* token = response.FindKey(kTokenKey);
-  bool token_present = token && token->is_string();
+  const base::Value::Dict& response = result->GetDict();
+  const std::string* token = response.FindString(kTokenKey);
 
-  if (!token_present) {
+  if (!token) {
     std::move(callback).Run(
         {ParseStatus::kInvalidResponseError, fetch_status.response_code},
         std::string());
     return;
   }
   std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
-                          token->GetString());
+                          *token);
 }
 
 void OnLogoutCompleted(IdpNetworkRequestManager::LogoutCallback callback,
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 35a3c7e5..af6dbda3 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -3188,15 +3188,20 @@
   ResumeIfPaused();
 }
 
-void TestNavigationManager::WaitForFirstYieldAfterDidStartNavigation() {
+bool TestNavigationManager::WaitForFirstYieldAfterDidStartNavigation() {
   TRACE_EVENT(
       "test",
       "TestNavigationManager::WaitForFirstYieldAfterDidStartNavigation");
   if (current_state_ >= NavigationState::WILL_START)
-    return;
+    return true;
 
   DCHECK_EQ(desired_state_, NavigationState::WILL_START);
-  WaitForDesiredState();
+  // Ignore the result because DidStartNavigation will update |desired_state_|
+  // we check below.
+  (void)WaitForDesiredState();
+  // This returns false if the runloop was terminated by a timeout rather than
+  // reaching the |WILL_START|.
+  return current_state_ >= NavigationState::WILL_START;
 }
 
 bool TestNavigationManager::WaitForRequestStart() {
@@ -3224,10 +3229,10 @@
   return WaitForDesiredState();
 }
 
-void TestNavigationManager::WaitForNavigationFinished() {
+bool TestNavigationManager::WaitForNavigationFinished() {
   TRACE_EVENT("test", "TestNavigationManager::WaitForNavigationFinished");
   desired_state_ = NavigationState::FINISHED;
-  WaitForDesiredState();
+  return WaitForDesiredState();
 }
 
 void TestNavigationManager::DidStartNavigation(NavigationHandle* handle) {
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index a85ab69..022f135 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -1699,8 +1699,9 @@
   // where throttles run and none defer, this will break at the same time as
   // WaitForRequestStart. Note: since we won't know which throttle deferred,
   // don't use ResumeNavigation() after this call since it assumes we paused
-  // from the TestNavigationManagerThrottle.
-  void WaitForFirstYieldAfterDidStartNavigation();
+  // from the TestNavigationManagerThrottle. Returns false if the waiting was
+  // terminated before reaching DidStartNavigation (e.g. timeout).
+  bool WaitForFirstYieldAfterDidStartNavigation();
 
   // Waits until the navigation request is ready to be sent to the network
   // stack. This will wait until all NavigationThrottles have proceeded through
@@ -1714,8 +1715,9 @@
   [[nodiscard]] bool WaitForResponse();
 
   // Waits until the navigation has been finished. Will automatically resume
-  // navigations paused before this point.
-  void WaitForNavigationFinished();
+  // navigations paused before this point. Returns false if the waiting was
+  // terminated before reaching DidStartNavigation (e.g. timeout).
+  bool WaitForNavigationFinished();
 
   // Resume the navigation.
   // * Called after |WaitForRequestStart|, it causes the request to be sent.
@@ -1767,7 +1769,7 @@
 
   // Waits for the desired state. Returns false if the desired state cannot be
   // reached (eg the navigation finishes before reaching this state).
-  bool WaitForDesiredState();
+  [[nodiscard]] bool WaitForDesiredState();
 
   // Called when the state of the navigation has changed. This will either stop
   // the message loop if the state specified by the user has been reached, or
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 2a2410d..a1f691c 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -72,6 +72,10 @@
     "media/audio_decoder.h",
     "media/batching_media_log.cc",
     "media/batching_media_log.h",
+    "media/codec_factory.cc",
+    "media/codec_factory.h",
+    "media/codec_factory_mojo.cc",
+    "media/codec_factory_mojo.h",
     "media/gpu/gpu_video_accelerator_factories_impl.cc",
     "media/gpu/gpu_video_accelerator_factories_impl.h",
     "media/inspector_media_event_handler.cc",
@@ -340,7 +344,11 @@
   }
 
   if (is_fuchsia) {
-    sources += [ "renderer_main_platform_delegate_fuchsia.cc" ]
+    sources += [
+      "media/codec_factory_fuchsia.cc",
+      "media/codec_factory_fuchsia.h",
+      "renderer_main_platform_delegate_fuchsia.cc",
+    ]
     public_deps += [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.mediacodec" ]
 
     deps += [
diff --git a/content/renderer/media/codec_factory.cc b/content/renderer/media/codec_factory.cc
new file mode 100644
index 0000000..65939e76
--- /dev/null
+++ b/content/renderer/media/codec_factory.cc
@@ -0,0 +1,224 @@
+// 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.
+
+#include "content/renderer/media/codec_factory.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/decoder.h"
+#include "media/base/media_log.h"
+#include "media/base/overlay_info.h"
+#include "media/base/supported_video_decoder_config.h"
+#include "media/mojo/clients/mojo_video_encode_accelerator.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+
+namespace content {
+
+CodecFactory::CodecFactory(
+    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+    bool video_decode_accelerator_enabled,
+    bool video_encode_accelerator_enabled,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        pending_vea_provider_remote)
+    : media_task_runner_(std::move(media_task_runner)),
+      context_provider_(std::move(context_provider)),
+      video_decode_accelerator_enabled_(video_decode_accelerator_enabled),
+      video_encode_accelerator_enabled_(video_encode_accelerator_enabled) {
+  media_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CodecFactory::BindOnTaskRunner, base::Unretained(this),
+                     std::move(pending_vea_provider_remote)));
+}
+CodecFactory::~CodecFactory() = default;
+
+std::unique_ptr<media::VideoEncodeAccelerator>
+CodecFactory::CreateVideoEncodeAccelerator() {
+  DCHECK(video_encode_accelerator_enabled_);
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(vea_provider_.is_bound());
+
+  base::AutoLock lock(supported_profiles_lock_);
+  // When |supported_vea_profiles_| is empty, no hw encoder is available or
+  // we have not yet gotten the supported profiles.
+  if (!supported_vea_profiles_) {
+    DVLOG(2) << "VEA's profiles have not yet been gotten";
+  } else if (supported_vea_profiles_->empty()) {
+    // There is no profile supported by VEA.
+    return nullptr;
+  }
+
+  mojo::PendingRemote<media::mojom::VideoEncodeAccelerator> vea;
+  vea_provider_->CreateVideoEncodeAccelerator(
+      vea.InitWithNewPipeAndPassReceiver());
+
+  if (!vea) {
+    return nullptr;
+  }
+
+  return base::WrapUnique<media::VideoEncodeAccelerator>(
+      new media::MojoVideoEncodeAccelerator(std::move(vea)));
+}
+
+media::VideoDecoderType CodecFactory::GetVideoDecoderType() {
+  base::AutoLock lock(supported_profiles_lock_);
+  return video_decoder_type_;
+}
+
+absl::optional<media::SupportedVideoDecoderConfigs>
+CodecFactory::GetSupportedVideoDecoderConfigs() {
+  base::AutoLock lock(supported_profiles_lock_);
+  return supported_decoder_configs_;
+}
+
+absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
+CodecFactory::GetVideoEncodeAcceleratorSupportedProfiles() {
+  base::AutoLock lock(supported_profiles_lock_);
+  return supported_vea_profiles_;
+}
+
+bool CodecFactory::IsDecoderSupportKnown() {
+  base::AutoLock lock(supported_profiles_lock_);
+  return decoder_support_notifier_.is_notified();
+}
+
+bool CodecFactory::IsEncoderSupportKnown() {
+  base::AutoLock lock(supported_profiles_lock_);
+  return encoder_support_notifier_.is_notified();
+}
+
+void CodecFactory::NotifyDecoderSupportKnown(base::OnceClosure callback) {
+  base::AutoLock lock(supported_profiles_lock_);
+  decoder_support_notifier_.Register(
+      media::BindToCurrentLoop(std::move(callback)));
+}
+
+void CodecFactory::NotifyEncoderSupportKnown(base::OnceClosure callback) {
+  base::AutoLock lock(supported_profiles_lock_);
+  encoder_support_notifier_.Register(
+      media::BindToCurrentLoop(std::move(callback)));
+}
+
+CodecFactory::Notifier::Notifier() = default;
+CodecFactory::Notifier::~Notifier() = default;
+
+void CodecFactory::Notifier::Register(base::OnceClosure callback) {
+  if (is_notified_) {
+    std::move(callback).Run();
+    return;
+  }
+  callbacks_.push_back(std::move(callback));
+}
+
+void CodecFactory::Notifier::Notify() {
+  DCHECK(!is_notified_);
+  is_notified_ = true;
+  while (!callbacks_.empty()) {
+    std::move(callbacks_.back()).Run();
+    callbacks_.pop_back();
+  }
+}
+
+void CodecFactory::OnDecoderSupportFailed() {
+  base::AutoLock lock(supported_profiles_lock_);
+  if (decoder_support_notifier_.is_notified()) {
+    return;
+  }
+  supported_decoder_configs_ = media::SupportedVideoDecoderConfigs();
+  decoder_support_notifier_.Notify();
+}
+
+void CodecFactory::OnGetSupportedDecoderConfigs() {
+  base::AutoLock lock(supported_profiles_lock_);
+  decoder_support_notifier_.Notify();
+}
+
+void CodecFactory::OnEncoderSupportFailed() {
+  base::AutoLock lock(supported_profiles_lock_);
+  if (encoder_support_notifier_.is_notified()) {
+    return;
+  }
+  supported_vea_profiles_ = media::VideoEncodeAccelerator::SupportedProfiles();
+  encoder_support_notifier_.Notify();
+}
+
+void CodecFactory::OnGetVideoEncodeAcceleratorSupportedProfiles(
+    const media::VideoEncodeAccelerator::SupportedProfiles&
+        supported_profiles) {
+  base::AutoLock lock(supported_profiles_lock_);
+  supported_vea_profiles_ = supported_profiles;
+  encoder_support_notifier_.Notify();
+}
+
+void CodecFactory::BindOnTaskRunner(
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        pending_vea_provider_remote) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(context_provider_);
+
+  vea_provider_.Bind(std::move(pending_vea_provider_remote));
+
+  if (context_provider_->BindToCurrentSequence() !=
+      gpu::ContextResult::kSuccess) {
+    OnDecoderSupportFailed();
+    OnEncoderSupportFailed();
+    return;
+  }
+
+  if (video_encode_accelerator_enabled_) {
+    // The remote might be disconnected if the encoding process crashes, for
+    // example a GPU driver failure. Set a disconnect handler to watch these
+    // types of failures and treat them as if there are no supported encoder
+    // profiles.
+    // Unretained is safe since CodecFactory is never destroyed.
+    // It lives until the process shuts down.
+    vea_provider_.set_disconnect_handler(base::BindOnce(
+        &CodecFactory::OnEncoderSupportFailed, base::Unretained(this)));
+    vea_provider_->GetVideoEncodeAcceleratorSupportedProfiles(base::BindOnce(
+        &CodecFactory::OnGetVideoEncodeAcceleratorSupportedProfiles,
+        base::Unretained(this)));
+  } else {
+    OnEncoderSupportFailed();
+  }
+
+  if (!video_decode_accelerator_enabled_) {
+    OnDecoderSupportFailed();
+  }
+}
+
+CodecFactoryDefault::CodecFactoryDefault(
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+    bool video_decode_accelerator_enabled,
+    bool video_encode_accelerator_enabled,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        pending_vea_provider_remote)
+    : CodecFactory(std::move(task_runner),
+                   std::move(context_provider),
+                   video_decode_accelerator_enabled,
+                   video_encode_accelerator_enabled,
+                   std::move(pending_vea_provider_remote)) {
+  // There is no decoder provider.
+  OnDecoderSupportFailed();
+}
+
+CodecFactoryDefault::~CodecFactoryDefault() = default;
+
+std::unique_ptr<media::VideoDecoder> CodecFactoryDefault::CreateVideoDecoder(
+    media::GpuVideoAcceleratorFactories* gpu_factories,
+    media::MediaLog* media_log,
+    media::RequestOverlayInfoCB request_overlay_info_cb,
+    const gfx::ColorSpace& rendering_color_space) {
+  NOTIMPLEMENTED()
+      << "CodecFactoryDefault does not have a provider to create a "
+         "hardware video decoder.";
+  return nullptr;
+}
+
+}  // namespace content
diff --git a/content/renderer/media/codec_factory.h b/content/renderer/media/codec_factory.h
new file mode 100644
index 0000000..55e1374
--- /dev/null
+++ b/content/renderer/media/codec_factory.h
@@ -0,0 +1,196 @@
+// 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 CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
+#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
+
+#include <memory>
+
+#include "base/functional/callback_forward.h"
+#include "content/common/content_export.h"
+#include "media/base/decoder.h"
+#include "media/base/media_log.h"
+#include "media/base/overlay_info.h"
+#include "media/base/supported_video_decoder_config.h"
+#include "media/base/video_decoder.h"
+#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
+
+namespace content {
+
+// Assists to GpuVideoAcceleratorFactoriesImpl on hardware decoder and encoder
+// functionalities.
+//
+// It is a base class that handles the encoder resources
+// via media::mojom::VideoEncodeAcceleratorProvider. Its derived classes need
+// to implement how to connect to hardware decoder resources.
+class CONTENT_EXPORT CodecFactory {
+ public:
+  // `media_task_runner` - task runner for running multi-media operations.
+  // `context_provider` - context provider for creating a video decoder.
+  // `video_decode_accelerator_enabled` - whether the video decode accelerator
+  //    is enabled.
+  // `video_encode_accelerator_enabled` - whether the video encode accelerator
+  //    is enabled.
+  // `pending_vea_provider_remote` - bound pending
+  //    media::mojom::VideoEncodeAcceleratorProvider remote.
+  CodecFactory(
+      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool video_decode_accelerator_enabled,
+      bool video_encode_accelerator_enabled,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          pending_vea_provider_remote);
+
+  CodecFactory(const CodecFactory&) = delete;
+  CodecFactory& operator=(const CodecFactory&) = delete;
+  virtual ~CodecFactory();
+
+  // `gpu_factories` - pointer to the GpuVideoAcceleratorFactories that
+  //    owns |this|.
+  // `media_log` - process-wide pointer to log to chrome://media-internals log.
+  // `request_overlay_info_cb` - callback that gets the overlay information.
+  // `rendering_color_space` - color space for the purpose of color conversion.
+  //
+  // Derived class should construct its own type of video decoder.
+  virtual std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
+      media::GpuVideoAcceleratorFactories* gpu_factories,
+      media::MediaLog* media_log,
+      media::RequestOverlayInfoCB request_overlay_info_cb,
+      const gfx::ColorSpace& rendering_color_space) = 0;
+
+  std::unique_ptr<media::VideoEncodeAccelerator> CreateVideoEncodeAccelerator();
+
+  // Returns VideoDecoderType::kUnknown in cases where IsDecoderSupportKnown()
+  // is false.
+  // Otherwise, it returns the type of decoder that provided the
+  // configs for the config support check.
+  media::VideoDecoderType GetVideoDecoderType();
+
+  // Returns a nullopt if we have not yet gotten the configs.
+  // Returns an optional that contains an empty vector if we have gotten the
+  // result and there are no supported configs.
+  absl::optional<media::SupportedVideoDecoderConfigs>
+  GetSupportedVideoDecoderConfigs();
+
+  // Returns a nullopt if we have not yet gotten the profiles.
+  // Returns an optional that contains an empty vector if we have gotten the
+  // result and there are no supported profiles.
+  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
+  GetVideoEncodeAcceleratorSupportedProfiles();
+
+  // Returns true if media::SupportedVideoDecoderConfigs are populated.
+  bool IsDecoderSupportKnown();
+
+  // Returns true if media::VideoEncodeAccelerator::SupportedProfiles are
+  // populated.
+  bool IsEncoderSupportKnown();
+
+  // If the current decoder support is not yet known, use this to register a
+  // callback that is notified once the support is known. At that point, calling
+  // GetSupportedVideoDecoderConfigs will give the set of supported decoder
+  // configs.
+  //
+  // There is no way to unsubscribe a callback, it is recommended to use a
+  // WeakPtr if you need this feature.
+  void NotifyDecoderSupportKnown(base::OnceClosure callback);
+
+  // If the current encoder support is not yet known, use this to register a
+  // callback that is notified once the support is known. At that point, calling
+  // GetVideoEncodeAcceleratorSupportedProfiles will give the set of supported
+  // encoder profiles.
+  //
+  // There is no way to unsubscribe a callback, it is recommended to use a
+  // WeakPtr if you need this feature.
+  void NotifyEncoderSupportKnown(base::OnceClosure callback);
+
+ protected:
+  class Notifier {
+   public:
+    Notifier();
+    ~Notifier();
+
+    void Register(base::OnceClosure callback);
+    void Notify();
+
+    bool is_notified() { return is_notified_; }
+
+   private:
+    bool is_notified_ = false;
+    std::vector<base::OnceClosure> callbacks_;
+  };
+
+  void OnDecoderSupportFailed();
+  void OnGetSupportedDecoderConfigs();
+
+  void OnEncoderSupportFailed();
+  void OnGetVideoEncodeAcceleratorSupportedProfiles(
+      const media::VideoEncodeAccelerator::SupportedProfiles&
+          supported_profiles);
+
+  // Task runner on the Media thread for running multi-media operations
+  // (e.g., creating a video decoder).
+  // In Fuchsia, it needs to be started with the IO message pump for FIDL calls.
+  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
+
+  // Shared pointer to a shared context provider. All access should happen only
+  // on the media thread.
+  const scoped_refptr<viz::ContextProviderCommandBuffer> context_provider_;
+
+  // Whether video acceleration encoding/decoding should be enabled.
+  const bool video_decode_accelerator_enabled_;
+  const bool video_encode_accelerator_enabled_;
+
+  base::Lock supported_profiles_lock_;
+
+  // If the Optional is empty, then we have not yet gotten the configs.
+  // If the Optional contains an empty vector, then we have gotten the result
+  // and there are no supported configs.
+  absl::optional<media::SupportedVideoDecoderConfigs> supported_decoder_configs_
+      GUARDED_BY(supported_profiles_lock_);
+  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
+      supported_vea_profiles_ GUARDED_BY(supported_profiles_lock_);
+
+  media::VideoDecoderType video_decoder_type_
+      GUARDED_BY(supported_profiles_lock_) = media::VideoDecoderType::kUnknown;
+
+  Notifier decoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
+  Notifier encoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
+
+ private:
+  void BindOnTaskRunner(
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          pending_vea_provider_remote);
+
+  mojo::Remote<media::mojom::VideoEncodeAcceleratorProvider> vea_provider_;
+};
+
+// CodecFactoryDefault is the default derived class, which has no
+// decoder provider. It does not have any supported video decoder configs and
+// returns a null pointer when creating a hardware video decoder.
+class CodecFactoryDefault final : public CodecFactory {
+ public:
+  CodecFactoryDefault(
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool video_decode_accelerator_enabled,
+      bool video_encode_accelerator_enabled,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          pending_vea_provider_remote);
+  ~CodecFactoryDefault() override;
+
+  // Returns nullptr since there is no decoder provider.
+  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
+      media::GpuVideoAcceleratorFactories* gpu_factories,
+      media::MediaLog* media_log,
+      media::RequestOverlayInfoCB request_overlay_info_cb,
+      const gfx::ColorSpace& rendering_color_space) override;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
diff --git a/content/renderer/media/codec_factory_fuchsia.cc b/content/renderer/media/codec_factory_fuchsia.cc
new file mode 100644
index 0000000..c8e6d2b
--- /dev/null
+++ b/content/renderer/media/codec_factory_fuchsia.cc
@@ -0,0 +1,92 @@
+// 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.
+
+#include "content/renderer/media/codec_factory_fuchsia.h"
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/synchronization/lock.h"
+#include "content/renderer/media/codec_factory.h"
+#include "media/base/decoder.h"
+#include "media/base/overlay_info.h"
+#include "media/base/supported_video_decoder_config.h"
+#include "media/base/video_decoder.h"
+#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
+#include "media/fuchsia/video/fuchsia_video_decoder.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+
+namespace content {
+
+CodecFactoryFuchsia::CodecFactoryFuchsia(
+    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+    bool video_decode_accelerator_enabled,
+    bool video_encode_accelerator_enabled,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        pending_vea_provider_remote,
+    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+        pending_media_codec_provider_remote)
+    : CodecFactory(std::move(media_task_runner),
+                   std::move(context_provider),
+                   video_decode_accelerator_enabled,
+                   video_encode_accelerator_enabled,
+                   std::move(pending_vea_provider_remote)) {
+  media_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CodecFactoryFuchsia::BindOnTaskRunner,
+                     base::Unretained(this),
+                     std::move(pending_media_codec_provider_remote)));
+}
+CodecFactoryFuchsia::~CodecFactoryFuchsia() = default;
+
+std::unique_ptr<media::VideoDecoder> CodecFactoryFuchsia::CreateVideoDecoder(
+    media::GpuVideoAcceleratorFactories* gpu_factories,
+    media::MediaLog* media_log,
+    media::RequestOverlayInfoCB request_overlay_info_cb,
+    const gfx::ColorSpace& rendering_color_space) {
+  DCHECK(video_decode_accelerator_enabled_);
+
+  return std::make_unique<media::FuchsiaVideoDecoder>(context_provider_,
+                                                      media_codec_provider_,
+                                                      /*allow_overlays=*/true);
+}
+
+void CodecFactoryFuchsia::BindOnTaskRunner(
+    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+        media_codec_provider_remote) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+  if (!video_decode_accelerator_enabled_) {
+    OnDecoderSupportFailed();
+    return;
+  }
+
+  media_codec_provider_.Bind(std::move(media_codec_provider_remote),
+                             media_task_runner_);
+  // The remote might be disconnected if the decoding process crashes, for
+  // example a decoder driver failure. Set a disconnect handler to watch these
+  // types of failures and treat them as if there are no supported decoder
+  // configs.
+  // Unretained is safe since CodecFactory is never destroyed.
+  // It lives until the process shuts down.
+  media_codec_provider_.set_disconnect_handler(
+      base::BindOnce(&CodecFactoryFuchsia::OnDecoderSupportFailed,
+                     base::Unretained(this)),
+      media_task_runner_);
+  media_codec_provider_->GetSupportedVideoDecoderConfigs(
+      base::BindOnce(&CodecFactoryFuchsia::OnGetSupportedDecoderConfigs,
+                     base::Unretained(this)));
+}
+
+void CodecFactoryFuchsia::OnGetSupportedDecoderConfigs(
+    const media::SupportedVideoDecoderConfigs& supported_configs) {
+  {
+    base::AutoLock lock(supported_profiles_lock_);
+    supported_decoder_configs_.emplace(supported_configs);
+    video_decoder_type_ = media::VideoDecoderType::kFuchsia;
+  }
+  CodecFactory::OnGetSupportedDecoderConfigs();
+}
+
+}  // namespace content
diff --git a/content/renderer/media/codec_factory_fuchsia.h b/content/renderer/media/codec_factory_fuchsia.h
new file mode 100644
index 0000000..743c203
--- /dev/null
+++ b/content/renderer/media/codec_factory_fuchsia.h
@@ -0,0 +1,57 @@
+// 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 CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
+#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
+
+#include "content/common/content_export.h"
+#include "content/renderer/media/codec_factory.h"
+#include "media/base/overlay_info.h"
+#include "media/base/video_decoder.h"
+#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+#include "mojo/public/cpp/bindings/shared_remote.h"
+
+namespace content {
+
+// CodecFactoryFuchsia gets hardware decoder resources
+// via media::mojom::FuchsiaMediaCodecProvider.
+//
+// Codec-related services on Fuchsia are used directly from the renderer process
+// after the browser process provides a connection to the FIDL protocol via
+// media::mojom::FuchsiaMediaCodecProvider. This can improve performance by
+// avoiding the need to hop through the browser process.
+class CONTENT_EXPORT CodecFactoryFuchsia final : public CodecFactory {
+ public:
+  CodecFactoryFuchsia(
+      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool video_decode_accelerator_enabled,
+      bool video_encode_accelerator_enabled,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          pending_vea_provider_remote,
+      mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+          pending_media_codec_provider_remote);
+  ~CodecFactoryFuchsia() override;
+
+  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
+      media::GpuVideoAcceleratorFactories* gpu_factories,
+      media::MediaLog* media_log,
+      media::RequestOverlayInfoCB request_overlay_info_cb,
+      const gfx::ColorSpace& rendering_color_space) override;
+
+ private:
+  void BindOnTaskRunner(
+      mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+          media_codec_provider_remote);
+  void OnGetSupportedDecoderConfigs(
+      const media::SupportedVideoDecoderConfigs& supported_configs);
+
+  mojo::SharedRemote<media::mojom::FuchsiaMediaCodecProvider>
+      media_codec_provider_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
diff --git a/content/renderer/media/codec_factory_mojo.cc b/content/renderer/media/codec_factory_mojo.cc
new file mode 100644
index 0000000..411d3ab
--- /dev/null
+++ b/content/renderer/media/codec_factory_mojo.cc
@@ -0,0 +1,98 @@
+// 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.
+
+#include "content/renderer/media/codec_factory_mojo.h"
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/synchronization/lock.h"
+#include "content/renderer/media/codec_factory.h"
+#include "media/base/overlay_info.h"
+#include "media/mojo/clients/mojo_video_decoder.h"
+#include "media/mojo/mojom/interface_factory.mojom.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace content {
+
+CodecFactoryMojo::CodecFactoryMojo(
+    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+    bool video_decode_accelerator_enabled,
+    bool video_encode_accelerator_enabled,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        pending_vea_provider_remote,
+    mojo::PendingRemote<media::mojom::InterfaceFactory>
+        pending_interface_factory_remote)
+    : CodecFactory(std::move(media_task_runner),
+                   std::move(context_provider),
+                   video_decode_accelerator_enabled,
+                   video_encode_accelerator_enabled,
+                   std::move(pending_vea_provider_remote)) {
+  media_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&CodecFactoryMojo::BindOnTaskRunner,
+                                base::Unretained(this),
+                                std::move(pending_interface_factory_remote)));
+}
+CodecFactoryMojo::~CodecFactoryMojo() = default;
+
+std::unique_ptr<media::VideoDecoder> CodecFactoryMojo::CreateVideoDecoder(
+    media::GpuVideoAcceleratorFactories* gpu_factories,
+    media::MediaLog* media_log,
+    media::RequestOverlayInfoCB request_overlay_info_cb,
+    const gfx::ColorSpace& rendering_color_space) {
+  DCHECK(video_decode_accelerator_enabled_);
+  DCHECK(interface_factory_.is_bound());
+
+  mojo::PendingRemote<media::mojom::VideoDecoder> video_decoder;
+  interface_factory_->CreateVideoDecoder(
+      video_decoder.InitWithNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
+  return std::make_unique<media::MojoVideoDecoder>(
+      media_task_runner_, gpu_factories, media_log, std::move(video_decoder),
+      std::move(request_overlay_info_cb), rendering_color_space);
+}
+
+void CodecFactoryMojo::BindOnTaskRunner(
+    mojo::PendingRemote<media::mojom::InterfaceFactory>
+        interface_factory_remote) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(context_provider_);
+
+  interface_factory_.Bind(std::move(interface_factory_remote));
+
+  if (!video_decode_accelerator_enabled_) {
+    OnDecoderSupportFailed();
+    return;
+  }
+
+  // Note: This is a bit of a hack, since we don't specify the implementation
+  // before asking for the map of supported configs.  We do this because it
+  // (a) saves an ipc call, and (b) makes the return of those configs atomic.
+  interface_factory_->CreateVideoDecoder(
+      video_decoder_.BindNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
+  // The remote might be disconnected if the decoding process crashes, for
+  // example a GPU driver failure. Set a disconnect handler to watch these
+  // types of failures and treat them as if there are no supported decoder
+  // configs.
+  // Unretained is safe since CodecFactory is never destroyed.
+  // It lives until the process shuts down.
+  video_decoder_.set_disconnect_handler(base::BindOnce(
+      &CodecFactoryMojo::OnDecoderSupportFailed, base::Unretained(this)));
+  video_decoder_->GetSupportedConfigs(base::BindOnce(
+      &CodecFactoryMojo::OnGetSupportedDecoderConfigs, base::Unretained(this)));
+}
+
+void CodecFactoryMojo::OnGetSupportedDecoderConfigs(
+    const media::SupportedVideoDecoderConfigs& supported_configs,
+    media::VideoDecoderType decoder_type) {
+  {
+    base::AutoLock lock(supported_profiles_lock_);
+    video_decoder_.reset();
+    supported_decoder_configs_.emplace(supported_configs);
+    video_decoder_type_ = decoder_type;
+  }
+  CodecFactory::OnGetSupportedDecoderConfigs();
+}
+
+}  // namespace content
diff --git a/content/renderer/media/codec_factory_mojo.h b/content/renderer/media/codec_factory_mojo.h
new file mode 100644
index 0000000..ff371a6
--- /dev/null
+++ b/content/renderer/media/codec_factory_mojo.h
@@ -0,0 +1,56 @@
+// 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 CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
+#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
+
+#include <memory>
+
+#include "content/common/content_export.h"
+#include "content/renderer/media/codec_factory.h"
+#include "media/base/decoder.h"
+#include "media/base/overlay_info.h"
+#include "media/base/video_decoder.h"
+#include "media/mojo/mojom/interface_factory.mojom.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace content {
+
+// CodecFactoryMojo gets hardware decoder resources
+// via media::mojom::InterfaceFactory. Use it when mojo-based video decoder is
+// enabled.
+class CONTENT_EXPORT CodecFactoryMojo final : public CodecFactory {
+ public:
+  CodecFactoryMojo(
+      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool video_decode_accelerator_enabled,
+      bool video_encode_accelerator_enabled,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          pending_vea_provider_remote,
+      mojo::PendingRemote<media::mojom::InterfaceFactory>
+          pending_interface_factory_remote);
+  ~CodecFactoryMojo() override;
+
+  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
+      media::GpuVideoAcceleratorFactories* gpu_factories,
+      media::MediaLog* media_log,
+      media::RequestOverlayInfoCB request_overlay_info_cb,
+      const gfx::ColorSpace& rendering_color_space) override;
+
+ private:
+  void BindOnTaskRunner(
+      mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory);
+  void OnGetSupportedDecoderConfigs(
+      const media::SupportedVideoDecoderConfigs& supported_configs,
+      media::VideoDecoderType decoder_type);
+
+  mojo::Remote<media::mojom::InterfaceFactory> interface_factory_;
+  mojo::Remote<media::mojom::VideoDecoder> video_decoder_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
index d2bfea3..c6022f1 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
@@ -6,8 +6,10 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#include <memory>
+#include <optional>
 
-#include "base/bind.h"
+#include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_macros.h"
@@ -15,22 +17,15 @@
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "components/viz/common/gpu/context_provider.h"
-#include "content/child/child_thread_impl.h"
-#include "content/public/common/content_features.h"
+#include "content/renderer/media/codec_factory.h"
 #include "content/renderer/render_thread_impl.h"
-#include "gpu/command_buffer/client/context_support.h"
-#include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/ipc/client/command_buffer_proxy_impl.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
-#include "gpu/ipc/common/gpu_memory_buffer_support.h"
-#include "media/base/bind_to_current_loop.h"
-#include "media/gpu/gpu_video_accelerator_util.h"
+#include "media/base/decoder.h"
+#include "media/base/supported_video_decoder_config.h"
 #include "media/mojo/buildflags.h"
-#include "media/mojo/clients/mojo_video_decoder.h"
-#include "media/mojo/clients/mojo_video_encode_accelerator.h"
 #include "media/video/video_encode_accelerator.h"
 #include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
 #include "third_party/skia/include/core/SkTypes.h"
@@ -53,26 +48,6 @@
 
 }  // namespace
 
-GpuVideoAcceleratorFactoriesImpl::Notifier::Notifier() = default;
-GpuVideoAcceleratorFactoriesImpl::Notifier::~Notifier() = default;
-
-void GpuVideoAcceleratorFactoriesImpl::Notifier::Register(
-    base::OnceClosure callback) {
-  if (is_notified_) {
-    std::move(callback).Run();
-    return;
-  }
-  callbacks_.push_back(std::move(callback));
-}
-
-void GpuVideoAcceleratorFactoriesImpl::Notifier::Notify() {
-  DCHECK(!is_notified_);
-  is_notified_ = true;
-  for (auto& callback : callbacks_)
-    std::move(callback).Run();
-  callbacks_.clear();
-}
-
 // static
 std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
 GpuVideoAcceleratorFactoriesImpl::Create(
@@ -80,22 +55,40 @@
     const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+    std::unique_ptr<CodecFactory> codec_factory,
     bool enable_video_gpu_memory_buffers,
     bool enable_media_stream_gpu_memory_buffers,
     bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator,
-    mojo::PendingRemote<media::mojom::InterfaceFactory>
-        interface_factory_remote,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        vea_provider_remote) {
+    bool enable_video_encode_accelerator) {
   RecordContextProviderPhaseUmaEnum(
       ContextProviderPhase::CONTEXT_PROVIDER_ACQUIRED);
   return base::WrapUnique(new GpuVideoAcceleratorFactoriesImpl(
       std::move(gpu_channel_host), main_thread_task_runner, task_runner,
-      context_provider, enable_video_gpu_memory_buffers,
+      std::move(context_provider), std::move(codec_factory),
+      RenderThreadImpl::current()->GetGpuMemoryBufferManager(),
+      enable_video_gpu_memory_buffers, enable_media_stream_gpu_memory_buffers,
+      enable_video_decode_accelerator, enable_video_encode_accelerator));
+}
+
+// static
+std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
+GpuVideoAcceleratorFactoriesImpl::CreateForTesting(
+    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
+    const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
+    const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+    const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+    std::unique_ptr<CodecFactory> codec_factory,
+    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+    bool enable_video_gpu_memory_buffers,
+    bool enable_media_stream_gpu_memory_buffers,
+    bool enable_video_decode_accelerator,
+    bool enable_video_encode_accelerator) {
+  return base::WrapUnique(new GpuVideoAcceleratorFactoriesImpl(
+      std::move(gpu_channel_host), main_thread_task_runner, task_runner,
+      std::move(context_provider), std::move(codec_factory),
+      std::move(gpu_memory_buffer_manager), enable_video_gpu_memory_buffers,
       enable_media_stream_gpu_memory_buffers, enable_video_decode_accelerator,
-      enable_video_encode_accelerator, std::move(interface_factory_remote),
-      std::move(vea_provider_remote)));
+      enable_video_encode_accelerator));
 }
 
 GpuVideoAcceleratorFactoriesImpl::GpuVideoAcceleratorFactoriesImpl(
@@ -103,53 +96,40 @@
     const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+    std::unique_ptr<CodecFactory> codec_factory,
+    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
     bool enable_video_gpu_memory_buffers,
     bool enable_media_stream_gpu_memory_buffers,
     bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator,
-    mojo::PendingRemote<media::mojom::InterfaceFactory>
-        interface_factory_remote,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        vea_provider_remote)
+    bool enable_video_encode_accelerator)
     : main_thread_task_runner_(main_thread_task_runner),
       task_runner_(task_runner),
       gpu_channel_host_(std::move(gpu_channel_host)),
+      codec_factory_(std::move(codec_factory)),
       context_provider_(context_provider),
       enable_video_gpu_memory_buffers_(enable_video_gpu_memory_buffers),
       enable_media_stream_gpu_memory_buffers_(
           enable_media_stream_gpu_memory_buffers),
       video_decode_accelerator_enabled_(enable_video_decode_accelerator),
       video_encode_accelerator_enabled_(enable_video_encode_accelerator),
-      gpu_memory_buffer_manager_(
-          RenderThreadImpl::current()->GetGpuMemoryBufferManager()) {
+      gpu_memory_buffer_manager_(gpu_memory_buffer_manager) {
   DCHECK(main_thread_task_runner_);
   DCHECK(gpu_channel_host_);
 
   task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner,
-                     base::Unretained(this),
-                     std::move(interface_factory_remote),
-                     std::move(vea_provider_remote)));
+                     base::Unretained(this)));
 }
 
 GpuVideoAcceleratorFactoriesImpl::~GpuVideoAcceleratorFactoriesImpl() {}
 
-void GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner(
-    mojo::PendingRemote<media::mojom::InterfaceFactory>
-        interface_factory_remote,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        vea_provider_remote) {
+void GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   DCHECK(context_provider_);
 
-  interface_factory_.Bind(std::move(interface_factory_remote));
-  vea_provider_.Bind(std::move(vea_provider_remote));
-
   if (context_provider_->BindToCurrentSequence() !=
       gpu::ContextResult::kSuccess) {
-    OnDecoderSupportFailed();
-    OnEncoderSupportFailed();
     OnContextLost();
     return;
   }
@@ -160,98 +140,24 @@
   context_provider_->GetCommandBufferProxy()->GetGpuChannel().GetChannelToken(
       base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::OnChannelTokenReady,
                      base::Unretained(this)));
-
-  if (video_encode_accelerator_enabled_) {
-    vea_provider_.set_disconnect_handler(base::BindOnce(
-        &GpuVideoAcceleratorFactoriesImpl::OnEncoderSupportFailed,
-        base::Unretained(this)));
-    vea_provider_->GetVideoEncodeAcceleratorSupportedProfiles(
-        base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::
-                           OnGetVideoEncodeAcceleratorSupportedProfiles,
-                       base::Unretained(this)));
-  } else {
-    OnEncoderSupportFailed();
-  }
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-  if (video_decode_accelerator_enabled_) {
-    // Note: This is a bit of a hack, since we don't specify the implementation
-    // before asking for the map of supported configs.  We do this because it
-    // (a) saves an ipc call, and (b) makes the return of those configs atomic.
-    interface_factory_->CreateVideoDecoder(
-        video_decoder_.BindNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
-    video_decoder_.set_disconnect_handler(base::BindOnce(
-        &GpuVideoAcceleratorFactoriesImpl::OnDecoderSupportFailed,
-        base::Unretained(this)));
-    video_decoder_->GetSupportedConfigs(base::BindOnce(
-        &GpuVideoAcceleratorFactoriesImpl::OnSupportedDecoderConfigs,
-        base::Unretained(this)));
-  } else {
-    OnDecoderSupportFailed();
-  }
-#else
-  OnDecoderSupportFailed();
-#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::IsDecoderSupportKnown() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return decoder_support_notifier_.is_notified();
+  return codec_factory_->IsDecoderSupportKnown();
 }
 
 void GpuVideoAcceleratorFactoriesImpl::NotifyDecoderSupportKnown(
     base::OnceClosure callback) {
-  base::AutoLock lock(supported_profiles_lock_);
-  decoder_support_notifier_.Register(
-      media::BindToCurrentLoop(std::move(callback)));
-}
-
-void GpuVideoAcceleratorFactoriesImpl::OnSupportedDecoderConfigs(
-    const media::SupportedVideoDecoderConfigs& supported_configs,
-    media::VideoDecoderType decoder_type) {
-  base::AutoLock lock(supported_profiles_lock_);
-  video_decoder_.reset();
-  supported_decoder_configs_ = supported_configs;
-  video_decoder_type_ = decoder_type;
-  decoder_support_notifier_.Notify();
-}
-
-void GpuVideoAcceleratorFactoriesImpl::OnDecoderSupportFailed() {
-  base::AutoLock lock(supported_profiles_lock_);
-  video_decoder_.reset();
-  if (decoder_support_notifier_.is_notified())
-    return;
-  supported_decoder_configs_ = media::SupportedVideoDecoderConfigs();
-  decoder_support_notifier_.Notify();
+  codec_factory_->NotifyDecoderSupportKnown(std::move(callback));
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::IsEncoderSupportKnown() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return encoder_support_notifier_.is_notified();
+  return codec_factory_->IsEncoderSupportKnown();
 }
 
 void GpuVideoAcceleratorFactoriesImpl::NotifyEncoderSupportKnown(
     base::OnceClosure callback) {
-  base::AutoLock lock(supported_profiles_lock_);
-  encoder_support_notifier_.Register(
-      media::BindToCurrentLoop(std::move(callback)));
-}
-
-void GpuVideoAcceleratorFactoriesImpl::
-    OnGetVideoEncodeAcceleratorSupportedProfiles(
-        const media::VideoEncodeAccelerator::SupportedProfiles&
-            supported_profiles) {
-  base::AutoLock lock(supported_profiles_lock_);
-  supported_vea_profiles_ = supported_profiles;
-  encoder_support_notifier_.Notify();
-}
-
-void GpuVideoAcceleratorFactoriesImpl::OnEncoderSupportFailed() {
-  base::AutoLock lock(supported_profiles_lock_);
-  if (encoder_support_notifier_.is_notified())
-    return;
-  supported_vea_profiles_ = media::VideoEncodeAccelerator::SupportedProfiles();
-  encoder_support_notifier_.Notify();
+  codec_factory_->NotifyEncoderSupportKnown(std::move(callback));
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::CheckContextLost() {
@@ -330,16 +236,14 @@
     return Supported::kFalse;
   }
 
-  base::AutoLock lock(supported_profiles_lock_);
-
-  // If GetSupportedConfigs() has not completed (or was never started), report
-  // that all configs are supported. Clients will find out that configs are not
-  // supported when VideoDecoder::Initialize() fails.
-  if (!supported_decoder_configs_)
+  auto supported_decoder_configs =
+      codec_factory_->GetSupportedVideoDecoderConfigs();
+  if (!supported_decoder_configs) {
     return Supported::kUnknown;
+  }
 
   // Iterate over the supported configs.
-  for (const auto& supported : *supported_decoder_configs_) {
+  for (const auto& supported : *supported_decoder_configs) {
     if (supported.Matches(config))
       return Supported::kTrue;
   }
@@ -347,8 +251,7 @@
 }
 
 media::VideoDecoderType GpuVideoAcceleratorFactoriesImpl::GetDecoderType() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return video_decoder_type_;
+  return codec_factory_->GetVideoDecoderType();
 }
 
 std::unique_ptr<media::VideoDecoder>
@@ -357,50 +260,21 @@
     media::RequestOverlayInfoCB request_overlay_info_cb) {
   DCHECK(video_decode_accelerator_enabled_);
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(interface_factory_.is_bound());
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
   if (CheckContextLost())
     return nullptr;
 
-  mojo::PendingRemote<media::mojom::VideoDecoder> video_decoder;
-  interface_factory_->CreateVideoDecoder(
-      video_decoder.InitWithNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
-  return std::make_unique<media::MojoVideoDecoder>(
-      task_runner_, this, media_log, std::move(video_decoder),
-      std::move(request_overlay_info_cb), rendering_color_space_);
-#else
-  return nullptr;
-#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+  return codec_factory_->CreateVideoDecoder(
+      this, media_log, request_overlay_info_cb, rendering_color_space_);
 }
 
 std::unique_ptr<media::VideoEncodeAccelerator>
 GpuVideoAcceleratorFactoriesImpl::CreateVideoEncodeAccelerator() {
   DCHECK(video_encode_accelerator_enabled_);
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(vea_provider_.is_bound());
   if (CheckContextLost())
     return nullptr;
 
-  base::AutoLock lock(supported_profiles_lock_);
-  // When |supported_vea_profiles_| is empty, no hw encoder is available or
-  // we have not yet gotten the supported profiles.
-  if (!supported_vea_profiles_) {
-    DVLOG(2) << "VEA's profiles have not yet been gotten";
-  } else if (supported_vea_profiles_->empty()) {
-    // There is no profile supported by VEA.
-    return nullptr;
-  }
-
-  mojo::PendingRemote<media::mojom::VideoEncodeAccelerator> vea;
-  vea_provider_->CreateVideoEncodeAccelerator(
-      vea.InitWithNewPipeAndPassReceiver());
-
-  if (!vea)
-    return nullptr;
-
-  return std::unique_ptr<media::VideoEncodeAccelerator>(
-      new media::MojoVideoEncodeAccelerator(std::move(vea)));
+  return codec_factory_->CreateVideoEncodeAccelerator();
 }
 
 std::unique_ptr<gfx::GpuMemoryBuffer>
@@ -522,8 +396,7 @@
 
 absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
 GpuVideoAcceleratorFactoriesImpl::GetVideoEncodeAcceleratorSupportedProfiles() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return supported_vea_profiles_;
+  return codec_factory_->GetVideoEncodeAcceleratorSupportedProfiles();
 }
 
 viz::RasterContextProvider*
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
index 99bd984..bf8aa6e 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
@@ -12,20 +12,19 @@
 #include <vector>
 
 #include "base/callback_list.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/unguessable_token.h"
+#include "build/build_config.h"
 #include "components/viz/common/gpu/context_lost_observer.h"
-#include "media/base/supported_video_decoder_config.h"
-#include "media/mojo/mojom/interface_factory.mojom.h"
-#include "media/mojo/mojom/video_decoder.mojom.h"
-#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
+#include "content/common/content_export.h"
+#include "content/renderer/media/codec_factory.h"
+#include "media/mojo/buildflags.h"
 #include "media/video/gpu_video_accelerator_factories.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/gfx/geometry/size.h"
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include <fuchsia/mediacodec/cpp/fidl.h>
+#endif
 
 namespace base {
 class SequencedTaskRunner;
@@ -52,7 +51,7 @@
 // the |task_runner_|, as provided during construction.
 // |context_provider| should not support locking and will be bound to
 // |task_runner_| where all the operations on the context should also happen.
-class GpuVideoAcceleratorFactoriesImpl
+class CONTENT_EXPORT GpuVideoAcceleratorFactoriesImpl
     : public media::GpuVideoAcceleratorFactories,
       public viz::ContextLostObserver {
  public:
@@ -63,14 +62,22 @@
       const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
       const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+      std::unique_ptr<CodecFactory> codec_factory,
       bool enable_video_gpu_memory_buffers,
       bool enable_media_stream_gpu_memory_buffers,
       bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator,
-      mojo::PendingRemote<media::mojom::InterfaceFactory>
-          interface_factory_remote,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          vea_provider_remote);
+      bool enable_video_encode_accelerator);
+  static std::unique_ptr<GpuVideoAcceleratorFactoriesImpl> CreateForTesting(
+      scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
+      const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
+      const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+      const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+      std::unique_ptr<CodecFactory> codec_factory,
+      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+      bool enable_video_gpu_memory_buffers,
+      bool enable_media_stream_gpu_memory_buffers,
+      bool enable_video_decode_accelerator,
+      bool enable_video_encode_accelerator);
 
   // media::GpuVideoAcceleratorFactories implementation.
   bool IsGpuVideoDecodeAcceleratorEnabled() override;
@@ -140,60 +147,32 @@
   ~GpuVideoAcceleratorFactoriesImpl() override;
 
  private:
-  class Notifier {
-   public:
-    Notifier();
-    ~Notifier();
-
-    void Register(base::OnceClosure callback);
-    void Notify();
-
-    bool is_notified() { return is_notified_; }
-
-   private:
-    bool is_notified_ = false;
-    std::vector<base::OnceClosure> callbacks_;
-  };
-
   GpuVideoAcceleratorFactoriesImpl(
       scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
       const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
       const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
+      std::unique_ptr<CodecFactory> codec_factory,
+      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
       bool enable_gpu_memory_buffer_video_frames_for_video,
       bool enable_gpu_memory_buffer_video_frames_for_media_stream,
       bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator,
-      mojo::PendingRemote<media::mojom::InterfaceFactory>
-          interface_factory_remote,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          vea_provider_remote);
+      bool enable_video_encode_accelerator);
 
-  void BindOnTaskRunner(
-      mojo::PendingRemote<media::mojom::InterfaceFactory>
-          interface_factory_remote,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          vea_provider_remote);
+  void BindOnTaskRunner();
 
   // viz::ContextLostObserver implementation.
   void OnContextLost() override;
   void SetContextProviderLostOnMainThread();
 
-  void OnSupportedDecoderConfigs(
-      const media::SupportedVideoDecoderConfigs& supported_configs,
-      media::VideoDecoderType decoder_type);
-  void OnDecoderSupportFailed();
-
-  void OnGetVideoEncodeAcceleratorSupportedProfiles(
-      const media::VideoEncodeAccelerator::SupportedProfiles&
-          supported_profiles);
-  void OnEncoderSupportFailed();
   void OnChannelTokenReady(const base::UnguessableToken& token);
 
   const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   const scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
 
+  const std::unique_ptr<CodecFactory> codec_factory_;
+
   // Shared pointer to a shared context provider. It is initially set on main
   // thread, but all subsequent access and destruction should happen only on the
   // media thread.
@@ -218,27 +197,6 @@
   gfx::ColorSpace rendering_color_space_;
 
   gpu::GpuMemoryBufferManager* const gpu_memory_buffer_manager_;
-
-  mojo::Remote<media::mojom::InterfaceFactory> interface_factory_;
-  mojo::Remote<media::mojom::VideoEncodeAcceleratorProvider> vea_provider_;
-
-  // SupportedDecoderConfigs state.
-  mojo::Remote<media::mojom::VideoDecoder> video_decoder_;
-
-  base::Lock supported_profiles_lock_;
-
-  // If the Optional is empty, then we have not yet gotten the configs.  If the
-  // Optional contains an empty vector, then we have gotten the result and there
-  // are no supported configs.
-  absl::optional<media::SupportedVideoDecoderConfigs> supported_decoder_configs_
-      GUARDED_BY(supported_profiles_lock_);
-  media::VideoDecoderType video_decoder_type_
-      GUARDED_BY(supported_profiles_lock_) = media::VideoDecoderType::kUnknown;
-  Notifier decoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
-
-  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
-      supported_vea_profiles_ GUARDED_BY(supported_profiles_lock_);
-  Notifier encoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
 };
 
 }  // namespace content
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
new file mode 100644
index 0000000..5b70f84
--- /dev/null
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
@@ -0,0 +1,685 @@
+// 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.
+
+#include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
+
+#include <GLES2/gl2.h>
+#include <cstddef>
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "build/build_config.h"
+#include "components/viz/common/gpu/context_cache_controller.h"
+#include "components/viz/common/gpu/context_lost_observer.h"
+#include "components/viz/test/test_gpu_memory_buffer_manager.h"
+#include "content/public/common/gpu_stream_constants.h"
+#include "content/renderer/media/codec_factory.h"
+#include "gpu/command_buffer/client/gles2_interface_stub.h"
+#include "gpu/command_buffer/common/capabilities.h"
+#include "gpu/command_buffer/common/context_creation_attribs.h"
+#include "gpu/command_buffer/common/context_result.h"
+#include "gpu/config/gpu_feature_info.h"
+#include "gpu/ipc/client/command_buffer_proxy_impl.h"
+#include "gpu/ipc/client/gpu_channel_host.h"
+#include "gpu/ipc/common/gpu_channel.mojom.h"
+#include "gpu/ipc/common/mock_gpu_channel.h"
+#include "media/base/decoder.h"
+#include "media/base/media_util.h"
+#include "media/base/supported_video_decoder_config.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_decoder_config.h"
+#include "media/mojo/buildflags.h"
+#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-death-test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkImage.h"
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+#include "content/renderer/media/codec_factory_mojo.h"
+#include "media/filters/fake_video_decoder.h"
+#include "media/mojo/mojom/interface_factory.mojom.h"
+#include "media/mojo/services/mojo_cdm_service_context.h"
+#include "media/mojo/services/mojo_media_client.h"
+#include "media/mojo/services/mojo_video_decoder_service.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
+#endif
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include "content/renderer/media/codec_factory_fuchsia.h"
+#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
+#endif
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+namespace content {
+
+namespace {
+
+constexpr gfx::Size kCodedSize(320, 240);
+constexpr gfx::Rect kVisibleRect(320, 240);
+constexpr gfx::Size kNaturalSize(320, 240);
+
+const media::SupportedVideoDecoderConfig kH264MaxSupportedVideoDecoderConfig =
+    media::SupportedVideoDecoderConfig(
+        media::VideoCodecProfile::H264PROFILE_MIN,
+        media::VideoCodecProfile::H264PROFILE_MAX,
+        media::kDefaultSwDecodeSizeMin,
+        media::kDefaultSwDecodeSizeMax,
+        true,
+        false);
+
+const media::VideoDecoderConfig kH264BaseConfig(
+    media::VideoCodec::kH264,
+    media::H264PROFILE_MIN,
+    media::VideoDecoderConfig::AlphaMode::kIsOpaque,
+    media::VideoColorSpace(),
+    media::kNoTransformation,
+    kCodedSize,
+    kVisibleRect,
+    kNaturalSize,
+    media::EmptyExtraData(),
+    media::EncryptionScheme::kUnencrypted);
+
+const media::VideoDecoderConfig kVP9BaseConfig(
+    media::VideoCodec::kVP9,
+    media::VP9PROFILE_MIN,
+    media::VideoDecoderConfig::AlphaMode::kIsOpaque,
+    media::VideoColorSpace(),
+    media::kNoTransformation,
+    kCodedSize,
+    kVisibleRect,
+    kNaturalSize,
+    media::EmptyExtraData(),
+    media::EncryptionScheme::kUnencrypted);
+
+}  // namespace
+
+class TestGpuChannelHost : public gpu::GpuChannelHost {
+ public:
+  explicit TestGpuChannelHost(gpu::mojom::GpuChannel& gpu_channel)
+      : GpuChannelHost(0 /* channel_id */,
+                       gpu::GPUInfo(),
+                       gpu::GpuFeatureInfo(),
+                       mojo::ScopedMessagePipeHandle(
+                           mojo::MessagePipeHandle(mojo::kInvalidHandleValue))),
+        gpu_channel_(gpu_channel) {}
+
+  gpu::mojom::GpuChannel& GetGpuChannel() override { return *gpu_channel_; }
+
+ protected:
+  ~TestGpuChannelHost() override = default;
+  const raw_ref<gpu::mojom::GpuChannel> gpu_channel_;
+};
+
+class MockOverlayInfoCbHandler {
+ public:
+  MOCK_METHOD2(Call, void(bool, media::ProvideOverlayInfoCB));
+};
+
+class MockContextProviderCommandBuffer
+    : public viz::ContextProviderCommandBuffer {
+ public:
+  MockContextProviderCommandBuffer(
+      scoped_refptr<gpu::GpuChannelHost> channel,
+      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager)
+      : viz::ContextProviderCommandBuffer(
+            std::move(channel),
+            gpu_memory_buffer_manager,
+            content::kGpuStreamIdDefault,
+            content::kGpuStreamPriorityDefault,
+            gpu::kNullSurfaceHandle,
+            GURL(),
+            false,
+            false,
+            true,
+            gpu::SharedMemoryLimits(),
+            gpu::ContextCreationAttribs(),
+            viz::command_buffer_metrics::ContextType::FOR_TESTING) {}
+
+  MOCK_METHOD(gpu::CommandBufferProxyImpl*,
+              GetCommandBufferProxy,
+              (),
+              (override));
+
+  // ContextProvider / RasterContextProvider implementation.
+  MOCK_METHOD(gpu::ContextResult, BindToCurrentSequence, (), (override));
+  MOCK_METHOD(gpu::gles2::GLES2Interface*, ContextGL, (), (override));
+  MOCK_METHOD(gpu::raster::RasterInterface*, RasterInterface, (), (override));
+  MOCK_METHOD(gpu::ContextSupport*, ContextSupport, (), (override));
+  MOCK_METHOD(class GrDirectContext*, GrContext, (), (override));
+  MOCK_METHOD(gpu::SharedImageInterface*, SharedImageInterface, (), (override));
+  MOCK_METHOD(viz::ContextCacheController*, CacheController, (), (override));
+  MOCK_METHOD(base::Lock*, GetLock, (), (override));
+  MOCK_METHOD(gpu::Capabilities&, ContextCapabilities, (), (const, override));
+  MOCK_METHOD(gpu::GpuFeatureInfo&, GetGpuFeatureInfo, (), (const, override));
+  MOCK_METHOD(void, AddObserver, (viz::ContextLostObserver*), (override));
+  MOCK_METHOD(void, RemoveObserver, (viz::ContextLostObserver*), (override));
+
+  // base::trace_event::MemoryDumpProvider implementation.
+  MOCK_METHOD(bool,
+              OnMemoryDump,
+              (const base::trace_event::MemoryDumpArgs&,
+               base::trace_event::ProcessMemoryDump*),
+              (override));
+
+ protected:
+  ~MockContextProviderCommandBuffer() override = default;
+};
+
+class MockGLESInterface : public gpu::gles2::GLES2InterfaceStub {
+ public:
+  MOCK_METHOD(GLenum, GetGraphicsResetStatusKHR, ());
+};
+
+class FakeVEAProviderImpl
+    : public media::mojom::VideoEncodeAcceleratorProvider {
+ public:
+  ~FakeVEAProviderImpl() override = default;
+
+  void Bind(mojo::PendingReceiver<media::mojom::VideoEncodeAcceleratorProvider>
+                receiver) {
+    receiver_.Bind(std::move(receiver));
+  }
+
+  void SetVideoEncodeAcceleratorSupportedProfiles(
+      std::vector<media::VideoEncodeAccelerator::SupportedProfile>
+          supported_profile) {
+    supported_profile_ = supported_profile;
+  }
+  // media::mojom::VideoEncodeAcceleratorProvider impl.
+  void CreateVideoEncodeAccelerator(
+      mojo::PendingReceiver<media::mojom::VideoEncodeAccelerator> receiver)
+      override {}
+  void GetVideoEncodeAcceleratorSupportedProfiles(
+      GetVideoEncodeAcceleratorSupportedProfilesCallback callback) override {
+    std::move(callback).Run(supported_profile_);
+  }
+
+ private:
+  mojo::Receiver<media::mojom::VideoEncodeAcceleratorProvider> receiver_{this};
+  std::vector<media::VideoEncodeAccelerator::SupportedProfile>
+      supported_profile_;
+};
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+// Client to MojoVideoDecoderService vended by FakeInterfaceFactory. Creates a
+// FakeGpuVideoDecoder when requested.
+class FakeMojoMediaClient : public media::MojoMediaClient {
+ public:
+  void SetSupportedVideoDecoderConfigs(
+      media::SupportedVideoDecoderConfigs configs) {
+    supported_video_decoder_configs_ = configs;
+  }
+
+  // MojoMediaClient implementation.
+  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+      media::MediaLog* media_log,
+      media::mojom::CommandBufferIdPtr command_buffer_id,
+      media::RequestOverlayInfoCB request_overlay_info_cb,
+      const gfx::ColorSpace& target_color_space,
+      mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>
+          oop_video_decoder) override {
+    return std::make_unique<media::FakeVideoDecoder>(
+        0 /* decoder_id */, 0 /* decoding_delay */,
+        13 /* max_parallel_decoding_requests */, media::BytesDecodedCB());
+  }
+  media::SupportedVideoDecoderConfigs GetSupportedVideoDecoderConfigs()
+      override {
+    return supported_video_decoder_configs_;
+  }
+  media::VideoDecoderType GetDecoderImplementationType() override {
+    return media::VideoDecoderType::kTesting;
+  }
+
+ private:
+  media::SupportedVideoDecoderConfigs supported_video_decoder_configs_;
+};
+
+// Other end of remote InterfaceFactory requested by VideoDecoderBroker. Used
+// to create our (fake) media::mojom::VideoDecoder.
+class FakeInterfaceFactory : public media::mojom::InterfaceFactory {
+ public:
+  FakeInterfaceFactory() = default;
+  ~FakeInterfaceFactory() override = default;
+
+  void Bind(mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) {
+    receiver_.Bind(std::move(receiver));
+    receiver_.set_disconnect_handler(base::BindOnce(
+        &FakeInterfaceFactory::OnConnectionError, base::Unretained(this)));
+  }
+
+  void SetSupportedVideoDecoderConfigs(
+      media::SupportedVideoDecoderConfigs configs) {
+    mojo_media_client_.SetSupportedVideoDecoderConfigs(configs);
+  }
+
+  // Implement this one interface from mojom::InterfaceFactory. Using the real
+  // MojoVideoDecoderService allows us to reuse buffer conversion code. The
+  // FakeMojoMediaClient will create a FakeGpuVideoDecoder.
+  void CreateVideoDecoder(
+      mojo::PendingReceiver<media::mojom::VideoDecoder> receiver,
+      mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>
+          dst_video_decoder) override {
+    video_decoder_receivers_.Add(
+        std::make_unique<media::MojoVideoDecoderService>(
+            &mojo_media_client_, &cdm_service_context_,
+            mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>()),
+        std::move(receiver));
+  }
+
+  // Stub out other mojom::InterfaceFactory interfaces.
+  void CreateAudioDecoder(
+      mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) override {}
+  void CreateAudioEncoder(
+      mojo::PendingReceiver<media::mojom::AudioEncoder> receiver) override {}
+  void CreateDefaultRenderer(
+      const std::string& audio_device_id,
+      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
+#if BUILDFLAG(ENABLE_CAST_RENDERER)
+  void CreateCastRenderer(
+      const base::UnguessableToken& overlay_plane_id,
+      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
+#endif
+#if BUILDFLAG(IS_ANDROID)
+  void CreateFlingingRenderer(
+      const std::string& presentation_id,
+      mojo::PendingRemote<media::mojom::FlingingRendererClientExtension>
+          client_extension,
+      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
+  void CreateMediaPlayerRenderer(
+      mojo::PendingRemote<media::mojom::MediaPlayerRendererClientExtension>
+          client_extension_remote,
+      mojo::PendingReceiver<media::mojom::Renderer> receiver,
+      mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension>
+          renderer_extension_receiver) override {}
+#endif  // BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_WIN)
+  void CreateMediaFoundationRenderer(
+      mojo::PendingRemote<media::mojom::MediaLog> media_log_remote,
+      mojo::PendingReceiver<media::mojom::Renderer> receiver,
+      mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension>
+          renderer_extension_receiver,
+      mojo::PendingRemote<media::mojom::MediaFoundationRendererClientExtension>
+          client_extension_remote) override {}
+#endif  // BUILDFLAG(IS_WIN)
+  void CreateCdm(const media::CdmConfig& cdm_config,
+                 CreateCdmCallback callback) override {}
+
+ private:
+  void OnConnectionError() { receiver_.reset(); }
+
+  media::MojoCdmServiceContext cdm_service_context_;
+  FakeMojoMediaClient mojo_media_client_;
+  mojo::Receiver<media::mojom::InterfaceFactory> receiver_{this};
+  mojo::UniqueReceiverSet<media::mojom::VideoDecoder> video_decoder_receivers_;
+};
+#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+
+#if BUILDFLAG(IS_FUCHSIA)
+class FakeFuchsiaMediaCodecProvide
+    : public media::mojom::FuchsiaMediaCodecProvider {
+ public:
+  ~FakeFuchsiaMediaCodecProvide() override = default;
+
+  void Bind(
+      mojo::PendingReceiver<media::mojom::FuchsiaMediaCodecProvider> receiver) {
+    receiver_.Bind(std::move(receiver));
+  }
+
+  void SetSupportedVideoDecoderConfigs(
+      media::SupportedVideoDecoderConfigs configs) {
+    supported_video_decoder_configs_ = configs;
+  }
+
+  // media::mojom::FuchsiaMediaCodecProvider implementation.
+  void CreateVideoDecoder(
+      media::VideoCodec codec,
+      media::mojom::VideoDecoderSecureMemoryMode secure_mode,
+      fidl::InterfaceRequest<fuchsia::media::StreamProcessor>
+          stream_processor_request) final {
+    ADD_FAILURE() << "Not implemented.";
+  }
+
+  void GetSupportedVideoDecoderConfigs(
+      GetSupportedVideoDecoderConfigsCallback callback) final {
+    std::move(callback).Run(supported_video_decoder_configs_);
+  }
+
+ private:
+  media::SupportedVideoDecoderConfigs supported_video_decoder_configs_;
+  mojo::Receiver<media::mojom::FuchsiaMediaCodecProvider> receiver_{this};
+};
+#endif  // BUILDFLAG(IS_FUCHSIA)
+
+class GpuVideoAcceleratorFactoriesImplTest : public testing::Test {
+ public:
+  GpuVideoAcceleratorFactoriesImplTest()
+      : gpu_channel_host_(
+            base::MakeRefCounted<TestGpuChannelHost>(mock_gpu_channel_)),
+        mock_context_provider_(
+            base::MakeRefCounted<NiceMock<MockContextProviderCommandBuffer>>(
+                gpu_channel_host_,
+                &gpu_memory_buffer_manager_)) {}
+  ~GpuVideoAcceleratorFactoriesImplTest() override = default;
+
+  void SetUp() override {
+    MockGpuChannel();
+    MockContextProvider();
+  }
+
+  void TearDown() override {
+    task_environment_.RunUntilIdle();
+    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_provider_));
+    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_gl_));
+    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_gpu_channel_));
+    delete gpu_command_buffer_proxy_;
+    mock_context_provider_.reset();
+    gpu_channel_host_.reset();
+  }
+
+  void MockGpuChannel() {
+    // Simulate success, since we're not actually talking to the service
+    // in this test suite.
+    ON_CALL(mock_gpu_channel_, CreateCommandBuffer(_, _, _, _, _, _, _))
+        .WillByDefault(Invoke(
+            [&](gpu::mojom::CreateCommandBufferParamsPtr params,
+                int32_t routing_id, base::UnsafeSharedMemoryRegion shared_state,
+                mojo::PendingAssociatedReceiver<gpu::mojom::CommandBuffer>
+                    receiver,
+                mojo::PendingAssociatedRemote<gpu::mojom::CommandBufferClient>
+                    client,
+                gpu::ContextResult* result,
+                gpu::Capabilities* capabilities) -> bool {
+              // There's no real GpuChannel pipe for this endpoint to use, so
+              // associate it with a dedicated pipe for these tests. This
+              // allows the CommandBufferProxyImpl to make calls on its
+              // CommandBuffer endpoint.
+              receiver.EnableUnassociatedUsage();
+              *result = gpu::ContextResult::kSuccess;
+              return true;
+            }));
+  }
+
+  void MockContextProvider() {
+    ON_CALL(*mock_context_provider_, BindToCurrentSequence())
+        .WillByDefault(Return(gpu::ContextResult::kSuccess));
+    ON_CALL(mock_context_gl_, GetGraphicsResetStatusKHR())
+        .WillByDefault(Return(GL_NO_ERROR));
+    ON_CALL(*mock_context_provider_, ContextGL())
+        .WillByDefault(Return(&mock_context_gl_));
+
+    gpu_command_buffer_proxy_ = new gpu::CommandBufferProxyImpl(
+        gpu_channel_host_, &gpu_memory_buffer_manager_,
+        content::kGpuStreamIdDefault,
+        task_environment_.GetMainThreadTaskRunner());
+    gpu_command_buffer_proxy_->Initialize(
+        gpu::kNullSurfaceHandle, nullptr, content::kGpuStreamPriorityDefault,
+        gpu::ContextCreationAttribs(), GURL());
+    ON_CALL(*mock_context_provider_, GetCommandBufferProxy())
+        .WillByDefault(Return(gpu_command_buffer_proxy_));
+  }
+
+  std::unique_ptr<CodecFactory> CreateCodecFactory(
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool enable_video_decode_accelerator,
+      bool enable_video_encode_accelerator) {
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        vea_provider;
+    fake_vea_provider_.Bind(vea_provider.InitWithNewPipeAndPassReceiver());
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+    mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
+    fake_media_codec_provider_.Bind(
+        interface_factory.InitWithNewPipeAndPassReceiver());
+    return std::make_unique<CodecFactoryMojo>(
+        task_environment_.GetMainThreadTaskRunner(),
+        std::move(context_provider), enable_video_decode_accelerator,
+        enable_video_encode_accelerator, std::move(vea_provider),
+        std::move(interface_factory));
+#elif BUILDFLAG(IS_FUCHSIA)
+    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+        media_codec_provider;
+    fake_media_codec_provider_.Bind(
+        media_codec_provider.InitWithNewPipeAndPassReceiver());
+    return std::make_unique<CodecFactoryFuchsia>(
+        task_environment_.GetMainThreadTaskRunner(),
+        std::move(context_provider), enable_video_decode_accelerator,
+        enable_video_encode_accelerator, std::move(vea_provider),
+        std::move(media_codec_provider));
+#else
+    return std::make_unique<CodecFactoryDefault>(
+        task_environment_.GetMainThreadTaskRunner(),
+        std::move(context_provider), enable_video_decode_accelerator,
+        enable_video_encode_accelerator, std::move(vea_provider));
+#endif
+  }
+
+  std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
+  CreateGpuVideoAcceleratorFactories(bool enable_video_decode_accelerator,
+                                     bool enable_video_encode_accelerator) {
+    std::unique_ptr<CodecFactory> codec_factory = CreateCodecFactory(
+        mock_context_provider_, enable_video_decode_accelerator,
+        enable_video_encode_accelerator);
+    auto gpu_factories = GpuVideoAcceleratorFactoriesImpl::CreateForTesting(
+        gpu_channel_host_, task_environment_.GetMainThreadTaskRunner(),
+        task_environment_.GetMainThreadTaskRunner(), mock_context_provider_,
+        std::move(codec_factory), &gpu_memory_buffer_manager_,
+        true, /* enable_video_gpu_memory_buffers */
+        true, /* enable_media_stream_gpu_memory_buffers */
+        enable_video_decode_accelerator, enable_video_encode_accelerator);
+
+    // Wait until all async IO messages (e.g. Mojo and FIDL) to be delieved
+    // and handled.
+    task_environment_.RunUntilIdle();
+
+    return gpu_factories;
+  }
+
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
+
+  NiceMock<gpu::MockGpuChannel> mock_gpu_channel_;
+  NiceMock<MockGLESInterface> mock_context_gl_;
+  viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
+  scoped_refptr<TestGpuChannelHost> gpu_channel_host_;
+  scoped_refptr<MockContextProviderCommandBuffer> mock_context_provider_;
+  gpu::CommandBufferProxyImpl* gpu_command_buffer_proxy_;
+
+  FakeVEAProviderImpl fake_vea_provider_;
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+  FakeInterfaceFactory fake_media_codec_provider_;
+#elif BUILDFLAG(IS_FUCHSIA)
+  FakeFuchsiaMediaCodecProvide fake_media_codec_provider_;
+#endif
+};
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, VideoDecoderAcceleratorDisabled) {
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, false);
+
+  EXPECT_FALSE(
+      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
+  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
+                kH264BaseConfig),
+            media::GpuVideoAcceleratorFactories::Supported::kFalse);
+  EXPECT_EQ(gpu_video_accelerator_factories->GetDecoderType(),
+            media::VideoDecoderType::kUnknown);
+
+  ASSERT_TRUE(
+      testing::Mock::VerifyAndClearExpectations(&mock_context_provider_));
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, VideoEncoderAcceleratorDisabled) {
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, false);
+
+  EXPECT_FALSE(
+      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, EncoderConfigsIsSupported) {
+  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles(
+      {media::VideoEncodeAccelerator::SupportedProfile(
+          media::VideoCodecProfile::VP9PROFILE_MAX, kCodedSize)});
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, true);
+
+  EXPECT_TRUE(
+      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
+  base::test::TestFuture<void> future;
+  gpu_video_accelerator_factories->NotifyEncoderSupportKnown(
+      future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  auto supported_profiles = gpu_video_accelerator_factories
+                                ->GetVideoEncodeAcceleratorSupportedProfiles();
+  EXPECT_TRUE(supported_profiles.has_value());
+  EXPECT_EQ(supported_profiles->size(), static_cast<size_t>(1));
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, EncoderConfigsIsNotSupported) {
+  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles({});
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, true);
+
+  EXPECT_TRUE(
+      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
+  base::test::TestFuture<void> future;
+  gpu_video_accelerator_factories->NotifyEncoderSupportKnown(
+      future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  auto supported_profiles = gpu_video_accelerator_factories
+                                ->GetVideoEncodeAcceleratorSupportedProfiles();
+  EXPECT_TRUE(supported_profiles.has_value());
+  EXPECT_TRUE(supported_profiles->empty());
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, CreateVideoEncodeAccelerator) {
+  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles(
+      {media::VideoEncodeAccelerator::SupportedProfile(
+          media::VideoCodecProfile::VP9PROFILE_MAX, kCodedSize)});
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, true);
+
+  EXPECT_NE(gpu_video_accelerator_factories->CreateVideoEncodeAccelerator(),
+            nullptr);
+}
+
+#ifdef GTEST_HAS_DEATH_TEST
+using GpuVideoAcceleratorFactoriesImplDeathTest =
+    GpuVideoAcceleratorFactoriesImplTest;
+
+TEST_F(GpuVideoAcceleratorFactoriesImplDeathTest,
+       CreateVideoEncodeAcceleratorFailed) {
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, false);
+
+  EXPECT_DCHECK_DEATH({
+    EXPECT_EQ(gpu_video_accelerator_factories->CreateVideoEncodeAccelerator(),
+              nullptr);
+  });
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplDeathTest, CreateVideoDecoderFailed) {
+  testing::StrictMock<MockOverlayInfoCbHandler> cb_handler;
+  media::RequestOverlayInfoCB mock_cb = base::BindRepeating(
+      &MockOverlayInfoCbHandler::Call, base::Unretained(&cb_handler));
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, false);
+
+  EXPECT_DCHECK_DEATH({
+    EXPECT_EQ(gpu_video_accelerator_factories->CreateVideoDecoder(
+                  nullptr, std::move(mock_cb)),
+              nullptr);
+  });
+}
+#endif  // GTEST_HAS_DEATH_TEST
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) || BUILDFLAG(IS_FUCHSIA)
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, DecoderConfigIsSupported) {
+  fake_media_codec_provider_.SetSupportedVideoDecoderConfigs(
+      {kH264MaxSupportedVideoDecoderConfig});
+
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(true, false);
+
+  EXPECT_TRUE(
+      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
+  base::test::TestFuture<void> future;
+  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
+      future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
+                kH264BaseConfig),
+            media::GpuVideoAcceleratorFactories::Supported::kTrue);
+  EXPECT_NE(gpu_video_accelerator_factories->GetDecoderType(),
+            media::VideoDecoderType::kUnknown);
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, DecoderConfigIsNotSupported) {
+  fake_media_codec_provider_.SetSupportedVideoDecoderConfigs(
+      {kH264MaxSupportedVideoDecoderConfig});
+
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(true, false);
+
+  EXPECT_TRUE(
+      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
+  base::test::TestFuture<void> future;
+  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
+      future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  EXPECT_EQ(
+      gpu_video_accelerator_factories->IsDecoderConfigSupported(kVP9BaseConfig),
+      media::GpuVideoAcceleratorFactories::Supported::kFalse);
+}
+
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, CreateVideoDecoder) {
+  testing::StrictMock<MockOverlayInfoCbHandler> cb_handler;
+  media::RequestOverlayInfoCB mock_cb = base::BindRepeating(
+      &MockOverlayInfoCbHandler::Call, base::Unretained(&cb_handler));
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(true, false);
+
+  EXPECT_NE(gpu_video_accelerator_factories->CreateVideoDecoder(
+                nullptr, std::move(mock_cb)),
+            nullptr);
+}
+#else
+TEST_F(GpuVideoAcceleratorFactoriesImplTest, DefaultCodecFactory) {
+  auto gpu_video_accelerator_factories =
+      CreateGpuVideoAcceleratorFactories(false, false);
+
+  EXPECT_FALSE(
+      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
+  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
+  base::test::TestFuture<void> future;
+  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
+      future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
+                kH264BaseConfig),
+            media::GpuVideoAcceleratorFactories::Supported::kFalse);
+}
+#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) || BUILDFLAG(IS_FUCHSIA)
+
+}  // namespace content
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 1ab2dbae..8820b82 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -75,6 +75,7 @@
 #include "content/common/main_frame_counter.h"
 #include "content/common/process_visibility_tracker.h"
 #include "content/common/pseudonymization_salt.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/content_constants.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_paths.h"
@@ -87,6 +88,7 @@
 #include "content/renderer/agent_scheduling_group.h"
 #include "content/renderer/browser_exposed_renderer_interfaces.h"
 #include "content/renderer/effective_connection_type_helper.h"
+#include "content/renderer/media/codec_factory.h"
 #include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
 #include "content/renderer/media/media_factory.h"
 #include "content/renderer/media/render_media_client.h"
@@ -120,6 +122,7 @@
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "mojo/public/cpp/bindings/binder_map.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "net/base/net_errors.h"
@@ -201,6 +204,16 @@
 #include <malloc.h>
 #endif
 
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+#include "content/renderer/media/codec_factory_mojo.h"
+#include "media/mojo/mojom/interface_factory.mojom.h"
+#endif
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include "content/renderer/media/codec_factory_fuchsia.h"
+#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
+#endif
+
 #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX)
 #include "base/test/clang_profiling.h"
 #endif
@@ -1047,7 +1060,6 @@
        gpu::kGpuFeatureStatusEnabled);
 
   const bool enable_video_encode_accelerator =
-
 #if BUILDFLAG(IS_LINUX)
       base::FeatureList::IsEnabled(media::kVaapiVideoEncodeLinux) &&
 #else
@@ -1076,31 +1088,17 @@
        gpu_channel_host->gpu_info().overlay_info.supports_overlays);
 #endif  // BUILDFLAG(IS_WIN)
 
-  mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
-  BindHostReceiver(interface_factory.InitWithNewPipeAndPassReceiver());
-
-  mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-      vea_provider;
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-  if (base::FeatureList::IsEnabled(media::kUseOutOfProcessVideoEncoding)) {
-    BindHostReceiver(vea_provider.InitWithNewPipeAndPassReceiver());
-  } else {
-    gpu_->CreateVideoEncodeAcceleratorProvider(
-        vea_provider.InitWithNewPipeAndPassReceiver());
-  }
-#else
-  gpu_->CreateVideoEncodeAcceleratorProvider(
-      vea_provider.InitWithNewPipeAndPassReceiver());
-#endif
-
+  auto codec_factory = CreateMediaCodecFactory(media_context_provider,
+                                               enable_video_decode_accelerator,
+                                               enable_video_encode_accelerator);
   gpu_factories_.push_back(GpuVideoAcceleratorFactoriesImpl::Create(
       std::move(gpu_channel_host),
       base::SingleThreadTaskRunner::GetCurrentDefault(),
       GetMediaSequencedTaskRunner(), std::move(media_context_provider),
-      enable_video_gpu_memory_buffers, enable_media_stream_gpu_memory_buffers,
-      enable_video_decode_accelerator, enable_video_encode_accelerator,
-      std::move(interface_factory), std::move(vea_provider)));
+      std::move(codec_factory), enable_video_gpu_memory_buffers,
+      enable_media_stream_gpu_memory_buffers, enable_video_decode_accelerator,
+      enable_video_encode_accelerator));
+
   gpu_factories_.back()->SetRenderingColorSpace(rendering_color_space_);
   return gpu_factories_.back().get();
 }
@@ -1839,4 +1837,45 @@
   attribution_os_support_ = attribution_os_support;
 }
 
+std::unique_ptr<CodecFactory> RenderThreadImpl::CreateMediaCodecFactory(
+    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+    bool enable_video_decode_accelerator,
+    bool enable_video_encode_accelerator) {
+  mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+      vea_provider;
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(media::kUseOutOfProcessVideoEncoding)) {
+    BindHostReceiver(vea_provider.InitWithNewPipeAndPassReceiver());
+  } else {
+    gpu_->CreateVideoEncodeAcceleratorProvider(
+        vea_provider.InitWithNewPipeAndPassReceiver());
+  }
+#else
+  gpu_->CreateVideoEncodeAcceleratorProvider(
+      vea_provider.InitWithNewPipeAndPassReceiver());
+#endif
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+  mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
+  BindHostReceiver(interface_factory.InitWithNewPipeAndPassReceiver());
+  return std::make_unique<CodecFactoryMojo>(
+      GetMediaSequencedTaskRunner(), context_provider,
+      enable_video_decode_accelerator, enable_video_encode_accelerator,
+      std::move(vea_provider), std::move(interface_factory));
+#elif BUILDFLAG(IS_FUCHSIA)
+  mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
+      media_codec_provider;
+  BindHostReceiver(media_codec_provider.InitWithNewPipeAndPassReceiver());
+  return std::make_unique<CodecFactoryFuchsia>(
+      GetMediaSequencedTaskRunner(), context_provider,
+      enable_video_decode_accelerator, enable_video_encode_accelerator,
+      std::move(vea_provider), std::move(media_codec_provider));
+#else
+  return std::make_unique<CodecFactoryDefault>(
+      GetMediaSequencedTaskRunner(), context_provider,
+      enable_video_decode_accelerator, enable_video_encode_accelerator,
+      std::move(vea_provider));
+#endif
+}
+
 }  // namespace content
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index d3dc1320..d4cbe57 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -40,6 +40,7 @@
 #include "content/common/shared_storage_worklet_service.mojom.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/discardable_memory_utils.h"
+#include "content/renderer/media/codec_factory.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "ipc/ipc_sync_channel.h"
 #include "media/media_buildflags.h"
@@ -467,6 +468,11 @@
   void OnRendererInterfaceReceiver(
       mojo::PendingAssociatedReceiver<mojom::Renderer> receiver);
 
+  std::unique_ptr<CodecFactory> CreateMediaCodecFactory(
+      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
+      bool enable_video_decode_accelerator,
+      bool enable_video_encode_accelerator);
+
   scoped_refptr<discardable_memory::ClientDiscardableSharedMemoryManager>
       discardable_memory_allocator_;
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 02e3f16..928f2a63 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2585,6 +2585,7 @@
     "../renderer/accessibility/ax_image_stopwords_unittest.cc",
     "../renderer/content_security_policy_util_unittest.cc",
     "../renderer/media/batching_media_log_unittest.cc",
+    "../renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc",
     "../renderer/media/inspector_media_event_handler_unittest.cc",
     "../renderer/media/renderer_webaudiodevice_impl_unittest.cc",
     "../renderer/render_thread_impl_unittest.cc",
@@ -2754,6 +2755,7 @@
     "//gin",
     "//gpu",
     "//gpu:test_support",
+    "//gpu/ipc/common:test_support",
     "//gpu/ipc/host",
     "//gpu/ipc/service",
     "//ipc:test_support",
@@ -2785,6 +2787,7 @@
     "//services/video_capture/public/cpp:mocks",
     "//services/video_capture/public/mojom",
     "//services/video_capture/public/mojom:constants",
+    "//services/viz/public/cpp/gpu",
     "//skia",
     "//sql",
     "//sql:test_support",
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 7780b113..b61d6454 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -300,6 +300,19 @@
   return specialized_request;
 }
 
+CtapGetAssertionOptions SpecializeOptionsForAuthenticator(
+    const CtapGetAssertionOptions& options,
+    const FidoAuthenticator& authenticator) {
+  CtapGetAssertionOptions specialized_options(options);
+
+  if (!options.prf_inputs.empty() &&
+      !authenticator.SupportsHMACSecretExtension()) {
+    specialized_options.prf_inputs.clear();
+  }
+
+  return specialized_options;
+}
+
 }  // namespace
 
 GetAssertionRequestHandler::GetAssertionRequestHandler(
@@ -408,6 +421,8 @@
 
   CtapGetAssertionRequest request =
       SpecializeRequestForAuthenticator(request_, *authenticator);
+  CtapGetAssertionOptions options =
+      SpecializeOptionsForAuthenticator(options_, *authenticator);
   PINUVDisposition uv_disposition =
       authenticator->PINUVDispositionForGetAssertion(request, observer());
   switch (uv_disposition) {
@@ -443,7 +458,7 @@
 
   CtapGetAssertionRequest request_copy(request);
   authenticator->GetAssertion(
-      std::move(request_copy), options_,
+      std::move(request_copy), std::move(options),
       base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
                      weak_factory_.GetWeakPtr(), authenticator,
                      std::move(request), base::ElapsedTimer()));
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 59ccae6..f8fb263 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -164,8 +164,6 @@
     "api/device_permissions_prompt.h",
     "api/execute_code_function.cc",
     "api/execute_code_function.h",
-    "api/extension_types_utils.cc",
-    "api/extension_types_utils.h",
     "api/extensions_api_client.cc",
     "api/extensions_api_client.h",
     "api/guest_view/app_view/app_view_guest_internal_api.cc",
diff --git a/extensions/browser/api/execute_code_function.cc b/extensions/browser/api/execute_code_function.cc
index 373c2a1..30a4eb4 100644
--- a/extensions/browser/api/execute_code_function.cc
+++ b/extensions/browser/api/execute_code_function.cc
@@ -11,7 +11,6 @@
 
 #include "base/bind.h"
 #include "base/ranges/algorithm.h"
-#include "extensions/browser/api/extension_types_utils.h"
 #include "extensions/browser/extension_api_frame_id_map.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/load_and_localize_file.h"
@@ -20,6 +19,7 @@
 #include "extensions/common/extension_resource.h"
 #include "extensions/common/mojom/css_origin.mojom-shared.h"
 #include "extensions/common/mojom/run_location.mojom-shared.h"
+#include "extensions/common/utils/extension_types_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace {
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index 65975c67..f6b8330 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -15,7 +15,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/storage_partition.h"
-#include "extensions/browser/app_sorting.h"
 #include "extensions/browser/blocklist_extension_prefs.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_prefs.h"
@@ -129,15 +128,6 @@
   } else if (extension_prefs_->IsExtensionDisabled(extension->id())) {
     registry_->AddDisabled(extension);
   } else {  // Extension should be enabled.
-    // All apps that are displayed in the launcher are ordered by their ordinals
-    // so we must ensure they have valid ordinals.
-    if (extension->RequiresSortOrdinal()) {
-      AppSorting* app_sorting = extension_system_->app_sorting();
-      app_sorting->SetExtensionVisible(extension->id(),
-                                       extension->ShouldDisplayInNewTabPage());
-      app_sorting->EnsureValidOrdinals(extension->id(),
-                                       syncer::StringOrdinal());
-    }
     registry_->AddEnabled(extension);
     ActivateExtension(extension.get(), true);
   }
diff --git a/extensions/browser/extension_user_script_loader.cc b/extensions/browser/extension_user_script_loader.cc
index a50fb50..e3ab1ca 100644
--- a/extensions/browser/extension_user_script_loader.cc
+++ b/extensions/browser/extension_user_script_loader.cc
@@ -33,7 +33,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_process_host.h"
-#include "extensions/browser/api/extension_types_utils.h"
 #include "extensions/browser/api/scripting/scripting_constants.h"
 #include "extensions/browser/api/scripting/scripting_utils.h"
 #include "extensions/browser/component_extension_resource_manager.h"
@@ -53,6 +52,7 @@
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/url_pattern_set.h"
 #include "extensions/common/utils/content_script_utils.h"
+#include "extensions/common/utils/extension_types_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/resource/resource_bundle.h"
 
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 0317404d..d8084c1 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -443,6 +443,8 @@
     "utils/base_string.h",
     "utils/content_script_utils.cc",
     "utils/content_script_utils.h",
+    "utils/extension_types_utils.cc",
+    "utils/extension_types_utils.h",
     "value_builder.cc",
     "value_builder.h",
     "value_counter.cc",
diff --git a/extensions/common/api/content_scripts.idl b/extensions/common/api/content_scripts.idl
index 6860728..85636f2 100644
--- a/extensions/common/api/content_scripts.idl
+++ b/extensions/common/api/content_scripts.idl
@@ -75,10 +75,8 @@
     // Specifies when JavaScript files are injected into the web page. The
     // preferred and default value is <code>document_idle</code>.
     RunAt? run_at;
-    // Describes the JavaScript world that this script will execute in.
-    // Currently manifest scripts will always run in the isolated world and this
-    // field should not be specified. Eventually, main world support may be
-    // added.
+    // The JavaScript "world" to run the script in. Defaults to
+    // <code>ISOLATED</code>. Only available in Manifest V3 extensions.
     [nodoc] extensionTypes.ExecutionWorld? world;
   };
 
diff --git a/extensions/common/csp_validator.cc b/extensions/common/csp_validator.cc
index 1b472bb8..bc5ccb9 100644
--- a/extensions/common/csp_validator.cc
+++ b/extensions/common/csp_validator.cc
@@ -114,8 +114,8 @@
  public:
   // Subframe related directives can have multiple directive names: "child-src"
   // or "frame-src".
-  DirectiveStatus(std::initializer_list<const char*> directives)
-      : directive_names_(directives.begin(), directives.end()) {}
+  explicit DirectiveStatus(std::vector<std::string> directives)
+      : directive_names_(std::move(directives)) {}
 
   DirectiveStatus(const DirectiveStatus&) = delete;
   DirectiveStatus(DirectiveStatus&&) = default;
@@ -401,9 +401,7 @@
   virtual std::string GetDefaultCSPValue(const DirectiveStatus& status) = 0;
 
   // List of directives we care about.
-  // TODO(karandeepb): There is no reason for these to be on the heap. Stack
-  // allocate.
-  std::vector<std::unique_ptr<DirectiveStatus>> secure_directives_;
+  std::vector<DirectiveStatus> secure_directives_;
 
  private:
   const std::string manifest_key_;
@@ -424,9 +422,9 @@
   for (const auto& directive : directives) {
     CSPDirectiveToken csp_directive_token(directive);
     bool matches_enforcing_directive = false;
-    for (const std::unique_ptr<DirectiveStatus>& status : secure_directives_) {
-      if (csp_directive_token.MatchAndUpdateStatus(
-              status.get(), secure_function_, manifest_key_, warnings)) {
+    for (DirectiveStatus& status : secure_directives_) {
+      if (csp_directive_token.MatchAndUpdateStatus(&status, secure_function_,
+                                                   manifest_key_, warnings)) {
         matches_enforcing_directive = true;
         break;
       }
@@ -441,8 +439,8 @@
   }
 
   if (default_src_status.seen_in_policy()) {
-    for (const std::unique_ptr<DirectiveStatus>& status : secure_directives_) {
-      if (!status->seen_in_policy()) {
+    for (const DirectiveStatus& status : secure_directives_) {
+      if (!status.seen_in_policy()) {
         // This |status| falls back to "default-src". So warnings from
         // "default-src" will apply.
         if (warnings) {
@@ -457,16 +455,17 @@
   } else {
     // Did not see "default-src".
     // Make sure we cover all sources from |secure_directives_|.
-    for (const std::unique_ptr<DirectiveStatus>& status : secure_directives_) {
-      if (status->seen_in_policy())  // Already covered.
+    for (const DirectiveStatus& status : secure_directives_) {
+      if (status.seen_in_policy()) {  // Already covered.
         continue;
-      enforced_csp_parts.push_back(GetDefaultCSPValue(*status));
+      }
+      enforced_csp_parts.push_back(GetDefaultCSPValue(status));
 
       if (warnings && show_missing_csp_warnings_) {
         warnings->push_back(
             InstallWarning(ErrorUtils::FormatErrorMessage(
                                manifest_errors::kInvalidCSPMissingSecureSrc,
-                               manifest_key_, status->name()),
+                               manifest_key_, status.name()),
                            manifest_key_));
       }
     }
@@ -483,9 +482,9 @@
       : CSPEnforcer(std::move(manifest_key),
                     true,
                     base::BindRepeating(&GetSecureDirectiveValues, options)) {
-    secure_directives_.emplace_back(new DirectiveStatus({kScriptSrc}));
+    secure_directives_.emplace_back(std::vector<std::string>({kScriptSrc}));
     if (!allow_insecure_object_src)
-      secure_directives_.emplace_back(new DirectiveStatus({kObjectSrc}));
+      secure_directives_.emplace_back(std::vector<std::string>({kObjectSrc}));
   }
 
   ExtensionCSPEnforcer(const ExtensionCSPEnforcer&) = delete;
@@ -507,8 +506,8 @@
                     false,
                     base::BindRepeating(&GetAppSandboxSecureDirectiveValues)) {
     secure_directives_.emplace_back(
-        new DirectiveStatus({kChildSrc, kFrameSrc}));
-    secure_directives_.emplace_back(new DirectiveStatus({kScriptSrc}));
+        std::vector<std::string>({kChildSrc, kFrameSrc}));
+    secure_directives_.emplace_back(std::vector<std::string>({kScriptSrc}));
   }
 
   AppSandboxPageCSPEnforcer(const AppSandboxPageCSPEnforcer&) = delete;
diff --git a/extensions/common/manifest.cc b/extensions/common/manifest.cc
index 8a92ed3..268b293 100644
--- a/extensions/common/manifest.cc
+++ b/extensions/common/manifest.cc
@@ -349,15 +349,6 @@
   return nullptr;
 }
 
-bool Manifest::GetDictionary(const std::string& path,
-                             const base::Value** out_value) const {
-  const base::Value* value = available_values_.FindByDottedPath(path);
-  if (!value || !value->is_dict())
-    return false;
-  *out_value = value;
-  return true;
-}
-
 bool Manifest::GetList(const std::string& path,
                        const base::Value** out_value) const {
   const base::Value* value = available_values_.FindByDottedPath(path);
diff --git a/extensions/common/manifest.h b/extensions/common/manifest.h
index e2f1655..1ad2b75 100644
--- a/extensions/common/manifest.h
+++ b/extensions/common/manifest.h
@@ -176,8 +176,6 @@
   const base::Value* FindDictPathAsValue(base::StringPiece path) const;
 
   // Deprecated: Use the FindDictPath(asValue) functions instead.
-  bool GetDictionary(const std::string& path,
-                     const base::Value** out_value) const;
   bool GetList(const std::string& path, const base::Value** out_value) const;
 
   // Returns true if this equals the |other| manifest.
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index d471313..37ec3c35 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -274,6 +274,9 @@
 const char16_t kDefaultStateShouldNotBeSet[] =
     u"The default_state key cannot be set for browser_action or page_action "
     "keys.";
+const char kExecutionWorldRestrictedToMV3[] =
+    "The 'world' property is restricted to extensions with 'manifest_version' "
+    "set to 3 or higher.";
 const char kExpectString[] = "Expect string value.";
 const char kFileNotFound[] = "File not found: *.";
 const char kHasDifferentialFingerprint[] =
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 1115352..3d7cf336 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -241,6 +241,7 @@
 extern const char kDeclarativeNetRequestPermissionNeeded[];
 extern const char16_t kDefaultStateShouldNotBeSet[];
 extern const char kDevToolsExperimental[];
+extern const char kExecutionWorldRestrictedToMV3[];
 extern const char kExpectString[];
 extern const char kFileNotFound[];
 extern const char kHasDifferentialFingerprint[];
diff --git a/extensions/common/manifest_handlers/content_scripts_handler.cc b/extensions/common/manifest_handlers/content_scripts_handler.cc
index fe0b1702..0d6d915 100644
--- a/extensions/common/manifest_handlers/content_scripts_handler.cc
+++ b/extensions/common/manifest_handlers/content_scripts_handler.cc
@@ -17,6 +17,7 @@
 #include "base/values.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/common/api/content_scripts.h"
+#include "extensions/common/api/extension_types.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_features.h"
@@ -30,6 +31,7 @@
 #include "extensions/common/url_pattern.h"
 #include "extensions/common/url_pattern_set.h"
 #include "extensions/common/utils/content_script_utils.h"
+#include "extensions/common/utils/extension_types_utils.h"
 #include "extensions/strings/grit/extensions_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
@@ -142,6 +144,17 @@
   ParseGlobs(base::OptionalToPtr(content_script.include_globs),
              base::OptionalToPtr(content_script.exclude_globs), result.get());
 
+  // Parse execution world. This should only be possible for MV3.
+  if (content_script.world != api::extension_types::EXECUTION_WORLD_NONE) {
+    if (extension->manifest_version() >= 3) {
+      result->set_execution_world(ConvertExecutionWorld(content_script.world));
+    } else {
+      extension->AddInstallWarning(
+          InstallWarning(errors::kExecutionWorldRestrictedToMV3,
+                         ContentScriptsKeys::kContentScripts));
+    }
+  }
+
   if (!script_parsing::ParseFileSources(
           extension, base::OptionalToPtr(content_script.js),
           base::OptionalToPtr(content_script.css), definition_index,
diff --git a/extensions/common/manifest_url_handlers.cc b/extensions/common/manifest_url_handlers.cc
index 8d2a698b..404c2a9c 100644
--- a/extensions/common/manifest_url_handlers.cc
+++ b/extensions/common/manifest_url_handlers.cc
@@ -73,14 +73,6 @@
 }
 
 // static
-bool ManifestURL::UpdatesFromGallery(const base::DictionaryValue* manifest) {
-  if (const std::string* url = manifest->FindStringKey(keys::kUpdateURL)) {
-    return extension_urls::IsWebstoreUpdateUrl(GURL(*url));
-  }
-  return false;
-}
-
-// static
 const GURL& ManifestURL::GetAboutPage(const Extension* extension) {
   return Get(extension, keys::kAboutPage);
 }
diff --git a/extensions/common/manifest_url_handlers.h b/extensions/common/manifest_url_handlers.h
index b8c6cf0..b3c6ade 100644
--- a/extensions/common/manifest_url_handlers.h
+++ b/extensions/common/manifest_url_handlers.h
@@ -11,10 +11,6 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handler.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace extensions {
 
 // A structure to hold various URLs like devtools_page, homepage_url, etc
@@ -51,7 +47,6 @@
 
   // Returns true if this extension's update URL is the extension gallery.
   static bool UpdatesFromGallery(const Extension* extension);
-  static bool UpdatesFromGallery(const base::DictionaryValue* manifest);
 
   // Returns the About Page for this extension.
   static const GURL& GetAboutPage(const Extension* extension);
diff --git a/extensions/common/utils/content_script_utils.cc b/extensions/common/utils/content_script_utils.cc
index 558d828fa..c34dce7b 100644
--- a/extensions/common/utils/content_script_utils.cc
+++ b/extensions/common/utils/content_script_utils.cc
@@ -56,7 +56,8 @@
   InstallWarning script_file_too_large_warning(
       l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTENT_SCRIPT_FILE_TOO_LARGE,
                                 relative_path.LossyDisplayName()),
-      api::content_scripts::ManifestKeys::kContentScripts);
+      api::content_scripts::ManifestKeys::kContentScripts,
+      base::UTF16ToUTF8(relative_path.LossyDisplayName()));
   if (remaining_length == 0u) {
     warnings->push_back(std::move(script_file_too_large_warning));
     return true;
diff --git a/extensions/browser/api/extension_types_utils.cc b/extensions/common/utils/extension_types_utils.cc
similarity index 97%
rename from extensions/browser/api/extension_types_utils.cc
rename to extensions/common/utils/extension_types_utils.cc
index 7efa1e6..42c6b8a7 100644
--- a/extensions/browser/api/extension_types_utils.cc
+++ b/extensions/common/utils/extension_types_utils.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "extensions/browser/api/extension_types_utils.h"
+#include "extensions/common/utils/extension_types_utils.h"
 
 namespace extensions {
 
diff --git a/extensions/browser/api/extension_types_utils.h b/extensions/common/utils/extension_types_utils.h
similarity index 83%
rename from extensions/browser/api/extension_types_utils.h
rename to extensions/common/utils/extension_types_utils.h
index 0263abd..76e0317 100644
--- a/extensions/browser/api/extension_types_utils.h
+++ b/extensions/common/utils/extension_types_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef EXTENSIONS_BROWSER_API_EXTENSION_TYPES_UTILS_H_
-#define EXTENSIONS_BROWSER_API_EXTENSION_TYPES_UTILS_H_
+#ifndef EXTENSIONS_COMMON_UTILS_EXTENSION_TYPES_UTILS_H_
+#define EXTENSIONS_COMMON_UTILS_EXTENSION_TYPES_UTILS_H_
 
 #include "extensions/common/api/extension_types.h"
 #include "extensions/common/mojom/execution_world.mojom-shared.h"
@@ -27,4 +27,4 @@
 
 }  // namespace extensions
 
-#endif  // EXTENSIONS_BROWSER_API_EXTENSION_TYPES_UTILS_H_
+#endif  // EXTENSIONS_COMMON_UTILS_EXTENSION_TYPES_UTILS_H_
diff --git a/gpu/GRAPHICS_TEAM_OWNERS b/gpu/GRAPHICS_TEAM_OWNERS
index e7987031..8080ca3 100644
--- a/gpu/GRAPHICS_TEAM_OWNERS
+++ b/gpu/GRAPHICS_TEAM_OWNERS
@@ -11,11 +11,9 @@
 zmo@chromium.org
 
 # ANGLE team
-capn@chromium.org
 jmadill@chromium.org
 jonahr@chromium.org
 srisser@chromium.org
-sugoi@chromium.org
 syoussefi@chromium.org
 ynovikov@chromium.org
 
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
index 4674acf5..878f5e2 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
@@ -90,9 +90,14 @@
 // Test fixture for AppLauncherTabHelper class.
 class AppLauncherTabHelperTest : public PlatformTest {
  protected:
-  AppLauncherTabHelperTest()
-      : browser_state_(TestChromeBrowserState::Builder().Build()),
-        abuse_detector_([[FakeAppLauncherAbuseDetector alloc] init]) {
+  AppLauncherTabHelperTest() {
+    TestChromeBrowserState::Builder builder;
+    builder.AddTestingFactory(
+        ReadingListModelFactory::GetInstance(),
+        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
+                            std::vector<ReadingListEntry>()));
+    browser_state_ = builder.Build();
+    abuse_detector_ = [[FakeAppLauncherAbuseDetector alloc] init];
     AppLauncherTabHelper::CreateForWebState(&web_state_, abuse_detector_);
     // Allow is the default policy for this test.
     abuse_detector_.policy = ExternalAppLaunchPolicyAllow;
@@ -131,27 +136,11 @@
     return policy_decision.ShouldAllowNavigation();
   }
 
-  // Initialize reading list model and its required tab helpers.
-  void InitializeReadingListModel() {
-    TestChromeBrowserState::Builder test_cbs_builder;
-    chrome_browser_state_ = test_cbs_builder.Build();
-    web_state_.SetBrowserState(chrome_browser_state_.get());
-    ReadingListModelFactory::GetInstance()->SetTestingFactoryAndUse(
-        chrome_browser_state_.get(),
-        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
-                            std::vector<ReadingListEntry>()));
-    is_reading_list_initialized_ = true;
-  }
-
   // Returns true if the `expected_read_status` matches the read status for any
   // non empty source URL based on the transition type and the app policy.
   bool TestReadingListUpdate(bool is_app_blocked,
                              bool is_link_transition,
                              bool expected_read_status) {
-    // Make sure reading list model is initialized.
-    if (!is_reading_list_initialized_)
-      InitializeReadingListModel();
-
     web_state_.SetCurrentURL(GURL("https://chromium.org"));
     GURL pending_url("http://google.com");
     navigation_manager_->AddItem(pending_url, ui::PAGE_TRANSITION_LINK);
@@ -159,8 +148,8 @@
     navigation_manager_->SetPendingItem(item);
     item->SetOriginalRequestURL(pending_url);
 
-    ReadingListModel* model = ReadingListModelFactory::GetForBrowserState(
-        chrome_browser_state_.get());
+    ReadingListModel* model =
+        ReadingListModelFactory::GetForBrowserState(browser_state_.get());
     EXPECT_TRUE(model->DeleteAllEntries());
     model->AddOrReplaceEntry(pending_url, "unread",
                              reading_list::ADDED_VIA_CURRENT_APP,
@@ -199,11 +188,8 @@
   std::unique_ptr<TestChromeBrowserState> browser_state_;
   web::FakeWebState web_state_;
   FakeNavigationManager* navigation_manager_ = nullptr;
-
-  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_ = nil;
   FakeAppLauncherAbuseDetector* abuse_detector_ = nil;
   FakeAppLauncherTabHelperDelegate delegate_;
-  bool is_reading_list_initialized_ = false;
   AppLauncherTabHelper* tab_helper_ = nullptr;
 };
 
diff --git a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
index 52407db..72c3454e 100644
--- a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
+++ b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
@@ -62,12 +62,12 @@
   void SetUp() override {
     PlatformTest::SetUp();
     TestChromeBrowserState::Builder test_cbs_builder;
-    chrome_browser_state_ = test_cbs_builder.Build();
-    IOSChromePasswordStoreFactory::GetInstance()->SetTestingFactory(
-        chrome_browser_state_.get(),
+    test_cbs_builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
         base::BindRepeating(
             &password_manager::BuildPasswordStore<
                 web::BrowserState, password_manager::TestPasswordStore>));
+    chrome_browser_state_ = test_cbs_builder.Build();
   }
 
   password_manager::PasswordStoreInterface* GetPasswordStore() {
diff --git a/ios/chrome/browser/download/background_service/background_download_service_test.cc b/ios/chrome/browser/download/background_service/background_download_service_test.cc
index 3a41701d..d7f5758 100644
--- a/ios/chrome/browser/download/background_service/background_download_service_test.cc
+++ b/ios/chrome/browser/download/background_service/background_download_service_test.cc
@@ -96,6 +96,11 @@
   void SetUp() override {
     download::test::BackgroundDownloadTestBase::SetUp();
     TestChromeBrowserState::Builder builder;
+    builder.AddTestingFactory(
+        BackgroundDownloadServiceFactory::GetInstance(),
+        base::BindRepeating(
+            &BackgroundDownloadServiceTest::MakeBackgroundDowloadService,
+            base::Unretained(this)));
     browser_state_ = builder.Build();
 
     // Create a random file in root dir and an unknown file in downoad dir.
@@ -108,22 +113,9 @@
     ASSERT_TRUE(base::CreateTemporaryFileInDir(download_dir,
                                                &temp_file_path_to_delete_));
 
-    // Inject a fake client through SetTestingFactory.
-    BackgroundDownloadServiceFactory* factory =
-        BackgroundDownloadServiceFactory::GetInstance();
-    factory->SetTestingFactory(
-        browser_state_.get(),
-        base::BindLambdaForTesting([&](web::BrowserState* browser_state) {
-          auto fake_client = std::make_unique<NiceMock<FakeClient>>();
-          fake_client_ = fake_client.get();
-          auto clients = std::make_unique<download::DownloadClientMap>();
-          clients->emplace(download::DownloadClient::TEST,
-                           std::move(fake_client));
-          return factory->BuildServiceWithClients(browser_state,
-                                                  std::move(clients));
-        }));
     service_ = BackgroundDownloadServiceFactory::GetForBrowserState(
         browser_state_.get());
+    ASSERT_TRUE(fake_client_);
   }
 
   // Download a file from embedded test server. Use different `relative_url` for
@@ -137,16 +129,34 @@
     service_->StartDownload(std::move(params));
   }
 
-  FakeClient* client() { return fake_client_; }
+  FakeClient* client() {
+    DCHECK(fake_client_);
+    return fake_client_;
+  }
+
   const base::FilePath& temp_file_path() const { return temp_file_path_; }
   const base::FilePath& temp_file_path_to_delete() const {
     return temp_file_path_to_delete_;
   }
 
+  // Factory for BackgroundDownloadService injecting a FakeClient into the
+  // service. A pointer to the FakeClient object is kept in the test fixture
+  // instance to allow test cases to manipulate it.
+  std::unique_ptr<KeyedService> MakeBackgroundDowloadService(
+      web::BrowserState* browser_state) {
+    DCHECK(!fake_client_);
+    auto fake_client = std::make_unique<NiceMock<FakeClient>>();
+    fake_client_ = fake_client.get();
+    auto clients = std::make_unique<download::DownloadClientMap>();
+    clients->emplace(download::DownloadClient::TEST, std::move(fake_client));
+    return BackgroundDownloadServiceFactory::GetInstance()
+        ->BuildServiceWithClients(browser_state, std::move(clients));
+  }
+
  private:
   std::unique_ptr<ChromeBrowserState> browser_state_;
   download::BackgroundDownloadService* service_;
-  FakeClient* fake_client_;
+  FakeClient* fake_client_ = nullptr;
   base::FilePath temp_file_path_;
   base::FilePath temp_file_path_to_delete_;
 };
diff --git a/ios/chrome/browser/infobars/overlays/BUILD.gn b/ios/chrome/browser/infobars/overlays/BUILD.gn
index af70a41..3c3a6ac 100644
--- a/ios/chrome/browser/infobars/overlays/BUILD.gn
+++ b/ios/chrome/browser/infobars/overlays/BUILD.gn
@@ -20,8 +20,6 @@
     "infobar_overlay_request_inserter.mm",
     "infobar_overlay_tab_helper.h",
     "infobar_overlay_tab_helper.mm",
-    "permissions_overlay_tab_helper.h",
-    "permissions_overlay_tab_helper.mm",
     "translate_infobar_placeholder_overlay_request_cancel_handler.h",
     "translate_infobar_placeholder_overlay_request_cancel_handler.mm",
     "translate_overlay_tab_helper.h",
@@ -29,7 +27,6 @@
   ]
   public_deps = [ ":overlay_type" ]
   deps = [
-    ":infobar_delegates",
     ":util",
     "//base",
     "//components/infobars/core",
@@ -53,15 +50,6 @@
   sources = [ "infobar_overlay_type.h" ]
 }
 
-source_set("infobar_delegates") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  sources = [
-    "permissions_overlay_infobar_delegate.h",
-    "permissions_overlay_infobar_delegate.mm",
-  ]
-  deps = [ "//components/infobars/core" ]
-}
-
 source_set("util") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
@@ -108,7 +96,6 @@
     "infobar_overlay_request_inserter_unittest.mm",
     "infobar_overlay_tab_helper_unittest.mm",
     "infobar_overlay_util_unittest.mm",
-    "permissions_overlay_tab_helper_unittest.mm",
     "translate_infobar_placeholder_overlay_request_cancel_handler_unittest.mm",
     "translate_overlay_tab_helper_unittest.mm",
   ]
@@ -116,7 +103,6 @@
     ":overlays",
     ":test_support",
     ":util",
-    "//base",
     "//base/test:test_support",
     "//components/password_manager/core/browser:test_support",
     "//components/sync_preferences:test_support",
@@ -125,7 +111,6 @@
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/infobars",
     "//ios/chrome/browser/infobars:public",
-    "//ios/chrome/browser/infobars/overlays:infobar_delegates",
     "//ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test",
     "//ios/chrome/browser/infobars/test",
     "//ios/chrome/browser/main:test_support",
@@ -142,8 +127,6 @@
     "//ios/chrome/browser/ui/infobars/test",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/test:test_support",
-    "//ios/web/common",
-    "//ios/web/public/permissions",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
     "//testing/gtest",
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
index a3201b1..b399b21c 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
@@ -37,12 +37,12 @@
     "//components/translate/core/browser",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/infobars",
-    "//ios/chrome/browser/infobars/overlays:infobar_delegates",
     "//ios/chrome/browser/infobars/overlays:overlay_type",
     "//ios/chrome/browser/overlays",
     "//ios/chrome/browser/overlays/public/common",
     "//ios/chrome/browser/overlays/public/common/infobars",
     "//ios/chrome/browser/passwords:infobar_delegates",
+    "//ios/chrome/browser/permissions:infobar_delegate",
     "//ios/chrome/browser/safe_browsing/tailored_security:infobar_delegates",
     "//ios/chrome/browser/ui/authentication",
     "//ios/chrome/browser/ui/resources:legacy_password_key",
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/permissions_infobar_banner_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_banner/permissions_infobar_banner_overlay_request_config.mm
index 32f6c5c0..ce4f4cb7 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/permissions_infobar_banner_overlay_request_config.mm
+++ b/ios/chrome/browser/overlays/public/infobar_banner/permissions_infobar_banner_overlay_request_config.mm
@@ -8,8 +8,8 @@
 #import "components/infobars/core/infobar.h"
 #import "ios/chrome/browser/infobars/infobar_ios.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_type.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
 #import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/permissions/permissions.h"
 #import "ui/base/l10n/l10n_util.h"
@@ -26,8 +26,8 @@
 PermissionsBannerRequestConfig::PermissionsBannerRequestConfig(InfoBar* infobar)
     : infobar_(infobar) {
   DCHECK(infobar_);
-  PermissionsOverlayInfobarDelegate* delegate =
-      static_cast<PermissionsOverlayInfobarDelegate*>(infobar_->delegate());
+  PermissionsInfobarDelegate* delegate =
+      static_cast<PermissionsInfobarDelegate*>(infobar_->delegate());
   NSArray<NSNumber*>* accessible_permissions =
       delegate->GetMostRecentlyAccessiblePermissions();
   if ([accessible_permissions containsObject:@(web::PermissionCamera)]) {
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn
index 0b17686..4c8e098 100644
--- a/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/infobar_modal/permissions/BUILD.gn
@@ -14,9 +14,9 @@
     "//base",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/infobars",
-    "//ios/chrome/browser/infobars/overlays:infobar_delegates",
     "//ios/chrome/browser/overlays",
     "//ios/chrome/browser/overlays/public/common/infobars",
+    "//ios/chrome/browser/permissions:infobar_delegate",
     "//ios/chrome/browser/web",
     "//ios/web/public",
     "//ui/base",
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm
index 7a9aad7..b3eddf79 100644
--- a/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm
+++ b/ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.mm
@@ -7,8 +7,8 @@
 #import "base/strings/sys_string_conversions.h"
 #import "base/strings/utf_string_conversions.h"
 #import "ios/chrome/browser/infobars/infobar_ios.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
 #import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/navigation/navigation_item.h"
 #import "ios/web/public/navigation/navigation_manager.h"
@@ -25,8 +25,8 @@
     PermissionsInfobarModalOverlayRequestConfig(InfoBarIOS* infobar)
     : infobar_(infobar) {
   DCHECK(infobar_);
-  PermissionsOverlayInfobarDelegate* delegate =
-      static_cast<PermissionsOverlayInfobarDelegate*>(infobar_->delegate());
+  PermissionsInfobarDelegate* delegate =
+      static_cast<PermissionsInfobarDelegate*>(infobar_->delegate());
   web_state_ = delegate->GetWebState();
 
   web::NavigationItem* visible_item =
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_check_manager_unittest.mm b/ios/chrome/browser/passwords/ios_chrome_password_check_manager_unittest.mm
index e8b6253..139e11af 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_check_manager_unittest.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_password_check_manager_unittest.mm
@@ -18,6 +18,7 @@
 #import "base/test/bind.h"
 #import "base/test/scoped_feature_list.h"
 #import "base/time/time.h"
+#import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/bulk_leak_check_service.h"
 #import "components/password_manager/core/browser/mock_bulk_leak_check_service.h"
 #import "components/password_manager/core/browser/password_form.h"
@@ -74,26 +75,9 @@
               (override));
 };
 
-scoped_refptr<TestPasswordStore> CreateAndUseTestPasswordStore(
-    ChromeBrowserState* _browserState) {
-  return base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-      IOSChromePasswordStoreFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              _browserState,
-              base::BindRepeating(&password_manager::BuildPasswordStore<
-                                  web::BrowserState, TestPasswordStore>))
-          .get()));
-}
-
-MockBulkLeakCheckService* CreateAndUseBulkLeakCheckService(
-    ChromeBrowserState* _browserState) {
-  return static_cast<MockBulkLeakCheckService*>(
-      IOSChromeBulkLeakCheckServiceFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              _browserState, base::BindLambdaForTesting([](web::BrowserState*) {
-                return std::unique_ptr<KeyedService>(
-                    std::make_unique<MockBulkLeakCheckService>());
-              })));
+std::unique_ptr<KeyedService> MakeMockPasswordCheckManagerObserver(
+    web::BrowserState*) {
+  return std::make_unique<MockBulkLeakCheckService>();
 }
 
 PasswordForm MakeSavedPassword(
@@ -127,11 +111,25 @@
 
 class IOSChromePasswordCheckManagerTest : public PlatformTest {
  public:
-  IOSChromePasswordCheckManagerTest()
-      : browser_state_(TestChromeBrowserState::Builder().Build()),
-        bulk_leak_check_service_(
-            CreateAndUseBulkLeakCheckService(browser_state_.get())),
-        store_(CreateAndUseTestPasswordStore(browser_state_.get())) {
+  IOSChromePasswordCheckManagerTest() {
+    TestChromeBrowserState::Builder builder;
+    builder.AddTestingFactory(
+        IOSChromeBulkLeakCheckServiceFactory::GetInstance(),
+        base::BindRepeating(&MakeMockPasswordCheckManagerObserver));
+    builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<web::BrowserState,
+                                                  TestPasswordStore>));
+    browser_state_ = builder.Build();
+    bulk_leak_check_service_ = static_cast<MockBulkLeakCheckService*>(
+        IOSChromeBulkLeakCheckServiceFactory::GetForBrowserState(
+            browser_state_.get()));
+    store_ =
+        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
+                .get()));
     manager_ = IOSChromePasswordCheckManagerFactory::GetForBrowserState(
         browser_state_.get());
   }
diff --git a/ios/chrome/browser/passwords/well_known_change_password_tab_helper_unittest.mm b/ios/chrome/browser/passwords/well_known_change_password_tab_helper_unittest.mm
index 5157e046..438b5ba 100644
--- a/ios/chrome/browser/passwords/well_known_change_password_tab_helper_unittest.mm
+++ b/ios/chrome/browser/passwords/well_known_change_password_tab_helper_unittest.mm
@@ -70,6 +70,10 @@
   navigation_manager->LoadURLWithParams(params);
 }
 
+std::unique_ptr<KeyedService> MakeMockAffiliationService(web::BrowserState*) {
+  return std::make_unique<NiceMock<password_manager::MockAffiliationService>>();
+}
+
 }  // namespace
 
 // This test uses a mockserver to simulate different response. To handle the
@@ -85,7 +89,10 @@
         &WellKnownChangePasswordTabHelperTest::HandleRequest,
         base::Unretained(this)));
 
-    browser_state_ = TestChromeBrowserState::Builder().Build();
+    TestChromeBrowserState::Builder builder;
+    builder.AddTestingFactory(IOSChromeAffiliationServiceFactory::GetInstance(),
+                              base::BindRepeating(&MakeMockAffiliationService));
+    browser_state_ = builder.Build();
 
     web::WebState::CreateParams params(browser_state_.get());
     web_state_ = web::WebState::Create(params);
@@ -100,14 +107,8 @@
 
     affiliation_service_ =
         static_cast<password_manager::MockAffiliationService*>(
-            IOSChromeAffiliationServiceFactory::GetInstance()
-                ->SetTestingFactoryAndUse(
-                    web_state()->GetBrowserState(),
-                    base::BindRepeating([](web::BrowserState* browser_state) {
-                      return std::unique_ptr<KeyedService>(
-                          std::make_unique<NiceMock<
-                              password_manager::MockAffiliationService>>());
-                    })));
+            IOSChromeAffiliationServiceFactory::GetForBrowserState(
+                browser_state_.get()));
 
     web_state()->SetDelegate(&delegate_);
     password_manager::WellKnownChangePasswordTabHelper::CreateForWebState(
diff --git a/ios/chrome/browser/permissions/BUILD.gn b/ios/chrome/browser/permissions/BUILD.gn
new file mode 100644
index 0000000..cb5b671f
--- /dev/null
+++ b/ios/chrome/browser/permissions/BUILD.gn
@@ -0,0 +1,57 @@
+# 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.
+
+source_set("infobar_delegate") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "permissions_infobar_delegate.h",
+    "permissions_infobar_delegate.mm",
+  ]
+  deps = [ "//components/infobars/core" ]
+}
+
+source_set("tab_helper") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "permissions_tab_helper.h",
+    "permissions_tab_helper.mm",
+  ]
+  deps = [
+    ":infobar_delegate",
+    "//components/infobars/core",
+    "//ios/chrome/browser/infobars",
+    "//ios/chrome/browser/infobars/overlays",
+    "//ios/chrome/browser/infobars/overlays:util",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/common/infobars",
+    "//ios/chrome/browser/overlays/public/infobar_banner",
+    "//ios/chrome/browser/overlays/public/infobar_modal/permissions",
+    "//ios/web/common",
+    "//ios/web/public",
+    "//ios/web/public/permissions",
+  ]
+}
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [ "permissions_tab_helper_unittest.mm" ]
+  deps = [
+    ":infobar_delegate",
+    ":tab_helper",
+    "//base",
+    "//base/test:test_support",
+    "//ios/chrome/browser/infobars",
+    "//ios/chrome/browser/infobars/overlays",
+    "//ios/chrome/browser/permissions:infobar_delegate",
+    "//ios/chrome/test:test_support",
+    "//ios/web/common",
+    "//ios/web/common",
+    "//ios/web/public/permissions",
+    "//ios/web/public/permissions",
+    "//ios/web/public/test",
+    "//ios/web/public/test/fakes",
+    "//testing/gtest",
+  ]
+}
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h b/ios/chrome/browser/permissions/permissions_infobar_delegate.h
similarity index 70%
rename from ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h
rename to ios/chrome/browser/permissions/permissions_infobar_delegate.h
index 049e4e4..5e9fe6a20 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h
+++ b/ios/chrome/browser/permissions/permissions_infobar_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_INFOBAR_DELEGATE_H_
-#define IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_INFOBAR_DELEGATE_H_
+#ifndef IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_INFOBAR_DELEGATE_H_
+#define IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_INFOBAR_DELEGATE_H_
 
 #import <Foundation/Foundation.h>
 #include "components/infobars/core/confirm_infobar_delegate.h"
@@ -14,13 +14,13 @@
 
 // An interface derived from ConfirmInfoBarDelegate implemented by objects
 // wishing to a PermissionsInfoBar.
-class PermissionsOverlayInfobarDelegate : public ConfirmInfoBarDelegate {
+class PermissionsInfobarDelegate : public ConfirmInfoBarDelegate {
  public:
-  PermissionsOverlayInfobarDelegate(
+  PermissionsInfobarDelegate(
       NSArray<NSNumber*>* recently_accessible_permissions,
       web::WebState* web_state);
 
-  ~PermissionsOverlayInfobarDelegate() override;
+  ~PermissionsInfobarDelegate() override;
 
   // ConfirmInfoBarDelegate implementation.
   std::u16string GetMessageText() const override;
@@ -39,4 +39,4 @@
   web::WebState* web_state_;
 };
 
-#endif  // IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_INFOBAR_DELEGATE_H_
+#endif  // IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_INFOBAR_DELEGATE_H_
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.mm b/ios/chrome/browser/permissions/permissions_infobar_delegate.mm
similarity index 61%
rename from ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.mm
rename to ios/chrome/browser/permissions/permissions_infobar_delegate.mm
index ab73175..d5b6f102 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.mm
+++ b/ios/chrome/browser/permissions/permissions_infobar_delegate.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 
 #import "components/infobars/core/infobar_delegate.h"
 
@@ -10,31 +10,30 @@
 #error "This file requires ARC support."
 #endif
 
-PermissionsOverlayInfobarDelegate::PermissionsOverlayInfobarDelegate(
+PermissionsInfobarDelegate::PermissionsInfobarDelegate(
     NSArray<NSNumber*>* recently_accessible_permissions,
     web::WebState* web_state)
     : recently_accessible_permissions_(recently_accessible_permissions),
       web_state_(web_state) {}
 
-PermissionsOverlayInfobarDelegate::~PermissionsOverlayInfobarDelegate() =
-    default;
+PermissionsInfobarDelegate::~PermissionsInfobarDelegate() = default;
 
 NSArray<NSNumber*>*
-PermissionsOverlayInfobarDelegate::GetMostRecentlyAccessiblePermissions() {
+PermissionsInfobarDelegate::GetMostRecentlyAccessiblePermissions() {
   return recently_accessible_permissions_;
 }
 
 // As we don't need message in the infobar, we return empty message to satisfy
 // implementation requirement for ConfirmInfoBarDelegate.
-std::u16string PermissionsOverlayInfobarDelegate::GetMessageText() const {
+std::u16string PermissionsInfobarDelegate::GetMessageText() const {
   return std::u16string();
 }
 
-web::WebState* PermissionsOverlayInfobarDelegate::GetWebState() const {
+web::WebState* PermissionsInfobarDelegate::GetWebState() const {
   return web_state_;
 }
 
 infobars::InfoBarDelegate::InfoBarIdentifier
-PermissionsOverlayInfobarDelegate::GetIdentifier() const {
+PermissionsInfobarDelegate::GetIdentifier() const {
   return IOS_PERMISSIONS_INFOBAR_DELEGATE;
 }
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h b/ios/chrome/browser/permissions/permissions_tab_helper.h
similarity index 78%
rename from ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h
rename to ios/chrome/browser/permissions/permissions_tab_helper.h
index 359a9410..53104da7 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h
+++ b/ios/chrome/browser/permissions/permissions_tab_helper.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_TAB_HELPER_H_
-#define IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_TAB_HELPER_H_
+#ifndef IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_TAB_HELPER_H_
+#define IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_TAB_HELPER_H_
 
 #import <Foundation/Foundation.h>
 
@@ -24,17 +24,16 @@
 
 // Tab helper that observes changes to web permissions and creates/replaces the
 // respective infobar accordingly.
-class PermissionsOverlayTabHelper
+class PermissionsTabHelper
     : public infobars::InfoBarManager::Observer,
       public web::WebStateObserver,
-      public web::WebStateUserData<PermissionsOverlayTabHelper> {
+      public web::WebStateUserData<PermissionsTabHelper> {
  public:
-  explicit PermissionsOverlayTabHelper(web::WebState* web_state);
+  explicit PermissionsTabHelper(web::WebState* web_state);
 
-  PermissionsOverlayTabHelper(const PermissionsOverlayTabHelper&) = delete;
-  PermissionsOverlayTabHelper& operator=(const PermissionsOverlayTabHelper&) =
-      delete;
-  ~PermissionsOverlayTabHelper() override;
+  PermissionsTabHelper(const PermissionsTabHelper&) = delete;
+  PermissionsTabHelper& operator=(const PermissionsTabHelper&) = delete;
+  ~PermissionsTabHelper() override;
 
   // web::WebStateObserver implementation.
   void PermissionStateChanged(web::WebState* web_state,
@@ -47,7 +46,7 @@
   void OnManagerShuttingDown(infobars::InfoBarManager* manager) override;
 
  private:
-  friend class web::WebStateUserData<PermissionsOverlayTabHelper>;
+  friend class web::WebStateUserData<PermissionsTabHelper>;
 
   // Adds/replaces the infobar and show the banner.
   void ShowInfoBar();
@@ -86,4 +85,4 @@
   WEB_STATE_USER_DATA_KEY_DECL();
 };
 
-#endif  // IOS_CHROME_BROWSER_INFOBARS_OVERLAYS_PERMISSIONS_OVERLAY_TAB_HELPER_H_
+#endif  // IOS_CHROME_BROWSER_PERMISSIONS_PERMISSIONS_TAB_HELPER_H_
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.mm b/ios/chrome/browser/permissions/permissions_tab_helper.mm
similarity index 81%
rename from ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.mm
rename to ios/chrome/browser/permissions/permissions_tab_helper.mm
index 3ebc996..d57cab9 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.mm
+++ b/ios/chrome/browser/permissions/permissions_tab_helper.mm
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h"
+#import "ios/chrome/browser/permissions/permissions_tab_helper.h"
 
 #import "base/timer/timer.h"
 #import "ios/chrome/browser/infobars/infobar_ios.h"
 #import "ios/chrome/browser/infobars/infobar_manager_impl.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_util.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
 #import "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_placeholder_request_config.h"
 #import "ios/chrome/browser/overlays/public/overlay_request_queue.h"
 #import "ios/chrome/browser/overlays/public/overlay_request_queue_util.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/permissions/permissions.h"
 
@@ -24,8 +24,7 @@
 const float kTimeoutInMillisecond = 250;
 }  // namespace
 
-PermissionsOverlayTabHelper::PermissionsOverlayTabHelper(
-    web::WebState* web_state)
+PermissionsTabHelper::PermissionsTabHelper(web::WebState* web_state)
     : web_state_(web_state) {
   if (@available(iOS 15.0, *)) {
     if (web::features::IsMediaPermissionsControlEnabled()) {
@@ -40,11 +39,10 @@
   }
 }
 
-PermissionsOverlayTabHelper::~PermissionsOverlayTabHelper() {}
+PermissionsTabHelper::~PermissionsTabHelper() {}
 
-void PermissionsOverlayTabHelper::PermissionStateChanged(
-    web::WebState* web_state,
-    web::Permission permission) {
+void PermissionsTabHelper::PermissionStateChanged(web::WebState* web_state,
+                                                  web::Permission permission) {
   DCHECK_EQ(web_state_, web_state);
   web::PermissionState new_state =
       web_state_->GetStateForPermission(permission);
@@ -79,7 +77,7 @@
     if (!timer_.IsRunning()) {
       recently_accessible_permissions_ = [NSMutableArray array];
       timer_.Start(FROM_HERE, base::Milliseconds(kTimeoutInMillisecond), this,
-                   &PermissionsOverlayTabHelper::ShowInfoBar);
+                   &PermissionsTabHelper::ShowInfoBar);
     }
     [recently_accessible_permissions_ addObject:@(permission)];
   }
@@ -89,7 +87,7 @@
   }
 }
 
-void PermissionsOverlayTabHelper::WebStateDestroyed(web::WebState* web_state) {
+void PermissionsTabHelper::WebStateDestroyed(web::WebState* web_state) {
   DCHECK_EQ(web_state_, web_state);
   DCHECK(banner_queue_);
   if (web::features::IsMediaPermissionsControlEnabled()) {
@@ -100,29 +98,29 @@
   inserter_ = nullptr;
 }
 
-void PermissionsOverlayTabHelper::OnInfoBarRemoved(infobars::InfoBar* infobar,
-                                                   bool animate) {
+void PermissionsTabHelper::OnInfoBarRemoved(infobars::InfoBar* infobar,
+                                            bool animate) {
   if (infobar == infobar_) {
     infobar_manager_scoped_observation_.Reset();
     infobar_ = nullptr;
   }
 }
 
-void PermissionsOverlayTabHelper::OnManagerShuttingDown(
+void PermissionsTabHelper::OnManagerShuttingDown(
     infobars::InfoBarManager* manager) {
   DCHECK(infobar_manager_scoped_observation_.IsObservingSource(manager));
   infobar_manager_scoped_observation_.Reset();
 }
 
-void PermissionsOverlayTabHelper::ShowInfoBar() {
+void PermissionsTabHelper::ShowInfoBar() {
   infobars::InfoBarManager* infobar_manager =
       InfoBarManagerImpl::FromWebState(web_state_);
   if (!infobar_manager_scoped_observation_.IsObservingSource(infobar_manager)) {
     infobar_manager_scoped_observation_.Observe(infobar_manager);
   }
 
-  std::unique_ptr<PermissionsOverlayInfobarDelegate> delegate(
-      std::make_unique<PermissionsOverlayInfobarDelegate>(
+  std::unique_ptr<PermissionsInfobarDelegate> delegate(
+      std::make_unique<PermissionsInfobarDelegate>(
           recently_accessible_permissions_, web_state_));
 
   BOOL first_activation = infobar_ == nullptr;
@@ -137,8 +135,9 @@
     size_t index = 0;
     bool request_found = GetInfobarOverlayRequestIndex(
         banner_queue_, static_cast<InfoBarIOS*>(infobar_), &index);
-    if (request_found)  // The new banner is already shown.
+    if (request_found) {  // The new banner is already shown.
       return;
+    }
     InsertParams params(static_cast<InfoBarIOS*>(infobar_));
     params.overlay_type = InfobarOverlayType::kBanner;
     params.insertion_index = banner_queue_->size();
@@ -147,7 +146,7 @@
   }
 }
 
-void PermissionsOverlayTabHelper::UpdateIsInfoBarAccepted() {
+void PermissionsTabHelper::UpdateIsInfoBarAccepted() {
   if (infobar_ == nullptr) {
     return;
   }
@@ -163,4 +162,4 @@
   static_cast<InfoBarIOS*>(infobar_)->set_accepted(accepted);
 }
 
-WEB_STATE_USER_DATA_KEY_IMPL(PermissionsOverlayTabHelper)
+WEB_STATE_USER_DATA_KEY_IMPL(PermissionsTabHelper)
diff --git a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm b/ios/chrome/browser/permissions/permissions_tab_helper_unittest.mm
similarity index 92%
rename from ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm
rename to ios/chrome/browser/permissions/permissions_tab_helper_unittest.mm
index 4fcb7a9..691f386 100644
--- a/ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper_unittest.mm
+++ b/ios/chrome/browser/permissions/permissions_tab_helper_unittest.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h"
+#import "ios/chrome/browser/permissions/permissions_tab_helper.h"
 
 #import "base/test/scoped_feature_list.h"
 #import "base/test/task_environment.h"
@@ -13,7 +13,7 @@
 #import "ios/chrome/browser/infobars/infobar_manager_impl.h"
 #import "ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/permissions/permissions.h"
 #import "ios/web/public/test/fakes/fake_navigation_manager.h"
@@ -29,10 +29,10 @@
 constexpr base::TimeDelta kTimeoutDelay = base::Milliseconds(251);
 }  // namespace
 
-// Test fixture for PermissionsOverlayTabHelper.
-class PermissionsOverlayTabHelperTest : public PlatformTest {
+// Test fixture for PermissionsTabHelper.
+class PermissionsTabHelperTest : public PlatformTest {
  public:
-  PermissionsOverlayTabHelperTest()
+  PermissionsTabHelperTest()
       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
     scoped_feature_list_.InitWithFeatures(
         {web::features::kMediaPermissionsControl}, {});
@@ -42,14 +42,13 @@
     InfoBarManagerImpl::CreateForWebState(&web_state_);
     InfobarOverlayRequestInserter::CreateForWebState(
         &web_state_, &DefaultInfobarOverlayRequestFactory);
-    PermissionsOverlayTabHelper::CreateForWebState(&web_state_);
+    PermissionsTabHelper::CreateForWebState(&web_state_);
   }
 
-  ~PermissionsOverlayTabHelperTest() override {
+  ~PermissionsTabHelperTest() override {
     InfoBarManagerImpl::FromWebState(&web_state_)->ShutDown();
     // Observer should be removed before `scoped_feature_list_` is reset.
-    web_state_.RemoveObserver(
-        PermissionsOverlayTabHelper::FromWebState(&web_state_));
+    web_state_.RemoveObserver(PermissionsTabHelper::FromWebState(&web_state_));
   }
 
  protected:
@@ -68,10 +67,11 @@
 
   // Returns recently_accessible_permissions determined by the tab helper.
   NSArray<NSNumber*>* recently_accessible_permissions() {
-    if (infobar() == nullptr)
+    if (infobar() == nullptr) {
       return [NSArray array];
-    PermissionsOverlayInfobarDelegate* delegate =
-        static_cast<PermissionsOverlayInfobarDelegate*>(infobar()->delegate());
+    }
+    PermissionsInfobarDelegate* delegate =
+        static_cast<PermissionsInfobarDelegate*>(infobar()->delegate());
     return delegate->GetMostRecentlyAccessiblePermissions();
   }
 
@@ -82,7 +82,7 @@
 
 // Tests that an infobar is setup with the correct acceptance state when the
 // status of a single permission changes.
-TEST_F(PermissionsOverlayTabHelperTest, CheckInfobarCountForSinglePermission) {
+TEST_F(PermissionsTabHelperTest, CheckInfobarCountForSinglePermission) {
   if (@available(iOS 15, *)) {
     // Allowed permission.
     web_state_.SetStateForPermission(web::PermissionStateAllowed,
@@ -109,7 +109,7 @@
 
 // Tests that blocking a permission and allowing it again correctly resets
 // infobar and acceptance state.
-TEST_F(PermissionsOverlayTabHelperTest, BlockingAndAllowingSinglePermission) {
+TEST_F(PermissionsTabHelperTest, BlockingAndAllowingSinglePermission) {
   if (@available(iOS 15, *)) {
     // Allowed permission.
     web_state_.SetStateForPermission(web::PermissionStateAllowed,
@@ -143,7 +143,7 @@
 
 // Tests that making a permission inaccessible and allowing it again correctly
 // resets infobar and acceptance state.
-TEST_F(PermissionsOverlayTabHelperTest,
+TEST_F(PermissionsTabHelperTest,
        MakingPermissionNotAccessibleAndAllowingItAgain) {
   if (@available(iOS 15, *)) {
     // Allowed permission.
@@ -176,7 +176,7 @@
 
 // Tests that an infobar is setup with the correct acceptance state when the
 // status of both permission are allowed simultaneously.
-TEST_F(PermissionsOverlayTabHelperTest,
+TEST_F(PermissionsTabHelperTest,
        CheckInfobarCountForSimultaneouslyAllowedPermissions) {
   if (@available(iOS 15, *)) {
     // Allow both permissions.
@@ -217,7 +217,7 @@
 
 // Tests that an infobar is setup and replaced with the correct acceptance
 // state when the status of both permission are allowed one by one.
-TEST_F(PermissionsOverlayTabHelperTest,
+TEST_F(PermissionsTabHelperTest,
        CheckInfobarCountForSeparatelyAllowedPermissions) {
   if (@available(iOS 15, *)) {
     // Allow one permission.
@@ -264,7 +264,7 @@
 
 // Tests that infobar and acceptance state would be handled correctly when one
 // permission is blocked while the other permission changes states.
-TEST_F(PermissionsOverlayTabHelperTest,
+TEST_F(PermissionsTabHelperTest,
        CheckInfobarAndAcceptanceStateWhenOnePermissionIsBlocked) {
   if (@available(iOS 15, *)) {
     // Allow one permission.
diff --git a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
index b308bf8..b41ac132 100644
--- a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
@@ -56,6 +56,9 @@
   { policy::key::kAllowChromeDataInBackups,
     prefs::kAllowChromeDataInBackups,
     base::Value::Type::BOOLEAN },
+  { policy::key::kAppStoreRatingEnabled,
+    prefs::kAppStoreRatingPolicyEnabled,
+    base::Value::Type::BOOLEAN },
   { policy::key::kComponentUpdatesEnabled,
     prefs::kComponentUpdatesEnabled,
     base::Value::Type::BOOLEAN },
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index aecbe8f..8003075 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -191,6 +191,7 @@
   registry->RegisterListPref(prefs::kRestrictAccountsToPatterns);
   registry->RegisterIntegerPref(prefs::kBrowserSigninPolicy,
                                 static_cast<int>(BrowserSigninMode::kEnabled));
+  registry->RegisterBooleanPref(prefs::kAppStoreRatingPolicyEnabled, true);
 
   registry->RegisterIntegerPref(kTrialGroupPrefName, 0);
 
diff --git a/ios/chrome/browser/prefs/pref_names.cc b/ios/chrome/browser/prefs/pref_names.cc
index dcbb50e7..f9ad265 100644
--- a/ios/chrome/browser/prefs/pref_names.cc
+++ b/ios/chrome/browser/prefs/pref_names.cc
@@ -9,6 +9,9 @@
 // The application locale.
 const char kApplicationLocale[] = "intl.app_locale";
 
+// Boolean that is true when the AppStoreRatingEnabled policy is enabled.
+const char kAppStoreRatingPolicyEnabled[] = "ios.app_store_rating_enabled";
+
 // Boolean that is true when Suggest support is enabled.
 const char kArticlesForYouEnabled[] = "suggestions.articles_enabled";
 
diff --git a/ios/chrome/browser/prefs/pref_names.h b/ios/chrome/browser/prefs/pref_names.h
index 82ee729..f611833f 100644
--- a/ios/chrome/browser/prefs/pref_names.h
+++ b/ios/chrome/browser/prefs/pref_names.h
@@ -8,6 +8,7 @@
 namespace prefs {
 
 extern const char kApplicationLocale[];
+extern const char kAppStoreRatingPolicyEnabled[];
 extern const char kArticlesForYouEnabled[];
 extern const char kBrowserStateInfoCache[];
 extern const char kBrowserStateLastUsed[];
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index 613b6a4..a826bae6 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -87,6 +87,7 @@
     "//ios/chrome/browser/optimization_guide",
     "//ios/chrome/browser/overscroll_actions",
     "//ios/chrome/browser/passwords",
+    "//ios/chrome/browser/permissions:tab_helper",
     "//ios/chrome/browser/policy_url_blocking",
     "//ios/chrome/browser/prerender",
     "//ios/chrome/browser/reading_list",
diff --git a/ios/chrome/browser/tabs/tab_helper_util.mm b/ios/chrome/browser/tabs/tab_helper_util.mm
index 5076803..97c96c8 100644
--- a/ios/chrome/browser/tabs/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/tab_helper_util.mm
@@ -53,7 +53,6 @@
 #import "ios/chrome/browser/infobars/overlays/default_infobar_overlay_request_factory.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_request_inserter.h"
 #import "ios/chrome/browser/infobars/overlays/infobar_overlay_tab_helper.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_tab_helper.h"
 #import "ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper.h"
 #import "ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h"
 #import "ios/chrome/browser/link_to_text/link_to_text_tab_helper.h"
@@ -66,6 +65,7 @@
 #import "ios/chrome/browser/overscroll_actions/overscroll_actions_tab_helper.h"
 #import "ios/chrome/browser/passwords/password_tab_helper.h"
 #import "ios/chrome/browser/passwords/well_known_change_password_tab_helper.h"
+#import "ios/chrome/browser/permissions/permissions_tab_helper.h"
 #import "ios/chrome/browser/policy_url_blocking/policy_url_blocking_tab_helper.h"
 #import "ios/chrome/browser/prerender/prerender_service_factory.h"
 #import "ios/chrome/browser/reading_list/offline_page_tab_helper.h"
@@ -250,7 +250,7 @@
 
   OfflinePageTabHelper::CreateForWebState(
       web_state, ReadingListModelFactory::GetForBrowserState(browser_state));
-  PermissionsOverlayTabHelper::CreateForWebState(web_state);
+  PermissionsTabHelper::CreateForWebState(web_state);
 
   RepostFormTabHelper::CreateForWebState(web_state);
   NetExportTabHelper::CreateForWebState(web_state);
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 97358177..62f0302 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -1518,15 +1518,8 @@
     self.findBarCoordinator = nil;
   }
 
-  findBarCoordinator =
-      [[FindBarCoordinator alloc] initWithBaseViewController:self.viewController
-                                                     browser:self.browser];
-  self.findBarCoordinator = findBarCoordinator;
-
-  findBarCoordinator.presenter = _toolbarAccessoryPresenter;
-  findBarCoordinator.delegate = self;
-  findBarCoordinator.presentationDelegate = self.viewController;
-  [findBarCoordinator start];
+  self.findBarCoordinator = [self newFindBarCoordinator];
+  [self.findBarCoordinator start];
 }
 
 - (void)closeFindInPage {
@@ -1539,6 +1532,7 @@
       findTabHelper->StopFinding();
     } else {
       [self.findBarCoordinator stop];
+      self.findBarCoordinator = nil;
     }
   }
 }
@@ -1548,13 +1542,16 @@
       self.browser->GetWebStateList()->GetActiveWebState();
   auto* findHelper = FindTabHelper::FromWebState(currentWebState);
   if (findHelper && findHelper->IsFindUIActive() &&
-      !self.findBarCoordinator.presenter.isPresenting) {
+      !_toolbarAccessoryPresenter.isPresenting) {
+    DCHECK(!self.findBarCoordinator);
+    self.findBarCoordinator = [self newFindBarCoordinator];
     [self.findBarCoordinator start];
   }
 }
 
 - (void)hideFindUI {
   [self.findBarCoordinator stop];
+  self.findBarCoordinator = nil;
 }
 
 - (void)defocusFindInPage {
@@ -1604,6 +1601,18 @@
           !helper->IsFindUIActive());
 }
 
+- (FindBarCoordinator*)newFindBarCoordinator {
+  FindBarCoordinator* findBarCoordinator =
+      [[FindBarCoordinator alloc] initWithBaseViewController:self.viewController
+                                                     browser:self.browser];
+
+  findBarCoordinator.presenter = _toolbarAccessoryPresenter;
+  findBarCoordinator.delegate = self;
+  findBarCoordinator.presentationDelegate = self.viewController;
+
+  return findBarCoordinator;
+}
+
 #pragma mark - PromosManagerCommands
 
 - (void)maybeDisplayPromo {
@@ -1759,14 +1768,8 @@
     self.textZoomCoordinator = nil;
   }
 
-  textZoomCoordinator = [[TextZoomCoordinator alloc]
-      initWithBaseViewController:self.viewController
-                         browser:self.browser];
-  self.textZoomCoordinator = textZoomCoordinator;
-
-  textZoomCoordinator.presenter = _toolbarAccessoryPresenter;
-  textZoomCoordinator.delegate = self;
-  [textZoomCoordinator start];
+  self.textZoomCoordinator = [self newTextZoomCoordinator];
+  [self.textZoomCoordinator start];
 }
 
 - (void)closeTextZoom {
@@ -1780,6 +1783,7 @@
     }
   }
   [self.textZoomCoordinator stop];
+  self.textZoomCoordinator = nil;
 }
 
 - (void)showTextZoomUIIfActive {
@@ -1792,13 +1796,26 @@
   FontSizeTabHelper* fontSizeTabHelper =
       FontSizeTabHelper::FromWebState(currentWebState);
   if (fontSizeTabHelper && fontSizeTabHelper->IsTextZoomUIActive() &&
-      !self.textZoomCoordinator.presenter.isPresenting) {
+      !_toolbarAccessoryPresenter.isPresenting) {
+    DCHECK(!self.textZoomCoordinator);
+    self.textZoomCoordinator = [self newTextZoomCoordinator];
     [self.textZoomCoordinator start];
   }
 }
 
 - (void)hideTextZoomUI {
   [self.textZoomCoordinator stop];
+  self.textZoomCoordinator = nil;
+}
+
+- (TextZoomCoordinator*)newTextZoomCoordinator {
+  TextZoomCoordinator* textZoomCoordinator = [[TextZoomCoordinator alloc]
+      initWithBaseViewController:self.viewController
+                         browser:self.browser];
+  textZoomCoordinator.presenter = _toolbarAccessoryPresenter;
+  textZoomCoordinator.delegate = self;
+
+  return textZoomCoordinator;
 }
 
 #pragma mark - URLLoadingServiceDelegate
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator_unittest.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator_unittest.mm
index 0325da48..c8ee5c8 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator_unittest.mm
@@ -62,6 +62,10 @@
     test_cbs_builder.AddTestingFactory(
         ios::TemplateURLServiceFactory::GetInstance(),
         ios::TemplateURLServiceFactory::GetDefaultFactory());
+    test_cbs_builder.AddTestingFactory(
+        ReadingListModelFactory::GetInstance(),
+        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
+                            std::vector<ReadingListEntry>()));
     chrome_browser_state_ = test_cbs_builder.Build();
     large_icon_service_.reset(new favicon::LargeIconServiceImpl(
         &mock_favicon_service_, nullptr, 32, favicon_base::IconType::kTouchIcon,
@@ -69,7 +73,7 @@
     browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
     web_state_list_ = browser_->GetWebStateList();
     fake_web_state_ = std::make_unique<web::FakeWebState>();
-    InitializeReadingListModel();
+    fake_web_state_->SetBrowserState(chrome_browser_state_.get());
     dispatcher_ =
         OCMProtocolMock(@protocol(ContentSuggestionsMediatorDispatcher));
     consumer_ = OCMProtocolMock(@protocol(ContentSuggestionsConsumer));
@@ -119,15 +123,6 @@
     return test_web_state;
   }
 
-  // Initialize reading list model and its required tab helpers.
-  void InitializeReadingListModel() {
-    fake_web_state_->SetBrowserState(chrome_browser_state_.get());
-    ReadingListModelFactory::GetInstance()->SetTestingFactoryAndUse(
-        chrome_browser_state_.get(),
-        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
-                            std::vector<ReadingListEntry>()));
-  }
-
   web::WebTaskEnvironment task_environment_;
   sync_preferences::TestingPrefServiceSyncable pref_service_;
   IOSChromeScopedTestingLocalState local_state_;
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 14bf3a82..acf55219 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -1192,6 +1192,8 @@
   AppLaunchConfiguration config = [self appConfigurationForTestCase];
   config.relaunch_policy = ForceRelaunchByCleanShutdown;
   config.features_disabled.push_back(kTrendingQueriesModule);
+  // TODO(crbug.com/1403077): Reenable the discover feed sync promo feature
+  config.features_disabled.push_back(kEnableDiscoverFeedTopSyncPromo);
   [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
 
   [self
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
index 7b87f53..dfe8373 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
@@ -932,8 +932,8 @@
     [self cleanUpCollectionViewConstraints];
 
     [NSLayoutConstraint activateConstraints:@[
-      [self.feedHeaderViewController.view.leadingAnchor
-          constraintEqualToAnchor:self.collectionView.leadingAnchor],
+      [self.feedHeaderViewController.view.leftAnchor
+          constraintEqualToAnchor:self.collectionView.leftAnchor],
       [self.feedHeaderViewController.view.widthAnchor
           constraintEqualToAnchor:self.collectionView.widthAnchor],
       [self.collectionView.centerXAnchor
@@ -945,8 +945,8 @@
     if (IsDiscoverFeedTopSyncPromoEnabled() &&
         self.feedTopSectionViewController) {
       [NSLayoutConstraint activateConstraints:@[
-        [self.feedTopSectionViewController.view.leadingAnchor
-            constraintEqualToAnchor:self.collectionView.leadingAnchor],
+        [self.feedTopSectionViewController.view.leftAnchor
+            constraintEqualToAnchor:self.collectionView.leftAnchor],
         [self.feedTopSectionViewController.view.widthAnchor
             constraintEqualToAnchor:self.collectionView.widthAnchor],
         [self.feedTopSectionViewController.view.topAnchor
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
index 68fa295..cf4ae1f 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
@@ -95,7 +95,7 @@
   return nil;
 }
 
-// Returns visit Copied Link button matcher from UIMenuController.
+// Returns Visit Copied Link button matcher from UIMenuController.
 id<GREYMatcher> VisitCopiedLinkButton() {
   NSString* a11yLabelCopiedLink =
       l10n_util::GetNSString(IDS_IOS_VISIT_COPIED_LINK);
@@ -160,6 +160,20 @@
   return grey_accessibilityHint(a11yHintPasteButton);
 }
 
+// Returns Copy button from the context menu.
+id<GREYMatcher> CopyContextMenuButton() {
+  return grey_allOf(
+      grey_accessibilityLabel(l10n_util::GetNSString(IDS_IOS_SHARE_MENU_COPY)),
+      grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
+}
+
+// Returns Visit Copied Link button from the context menu.
+id<GREYMatcher> VisitCopiedLinkContextMenuButton() {
+  return grey_allOf(grey_accessibilityLabel(
+                        l10n_util::GetNSString(IDS_IOS_VISIT_COPIED_LINK)),
+                    grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
+}
+
 // Taps the fake omnibox and waits for the real omnibox to be visible.
 void FocusFakebox() {
   [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
@@ -170,7 +184,10 @@
 
 }  //  namespace
 
-@interface OmniboxTestCase : ChromeTestCase
+@interface OmniboxTestCase : ChromeTestCase {
+  GURL _URL1;
+}
+
 @end
 
 @implementation OmniboxTestCase
@@ -182,6 +199,9 @@
   self.testServer->RegisterRequestHandler(
       base::BindRepeating(&StandardResponse));
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
+
+  _URL1 = self.testServer->GetURL(kPage1URL);
+
   [ChromeEarlGrey clearPasteboard];
   [ChromeEarlGrey clearBrowsingHistory];
 }
@@ -293,9 +313,7 @@
 - (void)testOmniboxMenuPasteURLToSearch {
   FocusFakebox();
   // Copy URL into clipboard.
-  NSString* URL =
-      base::SysUTF8ToNSString(self.testServer->GetURL(kPage1URL).spec());
-  [ChromeEarlGrey copyTextToPasteboard:URL];
+  [ChromeEarlGrey copyTextToPasteboard:base::SysUTF8ToNSString(_URL1.spec())];
   // Tap Visit Copied Link menu button.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
       performAction:grey_longPress()];
@@ -307,8 +325,7 @@
 
 // Tests that Search Copied Image menu button is shown with an image in the
 // clipboard and is starting an image search.
-// Flaky. crbug.com/1361857
-- (void)DISABLED_testOmniboxMenuPasteImageToSearch {
+- (void)testOmniboxMenuPasteImageToSearch {
   [self copyImageIntoClipboard];
 
   // Wait for the context menu to dismiss, so the omnibox can be tapped.
@@ -322,6 +339,7 @@
       performAction:grey_longPress()];
   [[EarlGrey selectElementWithMatcher:SearchCopiedImageButton()]
       performAction:grey_tap()];
+
   // Check that the omnibox started a google search.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
       assertWithMatcher:chrome_test_util::OmniboxContainingText("google")];
@@ -359,9 +377,7 @@
                                     disabled:{}
                               relaunchPolicy:ForceRelaunchByCleanShutdown];
     FocusFakebox();
-    NSString* URL =
-        base::SysUTF8ToNSString(self.testServer->GetURL(kPage1URL).spec());
-    [ChromeEarlGrey copyTextToPasteboard:URL];
+    [ChromeEarlGrey copyTextToPasteboard:base::SysUTF8ToNSString(_URL1.spec())];
 
     [[EarlGrey selectElementWithMatcher:PasteToSearchButton()]
         performAction:grey_tap()];
@@ -399,8 +415,11 @@
 
 #pragma mark - Steady state tests
 
-@interface LocationBarSteadyStateTestCase : ChromeTestCase
-- (void)testFocusingOmniboxDismissesEditMenu;
+@interface LocationBarSteadyStateTestCase : ChromeTestCase {
+  GURL _URL1;
+  GURL _URL2;
+}
+
 @end
 
 @implementation LocationBarSteadyStateTestCase
@@ -413,6 +432,9 @@
       base::BindRepeating(&StandardResponse));
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
 
+  _URL1 = self.testServer->GetURL(kPage1URL);
+  _URL2 = self.testServer->GetURL(kPage2URL);
+
   [ChromeEarlGrey clearBrowsingHistory];
 
   // Clear the pasteboard in case there is a URL copied.
@@ -420,16 +442,6 @@
   [pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
 }
 
-- (AppLaunchConfiguration)appConfigurationForTestCase {
-  AppLaunchConfiguration config;
-
-  if ([self isRunningTest:@selector(testFocusingOmniboxDismissesEditMenu)]) {
-    config.features_disabled.push_back(kIOSLocationBarUseNativeContextMenu);
-  }
-
-  return config;
-}
-
 // Tapping on steady view starts editing.
 - (void)testTapSwitchesToEditing {
   [self openPage1];
@@ -441,9 +453,7 @@
 // Tests that in compact, a share button is visible.
 // Voice search is not enabled on the bots, so the voice search button is
 // not tested here.
-// TODO(crbug.com/996541) Starting in Xcode 11 beta 6, the share button does
-// not appear (even with a delay) flakily.
-- (void)DISABLED_testTrailingButton {
+- (void)testTrailingButton {
   [self openPage1];
 
   if ([ChromeEarlGrey isCompactWidth]) {
@@ -452,22 +462,20 @@
   }
 }
 
-// TODO(crbug.com/1056700): Test is flaky
-- (void)DISABLED_testCopyPaste {
+- (void)testCopyPaste {
   [self openPage1];
 
   // Long pressing should allow copying.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
       performAction:grey_longPress()];
 
-  // Verify that system text selection callout is displayed.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::
-                                          SystemSelectionCalloutCopyButton()]
+  // Verify that the Copy button is displayed.
+  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
       assertWithMatcher:grey_notNil()];
 
   // Pressing should not allow pasting when pasteboard is empty.
   // Verify that system text selection callout is not displayed.
-  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
+  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
       assertWithMatcher:grey_nil()];
   [[EarlGrey selectElementWithMatcher:SearchCopiedTextButton()]
       assertWithMatcher:grey_nil()];
@@ -477,21 +485,9 @@
   [self checkLocationBarSteadyState];
 
   // Tapping it should copy the URL.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::
-                                          SystemSelectionCalloutCopyButton()]
+  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
       performAction:grey_tap()];
-
-  // Edit menu takes a while to copy, and not waiting here will cause Page 2 to
-  // load before the copy happens, so Page 2 URL may be copied.
-  GREYCondition* copyCondition = [GREYCondition
-      conditionWithName:@"page1 URL copied condition"
-                  block:^BOOL {
-                    return [UIPasteboard.generalPasteboard.string
-                        hasSuffix:base::SysUTF8ToNSString(kPage1URL)];
-                  }];
-  // Wait for copy to happen or timeout after 5 seconds.
-  GREYAssertTrue([copyCondition waitWithTimeout:5],
-                 @"Copying page 1 URL failed");
+  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];
 
   // Go to another web page.
   [self openPage2];
@@ -499,37 +495,47 @@
   // Visit copied link should now be available.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
       performAction:grey_longPress()];
-  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
+  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
       assertWithMatcher:grey_notNil()];
 
   [self checkLocationBarSteadyState];
 
   // Tapping it should navigate to Page 1.
-  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
+  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
       performAction:grey_tap()];
 
   [ChromeEarlGrey waitForPageToFinishLoading];
   [ChromeEarlGrey waitForWebStateContainingText:kPage1];
 }
 
-- (void)testFocusingOmniboxDismissesEditMenu {
+- (void)testDismissesEditMenu {
   [self openPage1];
 
   // Long pressing should open edit menu.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
       performAction:grey_longPress()];
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::
-                                          SystemSelectionCalloutCopyButton()]
+  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
       assertWithMatcher:grey_notNil()];
 
-  // Focus omnibox.
-  [ChromeEarlGreyUI focusOmnibox];
-  [self checkLocationBarEditState];
+  // Dismiss context menu.
+  GREYAssertTrue([ChromeEarlGreyUI dismissContextMenuIfPresent],
+                 @"Failed to dismiss context menu.");
 
-  // Verify that the edit menu disappeared.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::
-                                          SystemSelectionCalloutCopyButton()]
-      assertWithMatcher:grey_nil()];
+  GREYCondition* contextMenuDismissed = [GREYCondition
+      conditionWithName:@"Wait for context menu to be dismissed"
+                  block:^BOOL {
+                    NSError* error;
+                    [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
+                        assertWithMatcher:grey_nil()
+                                    error:&error];
+                    return error == nil;
+                  }];
+
+  // Verify that the context menu disappeared.
+  GREYAssertTrue([contextMenuDismissed
+                     waitWithTimeout:base::test::ios::kWaitForUIElementTimeout
+                                         .InSecondsF()],
+                 @"Context menu is still visible.");
 }
 
 // Copies and pastes a URL, then performs an undo of the paste, and attempts to
@@ -606,17 +612,7 @@
 
   [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"X"
                                           flags:UIKeyModifierCommand];
-
-  // It takes a while to copy, and not waiting here will cause the test to fail.
-  GREYCondition* copyCondition = [GREYCondition
-      conditionWithName:@"page1 URL copied condition"
-                  block:^BOOL {
-                    return [UIPasteboard.generalPasteboard.string
-                        hasSuffix:base::SysUTF8ToNSString(kPage1URL)];
-                  }];
-  // Wait for copy to happen or timeout after 5 seconds.
-  GREYAssertTrue([copyCondition waitWithTimeout:5],
-                 @"Copying page 1 URL failed");
+  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];
 
   // Verify that the omnibox is empty.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
@@ -693,14 +689,14 @@
 // Navigates to Page 1 in a tab and waits for it to load.
 - (void)openPage1 {
   // Go to a web page to have a normal location bar.
-  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPage1URL)];
+  [ChromeEarlGrey loadURL:_URL1];
   [ChromeEarlGrey waitForWebStateContainingText:kPage1];
 }
 
 // Navigates to Page 2 in a tab and waits for it to load.
 - (void)openPage2 {
   // Go to a web page to have a normal location bar.
-  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPage2URL)];
+  [ChromeEarlGrey loadURL:_URL2];
   [ChromeEarlGrey waitForWebStateContainingText:kPage2];
 }
 
@@ -739,8 +735,13 @@
 // displayed. Paste button should be hidden when pasteboard is empty otherwise
 // it should be displayed. Select & SelectAll buttons should be hidden when the
 // omnibox is empty.
-// TODO(crbug.com/1209342): test failing on device
-- (void)DISABLED_testEmptyOmnibox {
+- (void)testEmptyOmnibox {
+  // TODO(crbug.com/1209342): this test fails on iOS 15 devices.
+  if (base::ios::IsRunningOnIOS15OrLater() &&
+      !base::ios::IsRunningOnIOS16OrLater()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15.");
+  }
+
   // Focus omnibox.
   [self focusFakebox];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
@@ -836,12 +837,6 @@
 // If the selected text is the entire omnibox field, select & SelectAll button
 // should be hidden.
 - (void)testSelection {
-// TODO(crbug.com/1209342): test failing on ipad device
-#if !TARGET_IPHONE_SIMULATOR
-  if ([ChromeEarlGrey isIPadIdiom]) {
-    EARL_GREY_TEST_SKIPPED(@"This test doesn't pass on iPad device.");
-  }
-#endif
   // Focus omnibox.
   [self focusFakebox];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
@@ -892,12 +887,12 @@
 }
 
 - (void)testNoDefaultMatch {
-  // TODO(crbug.com/1253345) Re-enable this test for iOS 15 and earlier. There
-  // is currently a problem with the test on iOS 15 devices where copying to the
-  // pasteboard fails.
-  if (!base::ios::IsRunningOnIOS16OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15 and earlier.");
+  // TODO(crbug.com/1253345) This test fails on iOS 15 devices.
+  if (base::ios::IsRunningOnIOS15OrLater() &&
+      !base::ios::IsRunningOnIOS16OrLater()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15.");
   }
+
   NSString* copiedText = @"test no default match1";
 
   // Put some text in pasteboard.
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/permissions/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_banner/permissions/BUILD.gn
index 97fdcf55..3527b00 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/permissions/BUILD.gn
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/permissions/BUILD.gn
@@ -34,9 +34,9 @@
     "//components/infobars/core",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/infobars:infobars",
-    "//ios/chrome/browser/infobars/overlays:infobar_delegates",
     "//ios/chrome/browser/overlays/public/infobar_banner",
     "//ios/chrome/browser/overlays/test",
+    "//ios/chrome/browser/permissions:infobar_delegate",
     "//ios/chrome/browser/ui/infobars/banners",
     "//ios/chrome/browser/ui/infobars/banners/test",
     "//ios/web/public/permissions",
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/permissions/permissions_infobar_banner_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_banner/permissions/permissions_infobar_banner_overlay_mediator_unittest.mm
index 66c870a..514e011 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/permissions/permissions_infobar_banner_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/permissions/permissions_infobar_banner_overlay_mediator_unittest.mm
@@ -5,10 +5,10 @@
 #import "ios/chrome/browser/ui/overlays/infobar_banner/permissions/permissions_infobar_banner_overlay_mediator.h"
 
 #import "ios/chrome/browser/infobars/infobar_ios.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
 #import "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h"
 #import "ios/chrome/browser/overlays/public/infobar_banner/permissions_infobar_banner_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/test/fake_overlay_request_callback_installer.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/permissions/permissions.h"
@@ -40,8 +40,8 @@
   NSArray<NSNumber*>* recently_accessible_permissions =
       @[ @(web::PermissionCamera), @(web::PermissionMicrophone) ];
   // Second parameter is used for modal; not needed for this test.
-  std::unique_ptr<PermissionsOverlayInfobarDelegate> delegate =
-      std::make_unique<PermissionsOverlayInfobarDelegate>(
+  std::unique_ptr<PermissionsInfobarDelegate> delegate =
+      std::make_unique<PermissionsInfobarDelegate>(
           recently_accessible_permissions, nullptr);
   InfoBarIOS infobar(InfobarType::kInfobarTypePermissions, std::move(delegate));
 
@@ -68,8 +68,8 @@
   NSArray<NSNumber*>* recently_accessible_permissions =
       @[ @(web::PermissionCamera) ];
   // Second parameter is used for modal; not needed for this test.
-  std::unique_ptr<PermissionsOverlayInfobarDelegate> delegate =
-      std::make_unique<PermissionsOverlayInfobarDelegate>(
+  std::unique_ptr<PermissionsInfobarDelegate> delegate =
+      std::make_unique<PermissionsInfobarDelegate>(
           recently_accessible_permissions, nullptr);
   InfoBarIOS infobar(InfobarType::kInfobarTypePermissions, std::move(delegate));
 
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/permissions/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_modal/permissions/BUILD.gn
index e2b3710..d16c7051 100644
--- a/ios/chrome/browser/ui/overlays/infobar_modal/permissions/BUILD.gn
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/permissions/BUILD.gn
@@ -44,10 +44,10 @@
     "//components/infobars/core",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/infobars:infobars",
-    "//ios/chrome/browser/infobars/overlays:infobar_delegates",
     "//ios/chrome/browser/overlays:overlays",
     "//ios/chrome/browser/overlays/public/infobar_modal/permissions",
     "//ios/chrome/browser/overlays/test",
+    "//ios/chrome/browser/permissions:infobar_delegate",
     "//ios/chrome/browser/ui/permissions",
     "//ios/chrome/browser/ui/permissions:permission_info",
     "//ios/web/public/navigation",
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/permissions/permissions_infobar_modal_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_modal/permissions/permissions_infobar_modal_overlay_mediator_unittest.mm
index efe33da..89c7725 100644
--- a/ios/chrome/browser/ui/overlays/infobar_modal/permissions/permissions_infobar_modal_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/permissions/permissions_infobar_modal_overlay_mediator_unittest.mm
@@ -5,9 +5,9 @@
 #import "ios/chrome/browser/ui/overlays/infobar_modal/permissions/permissions_infobar_modal_overlay_mediator.h"
 
 #import "ios/chrome/browser/infobars/infobar_ios.h"
-#import "ios/chrome/browser/infobars/overlays/permissions_overlay_infobar_delegate.h"
 #import "ios/chrome/browser/overlays/public/infobar_modal/permissions/permissions_modal_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/public/overlay_request.h"
+#import "ios/chrome/browser/permissions/permissions_infobar_delegate.h"
 #import "ios/chrome/browser/ui/permissions/permission_info.h"
 #import "ios/chrome/browser/ui/permissions/permissions_consumer.h"
 #import "ios/chrome/grit/ios_strings.h"
@@ -68,9 +68,9 @@
       navigation_manager->SetVisibleItem(item.get());
       web_state_.SetNavigationManager(std::move(navigation_manager));
       // First parameter is used for banner; not needed for this test.
-      std::unique_ptr<PermissionsOverlayInfobarDelegate> delegate =
-          std::make_unique<PermissionsOverlayInfobarDelegate>([NSArray array],
-                                                              &web_state_);
+      std::unique_ptr<PermissionsInfobarDelegate> delegate =
+          std::make_unique<PermissionsInfobarDelegate>([NSArray array],
+                                                       &web_state_);
       InfoBarIOS infobar(InfobarType::kInfobarTypePermissions,
                          std::move(delegate));
       request_ = OverlayRequest::CreateWithConfig<
diff --git a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h
index ccd52f8..662a67ad 100644
--- a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h
+++ b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h
@@ -11,6 +11,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
 #include "google_apis/gaia/google_service_auth_error.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "testing/platform_test.h"
@@ -19,10 +20,6 @@
 class MockSyncService;
 }  // namespace syncer
 
-namespace web {
-class BrowserState;
-}  // namespace web
-
 @class AppState;
 class Browser;
 @class SceneState;
@@ -35,9 +32,6 @@
 // supporting structure they require.
 class PassphraseTableViewControllerTest : public ChromeTableViewControllerTest {
  public:
-  static std::unique_ptr<KeyedService> CreateNiceMockSyncService(
-      web::BrowserState* context);
-
   PassphraseTableViewControllerTest();
   ~PassphraseTableViewControllerTest() override;
 
@@ -45,6 +39,11 @@
   void SetUp() override;
   void TearDown() override;
 
+  // Allow sub-classes to register testing factories in the builder for the
+  // new TestChromeBrowserState.
+  virtual void RegisterTestingFactories(
+      TestChromeBrowserState::Builder& builder);
+
   void SetUpNavigationController(UIViewController* test_controller);
 
   web::WebTaskEnvironment task_environment_;
diff --git a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
index 4c88fee47..aa708f95 100644
--- a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
+++ b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
@@ -44,6 +44,8 @@
 using testing::NiceMock;
 using testing::Return;
 
+namespace {
+
 std::unique_ptr<sync_preferences::PrefServiceSyncable> CreatePrefService() {
   sync_preferences::PrefServiceMockFactory factory;
   scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
@@ -54,12 +56,13 @@
   return prefs;
 }
 
-std::unique_ptr<KeyedService>
-PassphraseTableViewControllerTest::CreateNiceMockSyncService(
+std::unique_ptr<KeyedService> CreateNiceMockSyncService(
     web::BrowserState* context) {
   return std::make_unique<NiceMock<syncer::MockSyncService>>();
 }
 
+}  // anonymous namespace
+
 PassphraseTableViewControllerTest::PassphraseTableViewControllerTest()
     : ChromeTableViewControllerTest(),
       fake_sync_service_(NULL),
@@ -79,11 +82,12 @@
       AuthenticationServiceFactory::GetInstance(),
       AuthenticationServiceFactory::GetDefaultFactory());
   test_cbs_builder.AddTestingFactory(
-      SyncServiceFactory::GetInstance(),
-      base::BindRepeating(&CreateMockSyncService));
-  test_cbs_builder.AddTestingFactory(
       SyncSetupServiceFactory::GetInstance(),
       base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService));
+  test_cbs_builder.AddTestingFactory(
+      SyncServiceFactory::GetInstance(),
+      base::BindRepeating(&CreateNiceMockSyncService));
+  RegisterTestingFactories(test_cbs_builder);
   test_cbs_builder.SetPrefService(CreatePrefService());
   chrome_browser_state_ = test_cbs_builder.Build();
   AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
@@ -97,9 +101,7 @@
   SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
 
   fake_sync_service_ = static_cast<syncer::MockSyncService*>(
-      SyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-          chrome_browser_state_.get(),
-          base::BindRepeating(&CreateNiceMockSyncService)));
+      SyncServiceFactory::GetForBrowserState(chrome_browser_state_.get()));
 
   // Set up non-default return values for our sync service mock.
   ON_CALL(*fake_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
@@ -128,6 +130,11 @@
   ChromeTableViewControllerTest::TearDown();
 }
 
+void PassphraseTableViewControllerTest::RegisterTestingFactories(
+    TestChromeBrowserState::Builder& builder) {
+  // nothing to do, this is for sub-classes to override
+}
+
 void PassphraseTableViewControllerTest::SetUpNavigationController(
     UIViewController* test_controller) {
   dummy_controller_ = [[UIViewController alloc] init];
diff --git a/ios/chrome/browser/ui/settings/password/legacy_password_settings_egtest.mm b/ios/chrome/browser/ui/settings/password/legacy_password_settings_egtest.mm
index 2f700d8a..12b9d110 100644
--- a/ios/chrome/browser/ui/settings/password/legacy_password_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/legacy_password_settings_egtest.mm
@@ -411,9 +411,15 @@
   AppLaunchConfiguration config;
   config.relaunch_policy = NoForceRelaunchAndResetState;
 
+  // Versions of these tests running against UI Split are found in
+  // password_manager_egtest.mm
   config.features_disabled.push_back(
       password_manager::features::kIOSPasswordUISplit);
 
+  // Grouping is intended to work with UI Split on.
+  config.features_disabled.push_back(
+      password_manager::features::kPasswordsGrouping);
+
   if ([self isRunningTest:@selector
             (testNoOndeviceEncryptionSetupWhenSignedOut)]) {
     config.features_enabled.push_back(syncer::kSyncTrustedVaultPassphrasePromo);
@@ -1382,12 +1388,12 @@
 // Test that when user types text in search field, passwords and blocked
 // items are filtered out and "save passwords" switch is removed.
 - (void)testSearchPasswords {
-// TODO(crbug.com/1067818): Test doesn't pass on iPad device.
-#if !TARGET_IPHONE_SIMULATOR
+  // TODO(crbug.com/1067818): Test doesn't pass on iPad device or simulator.
   if ([ChromeEarlGrey isIPadIdiom]) {
-    EARL_GREY_TEST_SKIPPED(@"This test doesn't pass on iPad device.");
+    EARL_GREY_TEST_SKIPPED(
+        @"This test doesn't pass on iPad device or simulator.");
   }
-#endif
+
   SaveExamplePasswordForms();
   SaveExampleBlockedForms();
 
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
index 4f3e846..b132831 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
@@ -7,6 +7,7 @@
 #import "base/strings/string_piece.h"
 #import "base/strings/string_util.h"
 #import "base/strings/utf_string_conversions.h"
+#import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/password_manager_test_utils.h"
 #import "components/password_manager/core/browser/test_password_store.h"
 #import "components/password_manager/core/browser/ui/credential_ui_entry.h"
@@ -45,18 +46,6 @@
 using password_manager::InsecureCredential;
 using password_manager::TestPasswordStore;
 
-// Sets test password store and returns pointer to it.
-scoped_refptr<TestPasswordStore> CreateAndUseTestPasswordStore(
-    ChromeBrowserState* _browserState) {
-  return base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-      IOSChromePasswordStoreFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              _browserState,
-              base::BindRepeating(&password_manager::BuildPasswordStore<
-                                  web::BrowserState, TestPasswordStore>))
-          .get()));
-}
-
 }  // namespace
 
 // Test class that conforms to PasswordIssuesConsumer in order to test the
@@ -88,9 +77,18 @@
     test_cbs_builder.AddTestingFactory(
         SyncSetupServiceFactory::GetInstance(),
         base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService));
+    test_cbs_builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<web::BrowserState,
+                                                  TestPasswordStore>));
     chrome_browser_state_ = test_cbs_builder.Build();
 
-    store_ = CreateAndUseTestPasswordStore(chrome_browser_state_.get());
+    store_ =
+        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
+                .get()));
 
     password_check_ = IOSChromePasswordCheckManagerFactory::GetForBrowserState(
         chrome_browser_state_.get());
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
index e209c52..2c442ce 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
@@ -398,6 +398,13 @@
 
 // Various tests for the main Password Manager UI.
 @interface PasswordManagerTestCase : ChromeTestCase
+
+- (BOOL)groupingEnabled;
+
+- (GREYElementInteraction*)
+    interactionForSinglePasswordEntryWithDomain:(NSString*)domain
+                                       username:(NSString*)username;
+
 @end
 
 @implementation PasswordManagerTestCase {
@@ -405,6 +412,19 @@
   std::unique_ptr<EarlGreyScopedBlockSwizzler> _passwordAutoFillStatusSwizzler;
 }
 
+- (BOOL)groupingEnabled {
+  return YES;
+}
+
+- (GREYElementInteraction*)
+    interactionForSinglePasswordEntryWithDomain:(NSString*)domain
+                                       username:(NSString*)username {
+  // With grouping enabled, discard the username; it's only shown on the details
+  // page.
+  return GetInteractionForListItem(ButtonWithAccessibilityLabel(domain),
+                                   kGREYDirectionDown);
+}
+
 - (void)setUp {
   [super setUp];
   GREYAssertNil([MetricsAppInterface setupHistogramTester],
@@ -440,6 +460,14 @@
   config.features_enabled.push_back(
       password_manager::features::kIOSPasswordUISplit);
 
+  if ([self groupingEnabled]) {
+    config.features_enabled.push_back(
+        password_manager::features::kPasswordsGrouping);
+  } else {
+    config.features_disabled.push_back(
+        password_manager::features::kPasswordsGrouping);
+  }
+
   if ([self isRunningTest:@selector
             (testNoOndeviceEncryptionSetupWhenSignedOut)]) {
     config.features_enabled.push_back(syncer::kSyncTrustedVaultPassphrasePromo);
@@ -482,6 +510,10 @@
 
 // Verifies the UI elements are accessible on the Passwords page.
 - (void)testAccessibilityOnPasswords {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   // Saving a form is needed for using the "password details" view.
   SaveExamplePasswordForm();
 
@@ -494,7 +526,8 @@
       performAction:grey_tap()];
 
   // Inspect "password details" view.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
   [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -514,7 +547,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -558,7 +592,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -588,7 +623,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -622,7 +658,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   CopyPasswordDetailWithID(IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME);
@@ -647,7 +684,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   CopyPasswordDetailWithID(IDS_IOS_SHOW_PASSWORD_VIEW_SITE);
@@ -674,7 +712,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -703,7 +742,8 @@
                   @"Stored password was not removed from PasswordStore.");
 
   // Also verify that the removed password is no longer in the list.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
 
   // Finally, verify that the Add button is visible and enabled, because there
@@ -729,7 +769,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -758,7 +799,8 @@
                   @"Stored password was not removed from PasswordStore.");
 
   // Also verify that the removed password is no longer in the list.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
 
   // Verify blocked sites are still there.
@@ -777,6 +819,11 @@
 // goes back to the list-of-passwords view which doesn't display that form
 // anymore.
 - (void)testDuplicatedSavedFormDeletionInDetailView {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
+
   // Save form to be deleted later.
   SaveExamplePasswordForm();
   // Save duplicate of the previously saved form to be deleted at the same time.
@@ -790,7 +837,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -819,7 +867,8 @@
                   @"Stored password was not removed from PasswordStore.");
 
   // Also verify that the removed password is no longer in the list.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
 
   // Finally, verify that the Add button is visible and enabled, because there
@@ -837,6 +886,10 @@
 // Checks that deleting a blocked form from password details view goes
 // back to the list-of-passwords view which doesn't display that form anymore.
 - (void)testBlockedFormDeletionInDetailView {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   // Save blocked form to be deleted later.
   GREYAssert([PasswordSettingsAppInterface
                  saveExampleBlockedOrigin:@"https://blocked.com"],
@@ -887,6 +940,10 @@
 // back to the list-of-passwords view which only displays a previously saved
 // password.
 - (void)testBlockedFormDeletionInDetailViewWithSavedForm {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   // Save blocked form to be deleted later.
   GREYAssert([PasswordSettingsAppInterface
                  saveExampleBlockedOrigin:@"https://blocked.com"],
@@ -924,7 +981,8 @@
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
 
   // Verify existing saved password is still in the list.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       assertWithMatcher:grey_sufficientlyVisible()];
 
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -940,7 +998,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -972,7 +1031,8 @@
       performAction:grey_tap()];
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
       performAction:grey_tap()];
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       assertWithMatcher:grey_sufficientlyVisible()];
 
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -991,7 +1051,8 @@
 
   TapEdit();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check that the current view is not the detail view, by failing to locate
@@ -1013,7 +1074,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Tap the password cell to display the context menu.
@@ -1050,6 +1112,10 @@
 
 // Checks that federated credentials have no password but show the federation.
 - (void)testFederated {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   GREYAssert([PasswordSettingsAppInterface
                  saveExampleFederatedOrigin:@"https://famous.provider.net"
                                    userName:@"federated username"
@@ -1058,7 +1124,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, federated username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"federated username"]
       performAction:grey_tap()];
 
   // Check that the Site and Username are present and correct.
@@ -1100,7 +1167,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:PasswordDetailWebsite()]
@@ -1166,7 +1234,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, federated username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"federated username"]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:PasswordDetailWebsite()]
@@ -1230,7 +1299,8 @@
         performAction:grey_tap()];
 
     // Check the stored items. Scroll down if needed.
-    [GetInteractionForPasswordEntry(@"example.com, concrete username")
+    [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                              username:@"concrete username"]
         assertWithMatcher:grey_notNil()];
   }
 
@@ -1283,7 +1353,8 @@
   TapEdit();
 
   // Select password entry to be removed.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:DeleteButtonAtBottom()]
@@ -1310,7 +1381,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -1340,7 +1412,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -1383,6 +1456,10 @@
 // any device. To limit the effect of (2), custom large scrolling steps are
 // added to the usual scrolling actions.
 - (void)testManyPasswords {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   if ([ChromeEarlGrey isIPadIdiom]) {
     // TODO(crbug.com/906551): Enable the test on iPad once the bug is fixed.
     EARL_GREY_TEST_DISABLED(@"Disabled for iPad.");
@@ -1446,7 +1523,8 @@
   TapEdit();
 
   // Select password entry to be removed.
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [[EarlGrey selectElementWithMatcher:DeleteButtonAtBottom()]
@@ -1541,12 +1619,16 @@
 // Test that when user types text in search field, passwords and blocked
 // items are filtered out and "save passwords" switch is removed.
 - (void)testSearchPasswords {
-// TODO(crbug.com/1067818): Test doesn't pass on iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if ([ChromeEarlGrey isIPadIdiom]) {
-    EARL_GREY_TEST_SKIPPED(@"This test doesn't pass on iPad device.");
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
   }
-#endif
+  // TODO(crbug.com/1067818): Test doesn't pass on iPad device or simulator.
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test doesn't pass on iPad device or simulator.");
+  }
+
   SaveExamplePasswordForms();
   SaveExampleBlockedForms();
 
@@ -1588,6 +1670,10 @@
 
 // Test search and delete all passwords and blocked items.
 - (void)testSearchAndDeleteAllPasswords {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   SaveExamplePasswordForms();
   SaveExampleBlockedForms();
 
@@ -1649,6 +1735,10 @@
 
 // Test that the user can edit a password that is part of search results.
 - (void)testCanEditPasswordsFromASearch {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   SaveExamplePasswordForms();
   OpenPasswordManager();
 
@@ -1700,7 +1790,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -1751,7 +1842,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -1794,7 +1886,8 @@
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
       performAction:grey_tap()];
 
-  [GetInteractionForPasswordEntry(@"example.com, new username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"new username"]
       assertWithMatcher:grey_notNil()];
 
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -1806,6 +1899,11 @@
 // Checks that attempts to edit a username to a value which is already used for
 // the same domain fails.
 - (void)testEditUsernameFails {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
+
   GREYAssert(
       [PasswordSettingsAppInterface saveExamplePassword:@"concrete password"
                                                userName:@"concrete username1"
@@ -1820,7 +1918,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username1")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username1"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -1861,7 +1960,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -1894,6 +1994,10 @@
 
 // Tests that removing multiple passwords works fine.
 - (void)testRemovingMultiplePasswords {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   constexpr int kPasswordsCount = 4;
 
   // Send the passwords to the queue to be added to the PasswordStore.
@@ -1985,7 +2089,8 @@
   [[EarlGrey selectElementWithMatcher:AddPasswordSaveButton()]
       performAction:grey_tap()];
 
-  [GetInteractionForPasswordEntry(@"example.com, new username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"new username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -2094,7 +2199,8 @@
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
       performAction:grey_tap()];
 
-  [GetInteractionForPasswordEntry(@"example.com, new username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"new username"]
       assertWithMatcher:grey_notNil()];
 
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -2107,6 +2213,10 @@
 // a credential that has the same website as that of an existing credential
 // (does not contain username).
 - (void)testDuplicatedCredentialWithNoUsername {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   OpenPasswordManager();
 
   [[EarlGrey selectElementWithMatcher:AddPasswordToolbarButton()]
@@ -2149,7 +2259,8 @@
   [[EarlGrey selectElementWithMatcher:AddPasswordSaveButton()]
       performAction:grey_tap()];
 
-  [GetInteractionForPasswordEntry(@"example.com, new username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"new username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -2194,6 +2305,11 @@
 // Checks that deleting a compromised password from password issues goes back
 // to the list-of-issues which doesn't display that password anymore.
 - (void)testDeletePasswordIssue {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
+
   GREYAssert([PasswordSettingsAppInterface
                  saveInsecurePassword:@"concrete password"
                              userName:@"concrete username"
@@ -2210,7 +2326,8 @@
   [GetInteractionForPasswordEntry([NSString
       stringWithFormat:@"%@, %@", text, detailText]) performAction:grey_tap()];
 
-  [GetInteractionForPasswordIssueEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   [PasswordSettingsAppInterface setUpMockReauthenticationModule];
@@ -2246,7 +2363,8 @@
 
   OpenPasswordManager();
 
-  [GetInteractionForPasswordEntry(@"example.com, concrete username")
+  [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
+                                            username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
@@ -2272,6 +2390,10 @@
 // properly when there are passwords with a favicon.
 // TODO(crbug.com/1348585): Fix to re-enable.
 - (void)testLogFaviconsForPasswordsMetrics {
+  if ([self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test isn't implemented with grouped passwords yet.");
+  }
   // Sign-in and synced user.
   FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
   [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
@@ -2437,3 +2559,31 @@
 }
 
 @end
+
+// Rerun all the tests in this file but with kPasswordsGrouping disabled. This
+// will be removed once that feature launches fully, but ensures regressions
+// aren't introduced in the meantime.
+@interface PasswordManagerGroupingDisabledTestCase : PasswordManagerTestCase
+
+@end
+
+@implementation PasswordManagerGroupingDisabledTestCase
+
+- (BOOL)groupingEnabled {
+  return NO;
+}
+
+- (GREYElementInteraction*)
+    interactionForSinglePasswordEntryWithDomain:(NSString*)domain
+                                       username:(NSString*)username {
+  NSString* label = [NSString stringWithFormat:@"%@, %@", domain, username];
+  return GetInteractionForListItem(ButtonWithAccessibilityLabel(label),
+                                   kGREYDirectionDown);
+}
+
+// This causes the test case to actually be detected as a test case. The actual
+// tests are all inherited from the parent class.
+- (void)testEmpty {
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator_unittest.mm
index 3a28987..985948d 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator_unittest.mm
@@ -6,6 +6,7 @@
 
 #import "base/test/scoped_feature_list.h"
 #import "base/test/task_environment.h"
+#import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/affiliation/mock_affiliation_service.h"
 #import "components/password_manager/core/browser/password_manager_test_utils.h"
 #import "components/password_manager/core/browser/test_password_store.h"
@@ -55,26 +56,23 @@
       .WillByDefault(testing::Return(passphrase_type));
 }
 
-// Sets up a password store factory for testing, and returns the test store.
-scoped_refptr<TestPasswordStore> CreateAndUseTestPasswordStore(
-    ChromeBrowserState* browser_state) {
-  return base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-      IOSChromePasswordStoreFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              browser_state,
-              base::BindRepeating(&password_manager::BuildPasswordStore<
-                                  web::BrowserState, TestPasswordStore>))
-          .get()));
-}
-
 class PasswordSettingsMediatorTest : public PlatformTest {
  protected:
   void SetUp() override {
     TestChromeBrowserState::Builder builder;
+    builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<web::BrowserState,
+                                                  TestPasswordStore>));
     browser_state_ = builder.Build();
 
     password_manager::MockAffiliationService affiliation_service_;
-    store_ = CreateAndUseTestPasswordStore(browser_state_.get());
+    store_ =
+        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
+                .get()));
     presenter_ = std::make_unique<SavedPasswordsPresenter>(
         &affiliation_service_, store_, /*accont_store=*/nullptr);
 
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
index 73ae4239..be880e1c 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
@@ -9,6 +9,7 @@
 #import "base/strings/string_util.h"
 #import "base/strings/utf_string_conversions.h"
 #import "base/test/scoped_feature_list.h"
+#import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/password_manager_test_utils.h"
 #import "components/password_manager/core/browser/test_password_store.h"
 #import "components/password_manager/core/browser/ui/credential_ui_entry.h"
@@ -46,18 +47,6 @@
 using password_manager::InsecureType;
 using password_manager::TestPasswordStore;
 
-// Sets test password store and returns pointer to it.
-scoped_refptr<TestPasswordStore> BuildTestPasswordStore(
-    ChromeBrowserState* _browserState) {
-  return base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-      IOSChromePasswordStoreFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              _browserState,
-              base::BindRepeating(&password_manager::BuildPasswordStore<
-                                  web::BrowserState, TestPasswordStore>))
-          .get()));
-}
-
 // Creates a saved password form.
 PasswordForm CreatePasswordForm() {
   PasswordForm form;
@@ -132,10 +121,18 @@
     builder.AddTestingFactory(
         SyncSetupServiceFactory::GetInstance(),
         base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService));
+    builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<web::BrowserState,
+                                                  TestPasswordStore>));
     browser_state_ = builder.Build();
 
-    store_ = BuildTestPasswordStore(browser_state_.get());
-
+    store_ =
+        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
+                .get()));
     password_check_ = IOSChromePasswordCheckManagerFactory::GetForBrowserState(
         browser_state_.get());
 
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator_unittest.mm b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator_unittest.mm
index a403a18..88f10d5 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator_unittest.mm
@@ -111,18 +111,6 @@
 using password_manager::TestPasswordStore;
 using l10n_util::GetNSString;
 
-// Sets test password store and returns pointer to it.
-scoped_refptr<TestPasswordStore> BuildTestPasswordStore(
-    ChromeBrowserState* _browserState) {
-  return base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-      IOSChromePasswordStoreFactory::GetInstance()
-          ->SetTestingFactoryAndUse(
-              _browserState,
-              base::BindRepeating(&password_manager::BuildPasswordStore<
-                                  web::BrowserState, TestPasswordStore>))
-          .get()));
-}
-
 // Registers account preference that will be used for Safe Browsing.
 PrefService* SetPrefService() {
   TestingPrefServiceSimple* prefs = new TestingPrefServiceSimple();
@@ -162,6 +150,11 @@
     test_cbs_builder.AddTestingFactory(
         SyncSetupServiceFactory::GetInstance(),
         base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService));
+    test_cbs_builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<web::BrowserState,
+                                                  TestPasswordStore>));
     browser_state_ = test_cbs_builder.Build();
     AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
         browser_state_.get(),
@@ -170,7 +163,11 @@
         AuthenticationServiceFactory::GetInstance()->GetForBrowserState(
             browser_state_.get()));
 
-    store_ = BuildTestPasswordStore(browser_state_.get());
+    store_ =
+        base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
+                .get()));
 
     password_check_ = IOSChromePasswordCheckManagerFactory::GetForBrowserState(
         browser_state_.get());
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller_unittest.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller_unittest.mm
index f5129cf..fc1d587 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller_unittest.mm
@@ -56,6 +56,11 @@
     test_cbs_builder.AddTestingFactory(
         ios::TemplateURLServiceFactory::GetInstance(),
         ios::TemplateURLServiceFactory::GetDefaultFactory());
+    test_cbs_builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<
+                web::BrowserState, password_manager::TestPasswordStore>));
     chrome_browser_state_ = test_cbs_builder.Build();
     AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
         chrome_browser_state_.get(),
@@ -69,12 +74,6 @@
     mockDelegate_ = [OCMockObject
         niceMockForProtocol:@protocol(SettingsNavigationControllerDelegate)];
 
-    IOSChromePasswordStoreFactory::GetInstance()->SetTestingFactory(
-        browser_->GetBrowserState(),
-        base::BindRepeating(
-            &password_manager::BuildPasswordStore<
-                web::BrowserState, password_manager::TestPasswordStore>));
-
     TemplateURLService* template_url_service =
         ios::TemplateURLServiceFactory::GetForBrowserState(
             chrome_browser_state_.get());
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
index c4a2772..c06ac0f 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
@@ -6,6 +6,7 @@
 
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/task_environment.h"
+#import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/password_manager_test_utils.h"
 #import "components/password_manager/core/browser/test_password_store.h"
 #import "components/policy/core/common/policy_loader_ios_constants.h"
@@ -74,6 +75,11 @@
     builder.AddTestingFactory(
         AuthenticationServiceFactory::GetInstance(),
         AuthenticationServiceFactory::GetDefaultFactory());
+    builder.AddTestingFactory(
+        IOSChromePasswordStoreFactory::GetInstance(),
+        base::BindRepeating(
+            &password_manager::BuildPasswordStore<
+                web::BrowserState, password_manager::TestPasswordStore>));
     chrome_browser_state_ = builder.Build();
 
     browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
@@ -97,12 +103,8 @@
 
     password_store_mock_ =
         base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
-            IOSChromePasswordStoreFactory::GetInstance()
-                ->SetTestingFactoryAndUse(
-                    chrome_browser_state_.get(),
-                    base::BindRepeating(&password_manager::BuildPasswordStore<
-                                        web::BrowserState,
-                                        password_manager::TestPasswordStore>))
+            IOSChromePasswordStoreFactory::GetForBrowserState(
+                chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS)
                 .get()));
 
     fake_identity_ = [FakeSystemIdentity fakeIdentity1];
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
index 951730d..570afb2 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
@@ -71,9 +71,8 @@
   void SetUp() override {
     PassphraseTableViewControllerTest::SetUp();
     mock_sync_setup_service_ = static_cast<NiceMock<SyncSetupServiceMock>*>(
-        SyncSetupServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-            chrome_browser_state_.get(),
-            base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService)));
+        SyncSetupServiceFactory::GetForBrowserState(
+            chrome_browser_state_.get()));
     ON_CALL(*fake_sync_service_, GetTransportState)
         .WillByDefault(Return(syncer::SyncService::TransportState::ACTIVE));
     ON_CALL(*fake_sync_service_->GetMockUserSettings(),
@@ -87,6 +86,13 @@
     PassphraseTableViewControllerTest::TearDown();
   }
 
+  void RegisterTestingFactories(
+      TestChromeBrowserState::Builder& builder) override {
+    builder.AddTestingFactory(
+        SyncSetupServiceFactory::GetInstance(),
+        base::BindRepeating(&SyncSetupServiceMock::CreateKeyedService));
+  }
+
   ChromeTableViewController* InstantiateController() override {
     return [[SyncEncryptionPassphraseTableViewController alloc]
         initWithBrowser:browser_.get()];
diff --git a/ios/chrome/browser/web_state_list/web_state_list.h b/ios/chrome/browser/web_state_list/web_state_list.h
index bd1f513..71b85fd 100644
--- a/ios/chrome/browser/web_state_list/web_state_list.h
+++ b/ios/chrome/browser/web_state_list/web_state_list.h
@@ -182,6 +182,10 @@
   // is a bitwise combination of ClosingFlags values.
   void CloseWebStateAt(int index, int close_flags);
 
+  // Closes and destroys all non-pinned WebStates. The `close_flags` is a
+  // bitwise combination of ClosingFlags values.
+  void CloseAllNonPinnedWebStates(int close_flags);
+
   // Closes and destroys all WebStates. The `close_flags` is a bitwise
   // combination of ClosingFlags values.
   void CloseAllWebStates(int close_flags);
@@ -249,11 +253,11 @@
   // Assumes that the WebStateList is locked.
   void CloseWebStateAtImpl(int index, int close_flags);
 
-  // Closes and destroys all WebStates. The `close_flags` is a bitwise
-  // combination of ClosingFlags values.
+  // Closes and destroys all WebStates after `start_index`. The `close_flags`
+  // is a bitwise combination of ClosingFlags values.
   //
   // Assumes that the WebStateList is locked.
-  void CloseAllWebStatesImpl(int close_flags);
+  void CloseAllWebStatesAfterIndexImpl(int start_index, int close_flags);
 
   // Makes the WebState at the specified index the active WebState.
   //
diff --git a/ios/chrome/browser/web_state_list/web_state_list.mm b/ios/chrome/browser/web_state_list/web_state_list.mm
index dc0f8139..b417f80 100644
--- a/ios/chrome/browser/web_state_list/web_state_list.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list.mm
@@ -285,7 +285,23 @@
 void WebStateList::CloseAllWebStates(int close_flags) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto lock = LockForMutation();
-  return CloseAllWebStatesImpl(close_flags);
+  PerformBatchOperation(base::BindOnce(
+      [](int start_index, int close_flags, WebStateList* web_state_list) {
+        web_state_list->CloseAllWebStatesAfterIndexImpl(start_index,
+                                                        close_flags);
+      },
+      0, close_flags));
+}
+
+void WebStateList::CloseAllNonPinnedWebStates(int close_flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto lock = LockForMutation();
+  PerformBatchOperation(base::BindOnce(
+      [](int start_index, int close_flags, WebStateList* web_state_list) {
+        web_state_list->CloseAllWebStatesAfterIndexImpl(start_index,
+                                                        close_flags);
+      },
+      GetIndexOfFirstNonPinnedWebState(), close_flags));
 }
 
 void WebStateList::ActivateWebStateAt(int index) {
@@ -462,25 +478,33 @@
   // Dropping detached_web_state will destroy it.
 }
 
-void WebStateList::CloseAllWebStatesImpl(int close_flags) {
+void WebStateList::CloseAllWebStatesAfterIndexImpl(int start_index,
+                                                   int close_flags) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(locked_);
 
-  PerformBatchOperation(base::BindOnce(
-      [](int close_flags, WebStateList* web_state_list) {
-        // Since all the WebStates will be closed, notify that the active
-        // WebState is de-activated before closing them. This avoid sending
-        // one notification per WebState in the worst case (when the active
-        // WebState is the last one and no opener is set to any WebState).
-        web_state_list->ActivateWebStateAtImpl(
-            kInvalidIndex, ActiveWebStateChangeReason::Closed);
+  // Immediately determine the new active index to avoid
+  // sending multiple notification about changing active
+  // WebState.
+  int new_active_index = kInvalidIndex;
+  if (start_index != 0) {
+    std::vector<int> removing_indexes;
+    removing_indexes.reserve(count() - start_index);
+    for (int i = start_index; i < count(); ++i) {
+      removing_indexes.push_back(i);
+    }
 
-        // Close the WebStates from last to first.
-        while (!web_state_list->empty())
-          web_state_list->CloseWebStateAtImpl(web_state_list->count() - 1,
-                                              close_flags);
-      },
-      close_flags));
+    WebStateListOrderController order_controller(*this);
+    new_active_index = order_controller.DetermineNewActiveIndex(
+        active_index_,
+        WebStateListRemovingIndexes(std::move(removing_indexes)));
+  }
+
+  ActivateWebStateAtImpl(new_active_index, ActiveWebStateChangeReason::Closed);
+
+  while (count() > start_index) {
+    CloseWebStateAtImpl(count() - 1, close_flags);
+  }
 }
 
 void WebStateList::ActivateWebStateAtImpl(int index,
diff --git a/ios/chrome/browser/web_state_list/web_state_list_unittest.mm b/ios/chrome/browser/web_state_list/web_state_list_unittest.mm
index c0524ac..4211407e 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_unittest.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list_unittest.mm
@@ -686,8 +686,104 @@
                    opener, start_index, false));
 }
 
-// Tests closing all webstates.
-TEST_F(WebStateListTest, CloseAllWebStates) {
+// Tests closing all non-pinned webstates (pinned WebStates present).
+TEST_F(WebStateListTest, CloseAllNonPinnedWebStates_PinnedWebStatesPresent) {
+  AppendNewWebState(kURL0);
+  AppendNewWebState(kURL1);
+  AppendNewWebState(kURL2);
+
+  web_state_list_.SetWebStatePinnedAt(0, true);
+
+  // Sanity checks before closing WebStates.
+  EXPECT_EQ(3, web_state_list_.count());
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(0));
+
+  observer_.ResetStatistics();
+  web_state_list_.CloseAllNonPinnedWebStates(WebStateList::CLOSE_USER_ACTION);
+
+  EXPECT_EQ(1, web_state_list_.count());
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(0));
+
+  EXPECT_TRUE(observer_.web_state_detached_called());
+  EXPECT_TRUE(observer_.batch_operation_started());
+  EXPECT_TRUE(observer_.batch_operation_ended());
+}
+
+// Tests closing all non-pinned webstates (non-pinned WebStates not present).
+TEST_F(WebStateListTest,
+       CloseAllNonPinnedWebStates_NonPinnedWebStatesNotPresent) {
+  AppendNewWebState(kURL0);
+  AppendNewWebState(kURL1);
+  AppendNewWebState(kURL2);
+
+  web_state_list_.SetWebStatePinnedAt(0, true);
+  web_state_list_.SetWebStatePinnedAt(1, true);
+  web_state_list_.SetWebStatePinnedAt(2, true);
+
+  // Sanity checks before closing WebStates.
+  EXPECT_EQ(3, web_state_list_.count());
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(0));
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(1));
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(2));
+
+  observer_.ResetStatistics();
+  web_state_list_.CloseAllNonPinnedWebStates(WebStateList::CLOSE_USER_ACTION);
+
+  EXPECT_EQ(3, web_state_list_.count());
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(0));
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(1));
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(2));
+
+  EXPECT_FALSE(observer_.web_state_detached_called());
+  EXPECT_TRUE(observer_.batch_operation_started());
+  EXPECT_TRUE(observer_.batch_operation_ended());
+}
+
+// Tests closing all non-pinned webstates (pinned WebStates not present).
+TEST_F(WebStateListTest, CloseAllNonPinnedWebStates_PinnedWebStatesNotPresent) {
+  AppendNewWebState(kURL0);
+  AppendNewWebState(kURL1);
+  AppendNewWebState(kURL2);
+
+  // Sanity checks before closing WebStates.
+  EXPECT_EQ(3, web_state_list_.count());
+
+  observer_.ResetStatistics();
+  web_state_list_.CloseAllNonPinnedWebStates(WebStateList::CLOSE_USER_ACTION);
+
+  EXPECT_EQ(0, web_state_list_.count());
+
+  EXPECT_TRUE(observer_.web_state_detached_called());
+  EXPECT_TRUE(observer_.batch_operation_started());
+  EXPECT_TRUE(observer_.batch_operation_ended());
+}
+
+// Tests closing all webstates (non-pinned).
+TEST_F(WebStateListTest, CloseAllWebStates_NonPinned) {
+  AppendNewWebState(kURL0);
+  AppendNewWebState(kURL1);
+  AppendNewWebState(kURL2);
+
+  web_state_list_.SetWebStatePinnedAt(0, true);
+  web_state_list_.SetWebStatePinnedAt(1, true);
+
+  // Sanity check before closing WebStates.
+  EXPECT_EQ(3, web_state_list_.count());
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(0));
+  EXPECT_TRUE(web_state_list_.IsWebStatePinnedAt(1));
+
+  observer_.ResetStatistics();
+  web_state_list_.CloseAllWebStates(WebStateList::CLOSE_USER_ACTION);
+
+  EXPECT_EQ(0, web_state_list_.count());
+
+  EXPECT_TRUE(observer_.web_state_detached_called());
+  EXPECT_TRUE(observer_.batch_operation_started());
+  EXPECT_TRUE(observer_.batch_operation_ended());
+}
+
+// Tests closing all webstates (pinned and non-pinned).
+TEST_F(WebStateListTest, CloseAllWebStates_PinnedNonPinned) {
   AppendNewWebState(kURL0);
   AppendNewWebState(kURL1);
   AppendNewWebState(kURL2);
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index c30903c..e6c0416 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -236,6 +236,7 @@
     "//ios/chrome/browser/overlays/public/web_content_area:unit_tests",
     "//ios/chrome/browser/overscroll_actions:unit_tests",
     "//ios/chrome/browser/passwords:unit_tests",
+    "//ios/chrome/browser/permissions:unit_tests",
     "//ios/chrome/browser/policy:unit_tests",
     "//ios/chrome/browser/prerender:unit_tests",
     "//ios/chrome/browser/promos_manager:unit_tests",
diff --git a/ios/chrome/test/data/policy/policy_test_cases.json b/ios/chrome/test/data/policy/policy_test_cases.json
index b7f08bc..3fee4fa 100644
--- a/ios/chrome/test/data/policy/policy_test_cases.json
+++ b/ios/chrome/test/data/policy/policy_test_cases.json
@@ -1,5 +1,43 @@
 {
   "-- Instructions --": "See https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/enterprise/policy_pref_mapping_test.md (This file is interpreted by //ios/chrome/browser/policy/policy_unittest.mm)",
+  "AppStoreRatingEnabled": {
+    "os": [
+      "ios"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "note": "Default value (no policies set).",
+        "prefs": {
+          "ios.app_store_rating_enabled": {
+            "location": "local_state",
+            "default_value": true
+          }
+        }
+      },
+      {
+        "policies": {
+          "AppStoreRatingEnabled": false
+        },
+        "prefs": {
+          "ios.app_store_rating_enabled": {
+            "location": "local_state",
+            "value": false
+          }
+        }
+      },
+      {
+        "policies": {
+          "AppStoreRatingEnabled": true
+        },
+        "prefs": {
+          "ios.app_store_rating_enabled": {
+            "location": "local_state",
+            "value": true
+          }
+        }
+      }
+    ]
+  },
   "AutofillAddressEnabled": {
     "os": [
       "ios"
diff --git a/media/base/byte_queue.cc b/media/base/byte_queue.cc
index 53ed077..e6b2f0b 100644
--- a/media/base/byte_queue.cc
+++ b/media/base/byte_queue.cc
@@ -9,10 +9,26 @@
 
 #include "base/check_op.h"
 #include "base/numerics/checked_math.h"
+#include "base/process/memory.h"
 
 namespace media {
 
-ByteQueue::ByteQueue() : buffer_(new uint8_t[size_]) {}
+ByteQueue::ByteQueue() {
+  uint8_t* new_buffer = nullptr;
+
+  // Though ::Push() is allowed to fail memory allocation for `buffer_`, do not
+  // allow memory allocation failure here during ByteQueue construction.
+  // TODO(crbug.com/1266639): Consider refactoring to an Initialize() method
+  // that does this allocation and that can indicate failure, so callers can
+  // more gracefully handle the former OOM case that now fails this CHECK. For
+  // example, some StreamParsers create additional ByteQueues during Parse, so
+  // such handling could be a parse error in that case. Other handling
+  // customization could be done where ByteQueues are created as part of
+  // StreamParser creation.
+  CHECK(base::UncheckedMalloc(size_, reinterpret_cast<void**>(&new_buffer)) &&
+        new_buffer);
+  buffer_.reset(new_buffer);
+}
 
 ByteQueue::~ByteQueue() = default;
 
@@ -21,7 +37,7 @@
   used_ = 0;
 }
 
-void ByteQueue::Push(const uint8_t* data, int size) {
+bool ByteQueue::Push(const uint8_t* data, int size) {
   DCHECK(data);
   DCHECK_GT(size, 0);
 
@@ -35,24 +51,28 @@
         (base::CheckedNumeric<size_t>(size_) + size_ / 4).ValueOrDie();
     const size_t new_size = std::max(size_needed, safe_size);
 
-    // Copy the data from the old buffer to the start of the new one.
-    if (used_ > 0) {
-      // Note: We could use realloc() here, but would need an additional move to
-      // pack data at offset_ = 0 after a potential internal new allocation +
-      // copy by realloc().
-      //
-      // In local tests on a few top video sites that ends up being the common
-      // case, so just prefer to copy and pack ourselves.
-      auto new_buffer = std::make_unique<uint8_t[]>(new_size);
-      memcpy(new_buffer.get(), Front(), used_);
-      buffer_ = std::move(new_buffer);
-    } else {
-      // Free the existing |data| first so that the memory can be reused, if
-      // possible. Note that the new array is purposely not initialized.
-      buffer_.reset();
-      buffer_ = std::make_unique<uint8_t[]>(new_size);
+    // Try to obtain a new backing buffer of `new_size` capacity. Note: If
+    // `used_` is positive, we could use realloc() here, but would need an
+    // additional move to pack data at offset_ = 0 after a potential internal
+    // new allocation + copy by realloc(). In local tests on a few top video
+    // sites that ends up being the common case, so just prefer to copy and pack
+    // ourselves. Further, we need to handle potential allocation failure, since
+    // callers may have fallback paths for that scenario, and the allocation
+    // path allowing this must not be used with realloc.
+    uint8_t* new_buffer = nullptr;
+    if (!base::UncheckedMalloc(new_size,
+                               reinterpret_cast<void**>(&new_buffer)) ||
+        !new_buffer) {
+      return false;
     }
 
+    // Note that the new array is purposely not initialized. Copy the data, if
+    // any, from the old buffer to the start of the new one.
+    if (used_ > 0) {
+      memcpy(new_buffer, Front(), used_);
+    }
+
+    buffer_.reset(new_buffer);  // This also frees the previous `buffer_`.
     size_ = new_size;
     offset_ = 0;
   } else if ((offset_ + used_ + size) > size_) {
@@ -63,6 +83,8 @@
 
   memcpy(Front() + used_, data, size);
   used_ += size;
+
+  return true;
 }
 
 void ByteQueue::Peek(const uint8_t** data, int* size) const {
diff --git a/media/base/byte_queue.h b/media/base/byte_queue.h
index 18b425f41..d564897 100644
--- a/media/base/byte_queue.h
+++ b/media/base/byte_queue.h
@@ -10,6 +10,7 @@
 
 #include <memory>
 
+#include "base/process/memory.h"
 #include "media/base/media_export.h"
 
 namespace media {
@@ -32,8 +33,10 @@
   // Reset the queue to the empty state.
   void Reset();
 
-  // Appends new bytes onto the end of the queue.
-  void Push(const uint8_t* data, int size);
+  // Appends new bytes onto the end of the queue. If allocation failure occurs,
+  // then the append of `data` is not done and returns false. Otherwise, returns
+  // true.
+  [[nodiscard]] bool Push(const uint8_t* data, int size);
 
   // Get a pointer to the front of the queue and the queue size. These values
   // are only valid until the next Push() or Pop() call.
@@ -58,7 +61,7 @@
   // Number of bytes stored in |buffer_|.
   int used_ = 0;
 
-  std::unique_ptr<uint8_t[]> buffer_;
+  std::unique_ptr<uint8_t, base::UncheckedFreeDeleter> buffer_;
 };
 
 }  // namespace media
diff --git a/media/base/stream_parser.h b/media/base/stream_parser.h
index 2f27fd53..e582b9a4 100644
--- a/media/base/stream_parser.h
+++ b/media/base/stream_parser.h
@@ -173,9 +173,6 @@
   // `QuotaExceededErr` exception per the MSE specification. App could use a
   // back-off and retry strategy or otherwise alter their behavior to attempt to
   // buffer media for further playback.
-  // TODO(crbug.com/1286810): Update resource allocation paths in the
-  // StreamParser implementations of this method to recognize and report
-  // allocation failure.
   [[nodiscard]] virtual bool AppendToParseBuffer(const uint8_t* buf,
                                                  size_t size) = 0;
 
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index e997e30..68cd8d4 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -939,7 +939,10 @@
       case INITIALIZED:
         DCHECK(IsValidId_Locked(id));
         if (!source_state_map_[id]->AppendToParseBuffer(data, length)) {
-          ReportError_Locked(CHUNK_DEMUXER_ERROR_APPEND_FAILED);
+          // Just indicate that the append failed. Let the caller give app an
+          // error so that it may adapt. This is different from
+          // RunSegmentParserLoop(), where fatal MediaSource failure should
+          // occur if the underlying parse fails.
           return false;
         }
         break;
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index 6aac01d..9cb07af 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -305,9 +305,6 @@
   // per the MSE specification. App could use a back-off and retry strategy or
   // otherwise alter their behavior to attempt to buffer media for further
   // playback.
-  // TODO(crbug.com/1286810): Update resource allocation paths in the
-  // StreamParser implementations eventually executed within this method to
-  // recognize and report allocation failure.
   [[nodiscard]] bool AppendToParseBuffer(const std::string& id,
                                          const uint8_t* data,
                                          size_t length);
diff --git a/media/filters/source_buffer_state.h b/media/filters/source_buffer_state.h
index a49d7a6..31deb83 100644
--- a/media/filters/source_buffer_state.h
+++ b/media/filters/source_buffer_state.h
@@ -66,9 +66,6 @@
   // append failure using a `QuotaExceededErr` exception per the MSE
   // specification. App could use a back-off and retry strategy or otherwise
   // alter their behavior to attempt to buffer media for further playback.
-  // TODO(crbug.com/1286810): Update resource allocation paths in the
-  // StreamParser implementations called by this method to recognize and report
-  // allocation failure.
   [[nodiscard]] bool AppendToParseBuffer(const uint8_t* data, size_t length);
 
   // Tells the stream parser to parse more of the data previously sent to it
diff --git a/media/formats/common/offset_byte_queue.cc b/media/formats/common/offset_byte_queue.cc
index 47efd0cd..782e3c6 100644
--- a/media/formats/common/offset_byte_queue.cc
+++ b/media/formats/common/offset_byte_queue.cc
@@ -19,10 +19,15 @@
   head_ = 0;
 }
 
-void OffsetByteQueue::Push(const uint8_t* buf, int size) {
-  queue_.Push(buf, size);
+bool OffsetByteQueue::Push(const uint8_t* buf, int size) {
+  if (!queue_.Push(buf, size)) {
+    DVLOG(4) << "Failed to push buf of size " << size;
+    Sync();
+    return false;
+  }
   Sync();
   DVLOG(4) << "Buffer pushed. head=" << head() << " tail=" << tail();
+  return true;
 }
 
 void OffsetByteQueue::Peek(const uint8_t** buf, int* size) {
diff --git a/media/formats/common/offset_byte_queue.h b/media/formats/common/offset_byte_queue.h
index 41ab1007..6e9468db 100644
--- a/media/formats/common/offset_byte_queue.h
+++ b/media/formats/common/offset_byte_queue.h
@@ -27,7 +27,7 @@
 
   // These work like their underlying ByteQueue counterparts.
   void Reset();
-  void Push(const uint8_t* buf, int size);
+  [[nodiscard]] bool Push(const uint8_t* buf, int size);
   void Peek(const uint8_t** buf, int* size);
   void Pop(int count);
 
diff --git a/media/formats/common/offset_byte_queue_unittest.cc b/media/formats/common/offset_byte_queue_unittest.cc
index 1361055..02b538e 100644
--- a/media/formats/common/offset_byte_queue_unittest.cc
+++ b/media/formats/common/offset_byte_queue_unittest.cc
@@ -21,8 +21,8 @@
       buf[i] = i;
     }
     queue_ = std::make_unique<OffsetByteQueue>();
-    queue_->Push(buf, sizeof(buf));
-    queue_->Push(buf, sizeof(buf));
+    ASSERT_TRUE(queue_->Push(buf, sizeof(buf))) << "Test should not hit OOM";
+    ASSERT_TRUE(queue_->Push(buf, sizeof(buf))) << "Test should not hit OOM";
     queue_->Pop(384);
 
     // Queue will start with 128 bytes of data and an offset of 384 bytes.
diff --git a/media/formats/mp2t/es_parser.cc b/media/formats/mp2t/es_parser.cc
index 2c17bc7..a7d9342 100644
--- a/media/formats/mp2t/es_parser.cc
+++ b/media/formats/mp2t/es_parser.cc
@@ -4,6 +4,8 @@
 
 #include "media/formats/mp2t/es_parser.h"
 
+#include "base/logging.h"
+#include "media/base/byte_queue.h"
 #include "media/base/stream_parser_buffer.h"
 #include "media/base/timestamp_constants.h"
 #include "media/formats/common/offset_byte_queue.h"
@@ -45,7 +47,11 @@
   }
 
   // Add the incoming bytes to the ES queue.
-  es_queue_->Push(buf, size);
+  if (!es_queue_->Push(buf, size)) {
+    DVLOG(2) << "Failed to push buf of size " << size;
+    return false;
+  }
+
   return ParseFromEsQueue();
 }
 
diff --git a/media/formats/mp2t/es_parser_h264.cc b/media/formats/mp2t/es_parser_h264.cc
index 0cc43f5..daa7723 100644
--- a/media/formats/mp2t/es_parser_h264.cc
+++ b/media/formats/mp2t/es_parser_h264.cc
@@ -216,9 +216,16 @@
   // Simulate an additional AUD to force emitting the last access unit
   // which is assumed to be complete at this point.
   uint8_t aud[] = {0x00, 0x00, 0x01, 0x09};
-  es_queue_->Push(aud, sizeof(aud));
-  ParseFromEsQueue();
 
+  // Fail if this AUD's push fails allocation, since otherwise the behavior of
+  // the subsequent parse would vary based on whether or not the system is
+  // near-OOM.
+  // TODO(crbug.com/1266639): Consider plumbing parse failure for this push
+  // failure case, instead of what used to OOM but now instead would fail this
+  // CHECK.
+  CHECK(es_queue_->Push(aud, sizeof(aud)));
+
+  ParseFromEsQueue();
   es_adapter_.Flush();
 }
 
diff --git a/media/formats/mp2t/mp2t_stream_parser.cc b/media/formats/mp2t/mp2t_stream_parser.cc
index ec6213f..0e7fc3d 100644
--- a/media/formats/mp2t/mp2t_stream_parser.cc
+++ b/media/formats/mp2t/mp2t_stream_parser.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/numerics/checked_math.h"
+#include "media/base/byte_queue.h"
 #include "media/base/media_tracks.h"
 #include "media/base/stream_parser.h"
 #include "media/base/stream_parser_buffer.h"
@@ -334,7 +335,10 @@
 
   // Add the data to the parser state.
   uninspected_pending_bytes_ = base::checked_cast<int>(size);
-  ts_byte_queue_.Push(buf, uninspected_pending_bytes_);
+  if (!ts_byte_queue_.Push(buf, uninspected_pending_bytes_)) {
+    DVLOG(2) << "AppendToParseBuffer(): Failed to push buf of size " << size;
+    return false;
+  }
 
   return true;
 }
diff --git a/media/formats/mp2t/ts_section_pes.cc b/media/formats/mp2t/ts_section_pes.cc
index c6bf662..2f4588d 100644
--- a/media/formats/mp2t/ts_section_pes.cc
+++ b/media/formats/mp2t/ts_section_pes.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "media/base/bit_reader.h"
+#include "media/base/byte_queue.h"
 #include "media/base/stream_parser_buffer.h"
 #include "media/base/timestamp_constants.h"
 #include "media/formats/mp2t/es_parser.h"
@@ -76,8 +77,9 @@
   }
 
   // Add the data to the parser state.
-  if (size > 0)
-    pes_byte_queue_.Push(buf, size);
+  if (size > 0) {
+    RCHECK(pes_byte_queue_.Push(buf, size));  // Can fail if allocation fails.
+  }
 
   // Try emitting the current PES packet.
   return (parse_result && Emit(false));
diff --git a/media/formats/mp2t/ts_section_psi.cc b/media/formats/mp2t/ts_section_psi.cc
index ca7a88b..65e5a43 100644
--- a/media/formats/mp2t/ts_section_psi.cc
+++ b/media/formats/mp2t/ts_section_psi.cc
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #include "media/base/bit_reader.h"
+#include "media/base/byte_queue.h"
 #include "media/formats/mp2t/mp2t_common.h"
 
 static bool IsCrcValid(const uint8_t* buf, int size) {
@@ -77,7 +78,7 @@
     return true;
 
   // Add the data to the parser state.
-  psi_byte_queue_.Push(buf, size);
+  RCHECK(psi_byte_queue_.Push(buf, size));  // Can fail if allocation fails.
   int raw_psi_size;
   const uint8_t* raw_psi;
   psi_byte_queue_.Peek(&raw_psi, &raw_psi_size);
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc
index 2f70c7c4..27e2b9d 100644
--- a/media/formats/mp4/mp4_stream_parser.cc
+++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -149,7 +149,11 @@
   // could lead to memory corruption, preferring CHECK.
   CHECK_EQ(queue_.tail(), max_parse_offset_);
 
-  queue_.Push(buf, base::checked_cast<int>(size));
+  if (!queue_.Push(buf, base::checked_cast<int>(size))) {
+    DVLOG(2) << "AppendToParseBuffer(): Failed to push buf of size " << size;
+    return false;
+  }
+
   return true;
 }
 
diff --git a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
index b2fd133..5e77078 100644
--- a/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
+++ b/media/formats/mpeg/mpeg_audio_stream_parser_base.cc
@@ -10,6 +10,7 @@
 #include "base/callback_helpers.h"
 #include "base/numerics/checked_math.h"
 #include "base/time/time.h"
+#include "media/base/byte_queue.h"
 #include "media/base/media_log.h"
 #include "media/base/media_tracks.h"
 #include "media/base/media_util.h"
@@ -132,7 +133,10 @@
   CHECK_EQ(uninspected_pending_bytes_, 0);
 
   uninspected_pending_bytes_ = base::checked_cast<int>(size);
-  queue_.Push(buf, uninspected_pending_bytes_);
+  if (!queue_.Push(buf, uninspected_pending_bytes_)) {
+    DVLOG(2) << "AppendToParseBuffer(): Failed to push buf of size " << size;
+    return false;
+  }
 
   return true;
 }
diff --git a/media/formats/webm/webm_stream_parser.cc b/media/formats/webm/webm_stream_parser.cc
index 36a000a..f3640d9 100644
--- a/media/formats/webm/webm_stream_parser.cc
+++ b/media/formats/webm/webm_stream_parser.cc
@@ -12,6 +12,7 @@
 #include "base/logging.h"
 #include "base/numerics/checked_math.h"
 #include "base/strings/string_number_conversions.h"
+#include "media/base/byte_queue.h"
 #include "media/base/media_track.h"
 #include "media/base/media_tracks.h"
 #include "media/base/stream_parser.h"
@@ -102,7 +103,10 @@
   CHECK_EQ(uninspected_pending_bytes_, 0);
 
   uninspected_pending_bytes_ = base::checked_cast<int>(size);
-  byte_queue_.Push(buf, uninspected_pending_bytes_);
+  if (!byte_queue_.Push(buf, uninspected_pending_bytes_)) {
+    DVLOG(2) << "AppendToParseBuffer(): Failed to push buf of size " << size;
+    return false;
+  }
 
   return true;
 }
diff --git a/media/gpu/chromeos/image_processor_factory.cc b/media/gpu/chromeos/image_processor_factory.cc
index 96b9eb5e..afd526b 100644
--- a/media/gpu/chromeos/image_processor_factory.cc
+++ b/media/gpu/chromeos/image_processor_factory.cc
@@ -284,18 +284,18 @@
     if (processor)
       return processor;
   }
-  if (base::FeatureList::IsEnabled(media::kPreferLibYuvImageProcessor)) {
+
     auto processor = CreateLibYUVImageProcessorWithInputCandidates(
         input_candidates, input_visible_rect, output_size, client_task_runner,
         out_format_picker, error_cb);
     if (processor)
       return processor;
-  }
-  auto processor = CreateV4L2ImageProcessorWithInputCandidates(
-      input_candidates, input_visible_rect, num_buffers, client_task_runner,
-      out_format_picker, error_cb);
-  if (processor)
-    return processor;
+
+    processor = CreateV4L2ImageProcessorWithInputCandidates(
+        input_candidates, input_visible_rect, num_buffers, client_task_runner,
+        out_format_picker, error_cb);
+    if (processor)
+      return processor;
 
 #endif
 
diff --git a/media/video/gpu_video_accelerator_factories.h b/media/video/gpu_video_accelerator_factories.h
index eb08a3bd..beb43bd2 100644
--- a/media/video/gpu_video_accelerator_factories.h
+++ b/media/video/gpu_video_accelerator_factories.h
@@ -160,9 +160,6 @@
   // May be called on any thread.
   virtual void NotifyEncoderSupportKnown(base::OnceClosure callback) = 0;
 
-  // Caller owns returned pointer, but should call Destroy() on it (instead of
-  // directly deleting) for proper destruction, as per the
-  // VideoEncodeAccelerator interface.
   virtual std::unique_ptr<VideoEncodeAccelerator>
   CreateVideoEncodeAccelerator() = 0;
 
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index 61a515a0..f37a1d50 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -456,7 +456,7 @@
   virtual bool IsGpuFrameResizeSupported();
 
  protected:
-  // Do not delete directly; use Destroy() or own it with a scoped_ptr, which
+  // Do not delete directly; use Destroy() or own it with a unique_ptr, which
   // will Destroy() it properly by default.
   virtual ~VideoEncodeAccelerator();
 };
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 2b8bda07..f87eb48 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -3510,6 +3510,9 @@
     "data/verify_certificate_chain_unittest/root-lacks-basic-constraints/chain.pem",
     "data/verify_certificate_chain_unittest/root-lacks-basic-constraints/main.test",
     "data/verify_certificate_chain_unittest/root-lacks-basic-constraints/ta-with-constraints.test",
+    "data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/chain.pem",
+    "data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/main.test",
+    "data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/ta-with-constraints.test",
     "data/verify_certificate_chain_unittest/target-and-intermediate/chain.pem",
     "data/verify_certificate_chain_unittest/target-and-intermediate/distrusted-root-expired.test",
     "data/verify_certificate_chain_unittest/target-and-intermediate/distrusted-root.test",
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index f1066db..3b9ccb9d 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -4798,7 +4798,7 @@
 
   if (VerifyProcTypeIsBuiltin()) {
     EXPECT_THAT(Verify(), IsOk());
-    EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk());
+    EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID));
   } else if (VerifyProcTypeIsMacAtMostOS10_14() ||
              verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
     EXPECT_THAT(Verify(), IsOk());
diff --git a/net/cert/pki/verify_certificate_chain.cc b/net/cert/pki/verify_certificate_chain.cc
index ec4081b..d84ed652 100644
--- a/net/cert/pki/verify_certificate_chain.cc
+++ b/net/cert/pki/verify_certificate_chain.cc
@@ -1199,6 +1199,14 @@
 void PathVerifier::ApplyTrustAnchorConstraints(const ParsedCertificate& cert,
                                                KeyPurpose required_key_purpose,
                                                CertErrors* errors) {
+  // If keyUsage is present, verify that |cert| has correct keyUsage bits for a
+  // CA. This matches the handling for intermediates from RFC 5280 section
+  // 6.1.4 step n.
+  if (cert.has_key_usage() &&
+      !cert.key_usage().AssertsBit(KEY_USAGE_BIT_KEY_CERT_SIGN)) {
+    errors->AddError(cert_errors::kKeyCertSignBitNotSet);
+  }
+
   // This is not part of RFC 5937 nor RFC 5280, but matches the EKU handling
   // done for intermediates (described in Web PKI's Baseline Requirements).
   VerifyExtendedKeyUsage(cert, required_key_purpose, errors,
diff --git a/net/cert/pki/verify_certificate_chain.h b/net/cert/pki/verify_certificate_chain.h
index 0e87f7a..5bfe8a54 100644
--- a/net/cert/pki/verify_certificate_chain.h
+++ b/net/cert/pki/verify_certificate_chain.h
@@ -222,7 +222,7 @@
 //
 //  * Signature:             No
 //  * Validity (expiration): No
-//  * Key usage:             No
+//  * Key usage:             Yes
 //  * Extended key usage:    Yes (required if required_key_purpose is STRICT)
 //  * Basic constraints:     Yes
 //  * Name constraints:      Yes
diff --git a/net/cert/pki/verify_certificate_chain_typed_unittest.h b/net/cert/pki/verify_certificate_chain_typed_unittest.h
index 183f6fb..fc913a8 100644
--- a/net/cert/pki/verify_certificate_chain_typed_unittest.h
+++ b/net/cert/pki/verify_certificate_chain_typed_unittest.h
@@ -136,6 +136,9 @@
   this->RunTest("target-serverauth-various-keyusages/ec-digitalSignature.test");
   this->RunTest("target-serverauth-various-keyusages/ec-keyAgreement.test");
   this->RunTest("target-serverauth-various-keyusages/ec-keyEncipherment.test");
+
+  this->RunTest("root-lacks-keycertsign-key-usage/main.test");
+  this->RunTest("root-lacks-keycertsign-key-usage/ta-with-constraints.test");
 }
 
 TYPED_TEST_P(VerifyCertificateChainSingleRootTest, ExtendedKeyUsage) {
diff --git a/net/data/gencerts/__init__.py b/net/data/gencerts/__init__.py
index 7d2e4590..d1c77d9 100755
--- a/net/data/gencerts/__init__.py
+++ b/net/data/gencerts/__init__.py
@@ -123,7 +123,7 @@
   # If the file doesn't already exist, generate a new key using the generation
   # parameters.
   if not os.path.isfile(path):
-    key_contents = subprocess.check_output(generation_arguments)
+    key_contents = subprocess.check_output(generation_arguments, text=True)
 
     # Prepend the generation parameters to the key file.
     write_string_to_file(generation_arguments_str + '\n' + key_contents,
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/chain.pem b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/chain.pem
new file mode 100644
index 0000000..fb858e4d
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/chain.pem
@@ -0,0 +1,266 @@
+[Created by: ./generate-chains.py]
+
+Certificate chain where the root keyUsage extension is present but does not
+contain keyCertSign.
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4d:9c:b5:b6:68:93:5e:c6:e1:a7:65:67:49:b3:e4:0e:bd:5e:63:b4
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Intermediate
+        Validity
+            Not Before: Oct  5 12:00:00 2021 GMT
+            Not After : Oct  5 12:00:00 2022 GMT
+        Subject: CN=Target
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:1c:a4:b9:20:81:72:4b:f5:2c:50:7e:3f:1a:
+                    85:27:5b:2d:50:7c:e5:36:fd:9c:ac:26:a0:7d:b7:
+                    94:d5:6b:fd:c3:46:1a:dc:4d:10:45:98:cc:87:8d:
+                    af:fc:0e:bc:c9:b2:61:4b:8c:c2:9b:c8:da:7b:05:
+                    b3:3b:e0:21:95:a5:9c:01:72:34:48:3d:e4:44:f2:
+                    e7:b6:6b:58:f4:8d:60:92:c8:91:29:41:23:29:23:
+                    fd:da:62:d0:c3:78:92:5f:01:09:55:2e:3a:a2:b3:
+                    2c:2d:c5:cd:79:26:7a:66:bc:e3:a5:17:51:ae:b7:
+                    29:50:75:10:6f:2c:55:a9:79:04:21:05:3b:14:32:
+                    65:7c:3a:2c:33:ea:6b:72:20:f1:87:31:f2:8f:27:
+                    69:4f:50:1d:c2:18:36:8e:b8:6d:c4:b8:0b:7a:23:
+                    87:e1:48:84:ec:44:98:77:df:a7:7a:06:37:4d:42:
+                    33:40:e2:b2:c9:67:2f:94:20:69:5c:6d:30:1a:b8:
+                    c5:60:9e:32:6e:4d:b2:85:de:94:b9:86:50:f9:0c:
+                    72:06:34:bb:f0:4a:fc:cd:c8:89:7b:eb:69:e5:64:
+                    e8:55:4c:12:79:cc:81:88:26:f6:59:22:d5:60:8c:
+                    36:4f:96:30:e2:ea:f4:10:dc:82:08:d2:3b:e2:05:
+                    13:77
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                A9:6C:19:42:95:E3:95:C7:FF:D2:0F:BD:2C:E9:43:1F:2E:09:A2:C7
+            X509v3 Authority Key Identifier: 
+                5E:60:7B:07:9D:3D:65:C3:D3:DE:FB:C8:28:BE:34:22:10:C9:C4:A2
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Intermediate.cer
+            X509v3 CRL Distribution Points: 
+                Full Name:
+                  URI:http://url-for-crl/Intermediate.crl
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        14:bf:fa:03:43:bd:68:2c:77:55:2b:4a:49:ed:1b:71:4d:50:
+        0c:91:f8:c0:2c:db:66:d8:a9:45:86:25:f6:cf:3a:db:94:e9:
+        c2:c9:76:3f:c1:b3:1f:58:08:e8:05:d2:2f:de:82:35:ad:87:
+        56:c7:a8:d9:2c:e4:1a:a9:3f:91:52:cc:82:1b:2d:6d:83:99:
+        7e:2a:0f:90:93:a7:d3:09:a6:53:49:bc:d2:73:08:73:77:9c:
+        5d:d8:7b:3e:ae:42:e3:2d:d5:89:1c:45:de:06:3d:99:a9:e8:
+        63:f9:27:f7:01:1c:aa:85:00:1e:37:11:8d:4b:c0:a0:b4:fe:
+        16:30:6f:da:88:8e:a9:34:33:9b:9d:6c:d7:f2:c8:e6:86:9d:
+        f4:07:60:7e:86:fc:fb:4a:22:a4:cf:84:95:dc:da:cd:35:46:
+        71:d3:d3:71:e2:50:0b:a9:8c:25:1e:dc:13:9d:f6:e2:90:fa:
+        dd:64:a2:d4:d8:04:fd:64:eb:77:c7:87:88:b5:1a:90:0b:d1:
+        e3:5f:5c:94:3a:8d:3b:6b:ad:f7:9c:27:8f:dd:a2:b7:de:64:
+        fb:2f:c1:8e:b0:49:88:30:e8:2e:c9:49:26:a2:ad:3f:f8:b5:
+        4c:7a:d0:42:33:18:ae:fc:c1:29:66:8a:3d:7b:88:83:48:f6:
+        77:c0:02:d1
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIUTZy1tmiTXsbhp2VnSbPkDr1eY7QwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMSW50ZXJtZWRpYXRlMB4XDTIxMTAwNTEyMDAwMFoXDTIy
+MTAwNTEyMDAwMFowETEPMA0GA1UEAwwGVGFyZ2V0MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAsRykuSCBckv1LFB+PxqFJ1stUHzlNv2crCagfbeU1Wv9
+w0Ya3E0QRZjMh42v/A68ybJhS4zCm8jaewWzO+AhlaWcAXI0SD3kRPLntmtY9I1g
+ksiRKUEjKSP92mLQw3iSXwEJVS46orMsLcXNeSZ6ZrzjpRdRrrcpUHUQbyxVqXkE
+IQU7FDJlfDosM+prciDxhzHyjydpT1Adwhg2jrhtxLgLeiOH4UiE7ESYd9+negY3
+TUIzQOKyyWcvlCBpXG0wGrjFYJ4ybk2yhd6UuYZQ+QxyBjS78Er8zciJe+tp5WTo
+VUwSecyBiCb2WSLVYIw2T5Yw4ur0ENyCCNI74gUTdwIDAQABo4HpMIHmMB0GA1Ud
+DgQWBBSpbBlCleOVx//SD70s6UMfLgmixzAfBgNVHSMEGDAWgBReYHsHnT1lw9Pe
++8govjQiEMnEojA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUHMAKGI2h0dHA6Ly91
+cmwtZm9yLWFpYS9JbnRlcm1lZGlhdGUuY2VyMDQGA1UdHwQtMCswKaAnoCWGI2h0
+dHA6Ly91cmwtZm9yLWNybC9JbnRlcm1lZGlhdGUuY3JsMA4GA1UdDwEB/wQEAwIF
+oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQAD
+ggEBABS/+gNDvWgsd1UrSkntG3FNUAyR+MAs22bYqUWGJfbPOtuU6cLJdj/Bsx9Y
+COgF0i/egjWth1bHqNks5BqpP5FSzIIbLW2DmX4qD5CTp9MJplNJvNJzCHN3nF3Y
+ez6uQuMt1YkcRd4GPZmp6GP5J/cBHKqFAB43EY1LwKC0/hYwb9qIjqk0M5udbNfy
+yOaGnfQHYH6G/PtKIqTPhJXc2s01RnHT03HiUAupjCUe3BOd9uKQ+t1kotTYBP1k
+63fHh4i1GpAL0eNfXJQ6jTtrrfecJ4/dorfeZPsvwY6wSYgw6C7JSSairT/4tUx6
+0EIzGK78wSlmij17iINI9nfAAtE=
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            54:e3:4d:7d:5e:fd:13:7b:9f:32:55:f3:77:55:e6:39:52:6f:ef:40
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Oct  5 12:00:00 2021 GMT
+            Not After : Oct  5 12:00:00 2022 GMT
+        Subject: CN=Intermediate
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:9c:38:da:61:b8:ad:0d:aa:de:44:df:52:78:01:
+                    82:89:fb:5f:bd:fc:a9:79:d0:8d:10:5c:3c:4e:b7:
+                    b5:43:36:bc:05:3a:0c:4e:54:88:00:45:fb:33:25:
+                    ca:cf:3a:aa:ad:a4:de:d2:2e:c1:53:44:b8:f5:58:
+                    e7:27:19:5a:70:34:71:c7:4d:77:1f:53:f2:66:b9:
+                    79:e8:c2:10:8c:2f:5d:19:17:7e:e3:26:7e:0c:5d:
+                    8e:ae:85:01:f7:05:ac:ce:18:5d:7c:a4:fa:fc:38:
+                    e6:18:63:a5:4c:d0:a3:cb:a3:e6:47:ad:5f:ec:32:
+                    fc:11:02:87:cb:fa:87:c9:38:aa:b8:b2:7e:98:99:
+                    1b:4e:f3:01:fc:48:6a:60:a5:29:80:3c:0c:50:23:
+                    3a:ae:0a:a6:d4:29:69:1e:15:34:ed:93:31:fb:30:
+                    d4:d6:23:59:94:89:fa:99:b4:16:d8:04:63:d0:c3:
+                    a9:2d:be:ff:6a:84:c7:54:bb:e8:eb:bd:16:5b:88:
+                    33:10:1b:6e:20:4f:60:49:56:09:86:dc:95:fc:c5:
+                    d9:2c:de:03:32:88:fc:3a:84:06:48:92:10:7c:2d:
+                    b9:6a:25:fd:93:c0:51:75:bd:54:e7:ba:0b:bb:d8:
+                    b3:f5:60:55:9e:c7:06:70:fd:f3:f0:13:8e:a8:33:
+                    17:ff
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                5E:60:7B:07:9D:3D:65:C3:D3:DE:FB:C8:28:BE:34:22:10:C9:C4:A2
+            X509v3 Authority Key Identifier: 
+                20:1D:40:05:AB:57:09:3B:3A:83:BE:19:65:15:EB:74:EF:0C:0D:D7
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+            X509v3 CRL Distribution Points: 
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        12:3c:5d:46:a3:a4:11:4c:f6:d7:cc:92:61:b0:fd:ec:b6:12:
+        37:3f:52:43:39:5e:8b:ff:5f:a9:09:3e:3b:3a:e4:c9:ec:59:
+        00:8d:08:12:f9:b5:bd:8b:f4:41:dd:ff:d4:5c:5f:58:e7:e7:
+        8c:a9:96:60:f0:78:27:7c:ca:da:24:67:93:ff:72:58:50:6b:
+        8c:f2:11:da:8b:27:1b:68:9a:e0:9d:59:78:64:0d:d1:c4:c2:
+        72:5f:9f:ef:32:d6:65:10:38:62:54:97:d5:03:cf:c4:a5:34:
+        f6:d2:d0:dd:b4:fc:08:49:2d:43:55:7c:2d:43:a1:1c:30:65:
+        b2:30:f8:5a:0a:ce:e8:1e:ad:c0:41:2d:d3:a2:64:76:3b:e8:
+        82:3a:5b:93:f3:e8:84:de:7e:20:8d:05:06:2b:82:fd:6d:7c:
+        35:6a:04:05:c0:6c:1b:91:6d:db:b3:a7:a5:35:42:9f:af:69:
+        22:81:a9:f7:58:1c:fc:f1:31:54:26:f6:c5:b9:67:d0:da:eb:
+        01:bd:69:10:de:ea:d3:95:29:ab:f2:59:8d:62:b9:01:b2:32:
+        75:b7:47:3c:39:51:5d:be:46:48:da:19:f2:a8:61:8c:5f:cf:
+        9c:a9:c9:92:a8:4e:ef:5c:8a:3f:73:fd:38:91:c2:90:33:c6:
+        60:8e:67:22
+-----BEGIN CERTIFICATE-----
+MIIDgDCCAmigAwIBAgIUVONNfV79E3ufMlXzd1XmOVJv70AwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEUm9vdDAeFw0yMTEwMDUxMjAwMDBaFw0yMjEwMDUxMjAw
+MDBaMBcxFTATBgNVBAMMDEludGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAJw42mG4rQ2q3kTfUngBgon7X738qXnQjRBcPE63tUM2vAU6
+DE5UiABF+zMlys86qq2k3tIuwVNEuPVY5ycZWnA0ccdNdx9T8ma5eejCEIwvXRkX
+fuMmfgxdjq6FAfcFrM4YXXyk+vw45hhjpUzQo8uj5ketX+wy/BECh8v6h8k4qriy
+fpiZG07zAfxIamClKYA8DFAjOq4KptQpaR4VNO2TMfsw1NYjWZSJ+pm0FtgEY9DD
+qS2+/2qEx1S76Ou9FluIMxAbbiBPYElWCYbclfzF2SzeAzKI/DqEBkiSEHwtuWol
+/ZPAUXW9VOe6C7vYs/VgVZ7HBnD98/ATjqgzF/8CAwEAAaOByzCByDAdBgNVHQ4E
+FgQUXmB7B509ZcPT3vvIKL40IhDJxKIwHwYDVR0jBBgwFoAUIB1ABatXCTs6g74Z
+ZRXrdO8MDdcwNwYIKwYBBQUHAQEEKzApMCcGCCsGAQUFBzAChhtodHRwOi8vdXJs
+LWZvci1haWEvUm9vdC5jZXIwLAYDVR0fBCUwIzAhoB+gHYYbaHR0cDovL3VybC1m
+b3ItY3JsL1Jvb3QuY3JsMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
+MA0GCSqGSIb3DQEBCwUAA4IBAQASPF1Go6QRTPbXzJJhsP3sthI3P1JDOV6L/1+p
+CT47OuTJ7FkAjQgS+bW9i/RB3f/UXF9Y5+eMqZZg8HgnfMraJGeT/3JYUGuM8hHa
+iycbaJrgnVl4ZA3RxMJyX5/vMtZlEDhiVJfVA8/EpTT20tDdtPwISS1DVXwtQ6Ec
+MGWyMPhaCs7oHq3AQS3TomR2O+iCOluT8+iE3n4gjQUGK4L9bXw1agQFwGwbkW3b
+s6elNUKfr2kigan3WBz88TFUJvbFuWfQ2usBvWkQ3urTlSmr8lmNYrkBsjJ1t0c8
+OVFdvkZI2hnyqGGMX8+cqcmSqE7vXIo/c/04kcKQM8Zgjmci
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            54:e3:4d:7d:5e:fd:13:7b:9f:32:55:f3:77:55:e6:39:52:6f:ef:3f
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Oct  5 12:00:00 2021 GMT
+            Not After : Oct  5 12:00:00 2022 GMT
+        Subject: CN=Root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:9d:78:55:f3:0c:19:87:95:61:61:db:6c:07:71:
+                    bb:94:fd:a5:8d:cc:a7:b2:d6:0a:36:85:0a:07:1c:
+                    6b:e2:06:63:06:2b:ca:c6:ac:a8:fe:9b:02:f4:c0:
+                    bc:cf:12:cd:49:18:2c:90:35:55:16:a1:2b:49:77:
+                    0a:a3:e2:04:22:e8:2c:58:21:7c:f4:b6:bd:19:61:
+                    c6:50:4f:a8:0f:b1:e9:96:e7:fc:f6:1d:bb:ad:58:
+                    69:25:e9:db:6d:91:cc:61:2b:e5:93:9d:a9:37:c3:
+                    f3:29:74:58:cd:b9:85:48:b8:ca:49:14:7b:18:54:
+                    ee:c5:c7:18:98:48:91:f8:c2:c2:ec:15:67:27:bf:
+                    a6:1b:29:25:97:67:ce:07:25:13:56:1a:b0:42:c8:
+                    1d:1b:33:49:83:f4:da:67:52:79:22:3c:0d:9f:0c:
+                    e5:91:87:5f:fe:f6:43:70:bc:2f:68:c8:d6:37:8f:
+                    cf:97:7b:c9:d6:3f:9e:06:c6:b0:ea:20:b6:7f:b0:
+                    33:a0:0f:5b:05:2a:1a:02:b9:22:80:d8:1a:bb:dc:
+                    81:db:68:cb:d7:c1:99:6e:34:f7:f5:b2:72:94:94:
+                    c4:0e:04:8c:6d:b7:5f:6b:bb:c5:3f:6b:50:08:42:
+                    9a:cc:5f:41:96:ff:a2:fd:67:37:38:77:6f:d3:f4:
+                    2c:93
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                20:1D:40:05:AB:57:09:3B:3A:83:BE:19:65:15:EB:74:EF:0C:0D:D7
+            X509v3 Authority Key Identifier: 
+                20:1D:40:05:AB:57:09:3B:3A:83:BE:19:65:15:EB:74:EF:0C:0D:D7
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+            X509v3 CRL Distribution Points: 
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        24:3d:9b:fe:64:91:03:db:33:58:d3:74:2a:57:a5:82:48:28:
+        80:2e:57:82:09:ff:3d:6e:1a:f4:2d:c3:ee:90:9b:07:88:1d:
+        25:97:65:5b:ef:90:54:b4:61:86:4c:15:5e:1a:a5:ee:d4:41:
+        af:6f:0a:2b:4e:b0:75:bd:d8:2c:8e:20:e9:6f:7a:d1:1b:4a:
+        b6:2d:c6:60:be:cb:56:7d:f1:c3:06:03:cd:c4:23:25:1f:09:
+        f5:44:d1:7c:9e:48:29:b4:a6:a7:55:40:f7:11:05:dc:45:5c:
+        45:3f:2d:a6:23:54:e7:74:a8:d8:a3:81:23:00:77:64:9c:d3:
+        1e:f1:f1:33:b6:a5:21:8e:af:a9:14:f5:37:6a:e3:6f:82:9f:
+        65:6d:ab:de:0a:a5:29:62:d9:01:57:bf:69:48:c9:93:be:c2:
+        2d:4b:e6:ed:0e:1e:e5:d8:fe:9c:8b:fc:36:09:08:45:f8:31:
+        45:21:22:0b:62:c3:61:82:8f:65:bb:01:14:37:c2:b0:31:2f:
+        a2:40:b9:91:21:54:50:b6:24:39:6b:c1:a5:90:3f:b4:77:9c:
+        13:d7:0a:dc:3d:85:ef:77:fa:53:6b:fc:cf:8a:3e:45:db:5c:
+        8b:4b:6b:a5:d8:ee:0b:19:f2:c1:a3:02:e0:ba:36:43:c8:e8:
+        8b:c8:9a:2f
+-----BEGIN CERTIFICATE-----
+MIIDeDCCAmCgAwIBAgIUVONNfV79E3ufMlXzd1XmOVJv7z8wDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEUm9vdDAeFw0yMTEwMDUxMjAwMDBaFw0yMjEwMDUxMjAw
+MDBaMA8xDTALBgNVBAMMBFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCdeFXzDBmHlWFh22wHcbuU/aWNzKey1go2hQoHHGviBmMGK8rGrKj+mwL0
+wLzPEs1JGCyQNVUWoStJdwqj4gQi6CxYIXz0tr0ZYcZQT6gPsemW5/z2HbutWGkl
+6dttkcxhK+WTnak3w/MpdFjNuYVIuMpJFHsYVO7FxxiYSJH4wsLsFWcnv6YbKSWX
+Z84HJRNWGrBCyB0bM0mD9NpnUnkiPA2fDOWRh1/+9kNwvC9oyNY3j8+Xe8nWP54G
+xrDqILZ/sDOgD1sFKhoCuSKA2Bq73IHbaMvXwZluNPf1snKUlMQOBIxtt19ru8U/
+a1AIQprMX0GW/6L9Zzc4d2/T9CyTAgMBAAGjgcswgcgwHQYDVR0OBBYEFCAdQAWr
+Vwk7OoO+GWUV63TvDA3XMB8GA1UdIwQYMBaAFCAdQAWrVwk7OoO+GWUV63TvDA3X
+MDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAoYbaHR0cDovL3VybC1mb3ItYWlh
+L1Jvb3QuY2VyMCwGA1UdHwQlMCMwIaAfoB2GG2h0dHA6Ly91cmwtZm9yLWNybC9S
+b290LmNybDAOBgNVHQ8BAf8EBAMCBaAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
+9w0BAQsFAAOCAQEAJD2b/mSRA9szWNN0KlelgkgogC5Xggn/PW4a9C3D7pCbB4gd
+JZdlW++QVLRhhkwVXhql7tRBr28KK06wdb3YLI4g6W960RtKti3GYL7LVn3xwwYD
+zcQjJR8J9UTRfJ5IKbSmp1VA9xEF3EVcRT8tpiNU53So2KOBIwB3ZJzTHvHxM7al
+IY6vqRT1N2rjb4KfZW2r3gqlKWLZAVe/aUjJk77CLUvm7Q4e5dj+nIv8NgkIRfgx
+RSEiC2LDYYKPZbsBFDfCsDEvokC5kSFUULYkOWvBpZA/tHecE9cK3D2F73f6U2v8
+z4o+Rdtci0trpdjuCxnywaMC4Lo2Q8joi8iaLw==
+-----END CERTIFICATE-----
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/generate-chains.py b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/generate-chains.py
new file mode 100755
index 0000000..693a2e12
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/generate-chains.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+# 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.
+"""Certificate chain where the root keyUsage extension is present but does not
+contain keyCertSign."""
+
+import sys
+sys.path += ['../..']
+
+import gencerts
+
+# Self-signed root certificate without keyCertSign.
+root = gencerts.create_self_signed_root_certificate('Root')
+root.get_extensions().set_property('keyUsage',
+                                   'critical,digitalSignature,keyEncipherment')
+
+# Intermediate certificate.
+intermediate = gencerts.create_intermediate_certificate('Intermediate', root)
+
+# Target certificate.
+target = gencerts.create_end_entity_certificate('Target', intermediate)
+
+chain = [target, intermediate, root]
+gencerts.write_chain(__doc__, chain, 'chain.pem')
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Intermediate.key b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Intermediate.key
new file mode 100644
index 0000000..c59282d5
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Intermediate.key
@@ -0,0 +1,29 @@
+openssl genrsa 2048
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcONphuK0Nqt5E
+31J4AYKJ+1+9/Kl50I0QXDxOt7VDNrwFOgxOVIgARfszJcrPOqqtpN7SLsFTRLj1
+WOcnGVpwNHHHTXcfU/JmuXnowhCML10ZF37jJn4MXY6uhQH3BazOGF18pPr8OOYY
+Y6VM0KPLo+ZHrV/sMvwRAofL+ofJOKq4sn6YmRtO8wH8SGpgpSmAPAxQIzquCqbU
+KWkeFTTtkzH7MNTWI1mUifqZtBbYBGPQw6ktvv9qhMdUu+jrvRZbiDMQG24gT2BJ
+VgmG3JX8xdks3gMyiPw6hAZIkhB8LblqJf2TwFF1vVTnugu72LP1YFWexwZw/fPw
+E46oMxf/AgMBAAECggEAB0L0EU3HXkn4E4utgCsfZiPunKl1atZt8C0K/crq3IlB
+rraDiVKP92WvwJfI0d6GveD0UIdgcIS4889iZnFc84RxWeCSbgmo/1H7+WNBgXQ9
+wUntZntmdlNIK6y+dvAaSuj1oExLTe6kV2bhiwZs6EbU9rXEeyyyUph9BApZrHM1
+jwTbVsDRqq26gJq889SvvfIN2kwUaptFe9sAu7VNRlTU5XUVSEVwjaGs5ddDUKVT
+TYHSPMBCRQMbgXxp3pBJAlBmSxRd+T2zsEOIPuUE/an3kyNSGXl7H+SBizNnqqsw
+vkmq/e8yWnQW6niDJkdxm4Y8PetzdEl3zSZ43v5duQKBgQDAfN327WDp4Qyq0E3H
+VnRzKJA/21eau9tHqO20lKccAH1E8YQAdNsA2pannU0z10vcKifT9Z32VqtjBNxQ
+iOBah1q0EeKZL9oemmlc7MMpA8Ps3ddJuYgkt7wb/2PjnfHWXL+zfl0CC/nhAq8c
+qMd9PLp22RkfaTvFBEcCFRYJdwKBgQDPxK6f678E9Rs1mtpt6iTZP5yfMJ9T/Jkh
+4P8h/CDcaKKoyMz+0nx0mokIUXmn38RKeDf3/u8oe/9VL55etsSZy02BQWD4tFlP
+3xv18DBpLPZbPqO5/FZDz43RWgiyoYm+4GOElMEhI3R+pXM0WtOS/vL8HPF6cZeR
+xYOneBwHuQKBgB7+vFs7pCRk+b4zpqKXmE7G1FuD/VpML6YdXJF8cmA+7+z/Gutb
+5bwAdsvst3bGj0+XdixaW6JEGHrsWHGbaM0LCJ8AVkWmf+/3m0m8Ujyzf4QPwM/9
+UR/geijj+fi4AS4sZy4HBgEDXqxN21a5Es5mzfu/P51gO6Cg7LI4JQFHAoGAcOiC
+uXekzC3jRoNL1tWahtFR7RTAUSUBlu6t16srtHvMgYr2FDkQ11EvzOxx/2/UPWAN
+kmDhWQ/hl0qUdwY92xInoWmFKELiEky95i5MMKv1iWqGEUl3G8zMVgvmlCTDNY69
+pkwslDSvVy/UZoxBVC3moGmeNRtL8jTzMexM10kCgYANE5y/x9O+dNCDaUd6lcR3
+eV54CEGgWliifpKNVmeZfUy0zjx3pOiUaDjvLwtKtYnNvvpNlxq0weMRRqqb3isx
+R/HgGuqX5rdz9bz6pP7n2sqQ67Y/bN7HD8F+B3gn2UmjekscEW7p31sgPNtfa0+u
+zONbHBAlP5YbWROTvuw6Wg==
+-----END PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Root.key b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Root.key
new file mode 100644
index 0000000..f92bfb3
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Root.key
@@ -0,0 +1,29 @@
+openssl genrsa 2048
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdeFXzDBmHlWFh
+22wHcbuU/aWNzKey1go2hQoHHGviBmMGK8rGrKj+mwL0wLzPEs1JGCyQNVUWoStJ
+dwqj4gQi6CxYIXz0tr0ZYcZQT6gPsemW5/z2HbutWGkl6dttkcxhK+WTnak3w/Mp
+dFjNuYVIuMpJFHsYVO7FxxiYSJH4wsLsFWcnv6YbKSWXZ84HJRNWGrBCyB0bM0mD
+9NpnUnkiPA2fDOWRh1/+9kNwvC9oyNY3j8+Xe8nWP54GxrDqILZ/sDOgD1sFKhoC
+uSKA2Bq73IHbaMvXwZluNPf1snKUlMQOBIxtt19ru8U/a1AIQprMX0GW/6L9Zzc4
+d2/T9CyTAgMBAAECggEAOd1W5B2dogOECfQaEfA6qIi+lclPFHnvvdIMecl5YVDM
+gZ+E/5XB8fFfWDYdC5DSq9n8wi8+6bZm1DOwzsEGmRcs+GOx6bDe8x1JRQjIbqAH
+gMMV4xDQ/uV+mgaB8hUB//EkBycH4CyaxbBx04MpVaxF9S3X//xx026SfJ8qOLkx
+VLGP2sIeOexuv7luEnRFCXLFLaEocZ0O6F55E3q4scl2iVs4QumE6PVHoz+TNkIv
+jT5JFnxYAdpkmJZAne2T9cIJjBz4pUHlwhK5eIXzUEirJIT3Dfx5xkSjY+4rOiAU
+Q0KtadzjTTCqMQpbp3lS9hFLd2xoLtW+C90kDzVoOQKBgQDNDAN1CoN2xcT4Wcwl
+QfPrjzE8uh2Xb2kF9jjeoVx4Z+F40AebkrwVmsSP3DM3j3sI8ijHfDsu1/HsF421
+7WHDnRqsojTAY2hFLYFOJZikK4288wwE5RPLbXz6FqYxsyqDlPOYMgt0GDuMZrFU
+Aw0Y5WjYAu0yz+DsyjQ0pMXFeQKBgQDEmbys+k9jbRF3mh7Ai7jP04rlHijH200p
+u91xQKwFSGnErvEINvSUc6NAYDzk4u+WzDs8woi4ZOrK/Ayn6+vxJLxCn1NI/BjJ
+H8XMuKvt6pAeX8ZYGPfjendq1E+RKVMYZifnEpPd6P4gY8oOHVbx91SJWB6U/MUy
+HlUs99X7awKBgQC6Ux1dIMWfi01WpDrW7FybhE4osbMsJggYa8r3u0eM/lCr+NXA
+8BDLzQyq8Vz1MwOJeIvH4kLhaUej0y9pzyEUoVFY6DjpTiu1GQm0GoPfQtiUh4M3
+e3aGV6LGmwhAh1+tnA/TED7KKy8JgVPIVNF0+xpPmDE1CCOK6J+R6tzweQKBgDk6
+Wy6rClLx28TJ6yu3QEvW0zaQieRrVNHSKsqdvbUn+AnVtrnibV2NL7c8jF1AJefq
+eU/dfLjYP3Ro3DJBPYQLYnWuNSsonvpksko+c2WlAuCklnLsibQA4SQKu0KRGewf
+SHw97ycIfHjnM1jad4Wlrkjwiq5pyekl5TtaxUITAoGAclnWjbVoNGwd+MKtKlHA
+gYyVqTEDoMeaHOg+SkNE00BHpUG8XcdYBlUR56XWRHBWxRn3AotBeVUjJYe385y2
+2wBXEYZ6tBkWK9YYy3w5CylUNXIAz03itb+xGZ/r1ZpEdYGta9yDUfRoLJ2Y6Eej
+xy1t92XPwm6o9yqrpa8KqwM=
+-----END PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Target.key b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Target.key
new file mode 100644
index 0000000..908f58c
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/keys/Target.key
@@ -0,0 +1,29 @@
+openssl genrsa 2048
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxHKS5IIFyS/Us
+UH4/GoUnWy1QfOU2/ZysJqB9t5TVa/3DRhrcTRBFmMyHja/8DrzJsmFLjMKbyNp7
+BbM74CGVpZwBcjRIPeRE8ue2a1j0jWCSyJEpQSMpI/3aYtDDeJJfAQlVLjqisywt
+xc15JnpmvOOlF1GutylQdRBvLFWpeQQhBTsUMmV8Oiwz6mtyIPGHMfKPJ2lPUB3C
+GDaOuG3EuAt6I4fhSITsRJh336d6BjdNQjNA4rLJZy+UIGlcbTAauMVgnjJuTbKF
+3pS5hlD5DHIGNLvwSvzNyIl762nlZOhVTBJ5zIGIJvZZItVgjDZPljDi6vQQ3III
+0jviBRN3AgMBAAECggEAHvw1X0PuEZpzUqTkaVLQ78E2TMoE6dl1svJxCn13Ft11
+QFf/9AGxcpOYWLCzlKPZaGoCNo7yLwH1IyesozGDfqRCBki+F9NoH53lZtch8WrY
+deWL680/AwAOdcnq3v+1j9RlFxdm37b76CgeWe8e4+Fw7B2Hx0q4+h/415JIjETa
+OWUkhe77FaA1DUOjrgfv9jhLag/3OLzO9Ae/sFJIoYACVB5lbOPjBGy6bpXfDgOC
+ppgn4NsUnkG/ouTm1c1SfgCxpYdpGZ9tQ08IqjuFwupd2dfBUb8mKnYf2gs5nnmt
+IWxi+656pnaGhmUjJLFaQ4y8f31tSpudWddukzQWBQKBgQC+dDV4hvY0aL52eL+P
+JKpYkAAWjXikg6KGYFQEl1MDIMhUQtWgxgfLU5uLD4ljST5qos6cWfO/ZWrriAAE
+Na5ooT4l5wVwgaRLeqwU4+06fpGsuXFvutkDdULdR8evJfwsSQyDUHlu3fWwLcaB
+irvbc/CVYIvIzg7orD/1GGAzkwKBgQDuEPGcXo8EN5HYD5LXtd2eT0LhouowHv5p
+9aDKG1g4zFubkkB13UZgQXE5ivA5ll7pJIWv0dh9UDeSK1noE8vpniMd4+uxAqdw
+sf2pPES43su24e7SfQpJEJ63sAYhWdJrzJ/qyxzroxO+65D8ozgUdeBlR/ECHCUA
+m0i1RibXDQKBgQCbDKyDZQyHekak2ITLGkR8OS95LM9sz6W+1ClSW4e/Yi71OjwE
+2XN6+qQEwC9PX0+rLMQb0bd4uC4ldeDdjH5iu/KGlN9+ymxg7outrilxl50tCwPo
+vCr0f8BhuZA9bSUxQH8pYJibw6PDPGEBEVsCvA6+7Yyfe/HzRlgDR0b6dQKBgG9+
+Zflq26YI9HfxCz+VQB6VVmhgKTeyPEqZq90bo1yucLTScPgCUqRf4cwmQs1lnDuq
+TCYErFQ8DlqZjPjA1L4rvpyQEuEKsip4YakxvamrRlL3SycvQnLnor26ZZSXAZJU
++gw3ZesBrAy9PGDlfC0w64/jen8XeUjocMvc9/G9AoGAUd3EFeCC1bCHDrzzYtEY
+54WY1dV7zT3ePOFpVEZ0KHe4kgszdGHC/olRmmyxnsv+gyOuJ38uEH85WQ93Uxvu
+gPp4xRUGmRMc40qKW8DKQhQru1p7I92D2Nplf65Uh7XR8AKc8fvzoowB3q95UzjC
+lkPYeGUpv6L/Tt1IEy87iP8=
+-----END PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/main.test b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/main.test
new file mode 100644
index 0000000..c898fa45
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/main.test
@@ -0,0 +1,5 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: DEFAULT
+key_purpose: SERVER_AUTH
+expected_errors:
diff --git a/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/ta-with-constraints.test b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/ta-with-constraints.test
new file mode 100644
index 0000000..4ddcdb7
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/root-lacks-keycertsign-key-usage/ta-with-constraints.test
@@ -0,0 +1,8 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR_WITH_CONSTRAINTS
+utc_time: DEFAULT
+key_purpose: SERVER_AUTH
+expected_errors:
+----- Certificate i=2 (CN=Root) -----
+ERROR: keyCertSign bit is not set
+
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 059cce8d..e9c9e03 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -464,8 +464,7 @@
     const X509Certificate* served_certificate_chain,
     const SignedCertificateTimestampAndStatusList&
         signed_certificate_timestamps,
-    ct::CTPolicyCompliance policy_compliance,
-    const NetworkAnonymizationKey& network_anonymization_key) {
+    ct::CTPolicyCompliance policy_compliance) {
   using CTRequirementLevel = RequireCTDelegate::CTRequirementLevel;
   std::string hostname = host_port_pair.host();
 
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index 8ef76b9d..f34123b 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -358,8 +358,7 @@
       const X509Certificate* served_certificate_chain,
       const SignedCertificateTimestampAndStatusList&
           signed_certificate_timestamps,
-      ct::CTPolicyCompliance policy_compliance,
-      const NetworkAnonymizationKey& network_anonymization_key);
+      ct::CTPolicyCompliance policy_compliance);
 
   // Assign a |Delegate| for persisting the transport security state. If
   // |NULL|, state will not be persisted. The caller retains
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index ce7bdc7..e418213 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -1123,8 +1123,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey());
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS);
 
     MockRequireCTDelegate always_require_delegate;
     EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_, _, _))
@@ -1135,29 +1134,25 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
     EXPECT_EQ(
         TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
     EXPECT_EQ(
         TransportSecurityState::CT_REQUIREMENTS_MET,
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));
     EXPECT_EQ(
         TransportSecurityState::CT_REQUIREMENTS_MET,
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY));
 
     state.SetRequireCTDelegate(nullptr);
     EXPECT_EQ(
@@ -1165,8 +1160,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   }
 
   // If CT is not required, then regardless of the CT state for the host,
@@ -1177,8 +1171,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey());
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS);
 
     MockRequireCTDelegate never_require_delegate;
     EXPECT_CALL(never_require_delegate, IsCTRequiredForHost(_, _, _))
@@ -1189,15 +1182,13 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
     EXPECT_EQ(
         TransportSecurityState::CT_NOT_REQUIRED,
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
 
     state.SetRequireCTDelegate(nullptr);
     EXPECT_EQ(
@@ -1205,8 +1196,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   }
 
   // If the Delegate is in the default state, then it should return the same
@@ -1217,8 +1207,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey());
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS);
 
     MockRequireCTDelegate default_require_ct_delegate;
     EXPECT_CALL(default_require_ct_delegate, IsCTRequiredForHost(_, _, _))
@@ -1229,8 +1218,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
 
     state.SetRequireCTDelegate(nullptr);
     EXPECT_EQ(
@@ -1238,8 +1226,7 @@
         state.CheckCTRequirements(
             HostPortPair("www.example.com", 443), true, hashes, cert.get(),
             cert.get(), SignedCertificateTimestampAndStatusList(),
-            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            NetworkAnonymizationKey()));
+            ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   }
 }
 
@@ -1300,39 +1287,29 @@
             state_.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, hashes, cert.get(),
                 cert.get(), SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED,
             state_.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, hashes, cert.get(),
                 cert.get(), SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
   EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED,
             state_.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, hashes, cert.get(),
                 cert.get(), SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));
   EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED,
             state_.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, hashes, cert.get(),
                 cert.get(), SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY));
 
   state_.SetRequireCTDelegate(nullptr);
   EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED,
             state_.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, hashes, cert.get(),
                 cert.get(), SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -1374,9 +1351,7 @@
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
 
   // ... but certificates issued after 1 June 2016 are required to be...
   EXPECT_EQ(
@@ -1384,33 +1359,25 @@
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));
 
   // ... unless they were issued by an excluded intermediate.
   hashes.push_back(HashValue(google_hash_value));
@@ -1419,17 +1386,13 @@
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_NOT_REQUIRED,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
 
   // And other certificates should remain unaffected.
   SHA256HashValue unrelated_hash_value = {{0x01, 0x02}};
@@ -1441,17 +1404,13 @@
                 HostPortPair("www.example.com", 443), true, unrelated_hashes,
                 before_cert.get(), before_cert.get(),
                 SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(TransportSecurityState::CT_NOT_REQUIRED,
             state.CheckCTRequirements(
                 HostPortPair("www.example.com", 443), true, unrelated_hashes,
                 after_cert.get(), after_cert.get(),
                 SignedCertificateTimestampAndStatusList(),
-
-                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-                NetworkAnonymizationKey()));
+                ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
 }
 
 // Tests that Certificate Transparency is required for all of the Symantec
@@ -1483,33 +1442,25 @@
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
           before_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));
 
   scoped_refptr<X509Certificate> after_cert =
       ImportCertFromFile(GetTestCertsDirectory(), "post_june_2016.pem");
@@ -1520,33 +1471,25 @@
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY));
   EXPECT_EQ(
       TransportSecurityState::CT_REQUIREMENTS_MET,
       state.CheckCTRequirements(
           HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
           after_cert.get(), SignedCertificateTimestampAndStatusList(),
-
-          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-          NetworkAnonymizationKey()));
+          ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));
 }
 
 #if BUILDFLAG(INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST)
diff --git a/net/quic/crypto/proof_verifier_chromium.cc b/net/quic/crypto/proof_verifier_chromium.cc
index 8da0467..9f3e9af1 100644
--- a/net/quic/crypto/proof_verifier_chromium.cc
+++ b/net/quic/crypto/proof_verifier_chromium.cc
@@ -534,8 +534,7 @@
           cert_verify_result.is_issued_by_known_root,
           cert_verify_result.public_key_hashes,
           cert_verify_result.verified_cert.get(), cert_.get(),
-          cert_verify_result.scts, cert_verify_result.policy_compliance,
-          proof_verifier_->network_anonymization_key_);
+          cert_verify_result.scts, cert_verify_result.policy_compliance);
 
   if (sct_auditing_delegate_) {
     sct_auditing_delegate_->MaybeEnqueueReport(
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index 391247f..56904f01 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -1340,8 +1340,7 @@
           server_cert_verify_result_.public_key_hashes,
           server_cert_verify_result_.verified_cert.get(), server_cert_.get(),
           server_cert_verify_result_.scts,
-          server_cert_verify_result_.policy_compliance,
-          ssl_config_.network_anonymization_key);
+          server_cert_verify_result_.policy_compliance);
 
   if (context_->sct_auditing_delegate()) {
     context_->sct_auditing_delegate()->MaybeEnqueueReport(
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index b85173a..c6e4e71b 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -882,7 +882,7 @@
       HostPortPair(new_hostname, 0), ssl_info.is_issued_by_known_root,
       ssl_info.public_key_hashes, ssl_info.cert.get(),
       ssl_info.unverified_cert.get(), ssl_info.signed_certificate_timestamps,
-      ssl_info.ct_policy_compliance, network_anonymization_key)) {
+      ssl_info.ct_policy_compliance)) {
     case TransportSecurityState::CT_REQUIREMENTS_NOT_MET:
       return false;
     case TransportSecurityState::CT_REQUIREMENTS_MET:
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 8ad7cb3..6bae8bc 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -1342,8 +1342,7 @@
 int NetworkContext::CheckCTComplianceForSignedExchange(
     net::CertVerifyResult& cert_verify_result,
     const net::X509Certificate& certificate,
-    const net::HostPortPair& host_port_pair,
-    const net::NetworkAnonymizationKey& network_anonymization_key) {
+    const net::HostPortPair& host_port_pair) {
   net::X509Certificate* verified_cert = cert_verify_result.verified_cert.get();
 
   net::ct::SCTList verified_scts;
@@ -1373,8 +1372,7 @@
       url_request_context_->transport_security_state()->CheckCTRequirements(
           host_port_pair, cert_verify_result.is_issued_by_known_root,
           cert_verify_result.public_key_hashes, verified_cert, &certificate,
-          cert_verify_result.scts, cert_verify_result.policy_compliance,
-          network_anonymization_key);
+          cert_verify_result.scts, cert_verify_result.policy_compliance);
 
   if (url_request_context_->sct_auditing_delegate()) {
     url_request_context_->sct_auditing_delegate()->MaybeEnqueueReport(
@@ -2696,8 +2694,7 @@
 #if BUILDFLAG(IS_CT_SUPPORTED)
     int ct_result = CheckCTComplianceForSignedExchange(
         *pending_cert_verify->result, *pending_cert_verify->certificate,
-        net::HostPortPair::FromURL(pending_cert_verify->url),
-        pending_cert_verify->network_anonymization_key);
+        net::HostPortPair::FromURL(pending_cert_verify->url));
 #endif  // BUILDFLAG(IS_CT_SUPPORTED)
     net::TransportSecurityState::PKPStatus pin_validity =
         url_request_context_->transport_security_state()->CheckPublicKeyPins(
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 5853a395..cca5788e 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -677,8 +677,7 @@
   int CheckCTComplianceForSignedExchange(
       net::CertVerifyResult& cert_verify_result,
       const net::X509Certificate& certificate,
-      const net::HostPortPair& host_port_pair,
-      const net::NetworkAnonymizationKey& network_anonymization_key);
+      const net::HostPortPair& host_port_pair);
 #endif  // BUILDFLAG(IS_CT_SUPPORTED)
 
 #if BUILDFLAG(IS_DIRECTORY_TRANSFER_REQUIRED)
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.cc b/services/tracing/public/cpp/perfetto/perfetto_config.cc
index ee543eee..e18b233b 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_config.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.cc
@@ -7,6 +7,7 @@
 #include <cstdint>
 #include <string>
 
+#include "base/command_line.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/trace_event/memory_dump_manager.h"
@@ -16,6 +17,7 @@
 #include "build/build_config.h"
 #include "build/chromecast_buildflags.h"
 #include "build/chromeos_buildflags.h"
+#include "components/tracing/common/tracing_switches.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
 
 namespace tracing {
@@ -168,6 +170,22 @@
   }
 }
 
+size_t GetDefaultTraceBufferSize() {
+  auto* command_line = base::CommandLine::ForCurrentProcess();
+  std::string switch_value = command_line->GetSwitchValueASCII(
+      switches::kDefaultTraceBufferSizeLimitInKb);
+  size_t switch_kilobytes;
+  if (!switch_value.empty() &&
+      base::StringToSizeT(switch_value, &switch_kilobytes)) {
+    return switch_kilobytes;
+  } else {
+    // TODO(eseckler): Reduce the default buffer size after benchmarks set
+    // what they require. Should also invest some time to reduce the overhead
+    // of begin/end pairs further.
+    return 200 * 1024;
+  }
+}
+
 }  // namespace
 
 perfetto::TraceConfig GetDefaultPerfettoConfig(
@@ -193,10 +211,8 @@
 
   size_t size_limit = chrome_config.GetTraceBufferSizeInKb();
   if (size_limit == 0) {
-    // TODO(eseckler): Reduce the default buffer size after benchmarks set what
-    // they require. Should also invest some time to reduce the overhead of
-    // begin/end pairs further.
-    size_limit = 200 * 1024;
+    // If trace config did not provide trace buffer size, we will use default
+    size_limit = GetDefaultTraceBufferSize();
   }
   auto* buffer_config = perfetto_config.add_buffers();
   buffer_config->set_size_kb(size_limit);
diff --git a/services/viz/public/cpp/gpu/context_provider_command_buffer.h b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
index 04de90c5..e434c1b 100644
--- a/services/viz/public/cpp/gpu/context_provider_command_buffer.h
+++ b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
@@ -84,7 +84,8 @@
       command_buffer_metrics::ContextType type,
       base::SharedMemoryMapper* buffer_mapper = nullptr);
 
-  gpu::CommandBufferProxyImpl* GetCommandBufferProxy();
+  // Virtual for testing.
+  virtual gpu::CommandBufferProxyImpl* GetCommandBufferProxy();
   // Gives the GL internal format that should be used for calling CopyTexImage2D
   // on the default framebuffer.
   uint32_t GetCopyTextureInternalFormat();
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index f8a163a..4d4e419 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -27252,7 +27252,8 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -27705,7 +27706,8 @@
         "args": [
           "--use-cmd-decoder=validating",
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64_rel.gl_tests.filter"
         ],
         "isolate_profile_data": true,
         "merge": {
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 94e28e4..7b00151 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -130,6 +130,7 @@
     "//testing/buildbot/filters/android.emulator_12.oop_network_service_content_browsertests.filter",
     "//testing/buildbot/filters/android.emulator_n.content_browsertests.filter",
     "//testing/buildbot/filters/android.emulator_p.content_browsertests.filter",
+    "//testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter",
     "//testing/buildbot/filters/cast-linux.content_browsertests.filter",
     "//testing/buildbot/filters/chromium.webrtc.fyi.android.tests.dbg.content_browsertests.filter",
     "//testing/buildbot/filters/linux-lacros.content_browsertests.filter",
diff --git a/testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter b/testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter
new file mode 100644
index 0000000..da3dbfd
--- /dev/null
+++ b/testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter
@@ -0,0 +1,21 @@
+# crbug.com/1403109
+-DomSerializerTests.SerializeHTMLDOMWithAddingMOTW
+-DomSerializerTests.SerializeHTMLDOMWithBaseTag
+-DomSerializerTests.SerializeHTMLDOMWithEmptyHead
+-DomSerializerTests.SerializeHTMLDOMWithEntitiesInAttributeValue
+-DomSerializerTests.SerializeHTMLDOMWithEntitiesInText
+-DomSerializerTests.SerializeHTMLDOMWithNonStandardEntities
+-DomSerializerTests.SerializeHTMLDOMWithoutDocType
+-DomSerializerTests.SerializeOffTheRecordHTMLDOMWithAddingMOTW
+-DomSerializerTests.SerializeXMLDocWithBuiltInEntities
+-DomSerializerTests.SubResourceForElementsInNonHTMLNamespace
+-RenderThreadImplDiscardableMemoryBrowserTest.CheckReleaseMemory
+-RenderThreadImplDiscardableMemoryBrowserTest.LockDiscardableMemory
+-RenderThreadImplDiscardableMemoryBrowserTest.ReleaseFreeDiscardableMemory
+-RenderViewBrowserTest.ConfirmCacheInformationPlumbed
+-SavableResourcesTest.GetSavableResourceLinksWithPageHasInvalidLinks
+-SavableResourcesTest.GetSavableResourceLinksWithPageHasValidLinks
+-SavableResourcesTest.GetSavableResourceLinksWithPageHasValidStyleLink
+-SingleProcessMemoryTracingTest.BrowserInitiatedSingleDump
+-SingleProcessMemoryTracingTest.ManyInterleavedDumps
+-SingleProcessMemoryTracingTest.RendererInitiatedSingleDump
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index aadda2d..8904a2a9 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1735,10 +1735,15 @@
           ],
         },
       },
+      'android-pie-arm64-coverage-experimental-rel': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64.content_browsertests_coverage.filter',
+        ],
+      },
       'android-pie-arm64-rel': {
         'swarming': {
           'quickrun_shards': 40
-        }
+        },
       },
       'android-pie-x86-rel': {
         'args': [
@@ -2234,6 +2239,11 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_n.gl_tests.filter',
         ],
       },
+      'android-pie-arm64-coverage-experimental-rel': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64_rel.gl_tests.filter', # https://crbug.com/1034007
+        ],
+      },
       'android-pie-arm64-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64_rel.gl_tests.filter', # https://crbug.com/1034007
diff --git a/third_party/blink/common/messaging/string_message_codec.cc b/third_party/blink/common/messaging/string_message_codec.cc
index 249b797..726124e 100644
--- a/third_party/blink/common/messaging/string_message_codec.cc
+++ b/third_party/blink/common/messaging/string_message_codec.cc
@@ -32,6 +32,15 @@
 
   size_t GetLength() const override { return length_; }
 
+  bool GetIsResizableByUserJavaScript() const override {
+    // VectorArrayBuffers are not used for ArrayBuffer transfers and are
+    // currently always fixed-length. Structured cloning resizables ArrayBuffers
+    // is not yet supported in SMC.
+    return false;
+  }
+
+  size_t GetMaxByteLength() const override { return length_; }
+
   absl::optional<base::span<const uint8_t>> GetAsSpanIfPossible()
       const override {
     return base::make_span(data_).subspan(position_, length_);
@@ -51,11 +60,22 @@
 // An ArrayBufferPayload impl based on mojo::BigBuffer.
 class BigBufferArrayBuffer : public WebMessageArrayBufferPayload {
  public:
-  explicit BigBufferArrayBuffer(mojo_base::BigBuffer data)
-      : data_(std::move(data)) {}
+  explicit BigBufferArrayBuffer(mojo_base::BigBuffer data,
+                                absl::optional<size_t> max_byte_length)
+      : data_(std::move(data)), max_byte_length_(max_byte_length) {
+    DCHECK(!max_byte_length || *max_byte_length >= GetLength());
+  }
 
   size_t GetLength() const override { return data_.size(); }
 
+  bool GetIsResizableByUserJavaScript() const override {
+    return max_byte_length_.has_value();
+  }
+
+  size_t GetMaxByteLength() const override {
+    return max_byte_length_.value_or(GetLength());
+  }
+
   absl::optional<base::span<const uint8_t>> GetAsSpanIfPossible()
       const override {
     return base::make_span(data_);
@@ -68,6 +88,7 @@
 
  private:
   mojo_base::BigBuffer data_;
+  absl::optional<size_t> max_byte_length_;
 };
 
 const uint32_t kVarIntShift = 7;
@@ -147,8 +168,11 @@
 
 // static
 std::unique_ptr<WebMessageArrayBufferPayload>
-WebMessageArrayBufferPayload::CreateFromBigBuffer(mojo_base::BigBuffer buffer) {
-  return std::make_unique<BigBufferArrayBuffer>(std::move(buffer));
+WebMessageArrayBufferPayload::CreateFromBigBuffer(
+    mojo_base::BigBuffer buffer,
+    absl::optional<size_t> max_byte_length) {
+  return std::make_unique<BigBufferArrayBuffer>(std::move(buffer),
+                                                max_byte_length);
 }
 
 // static
@@ -191,7 +215,9 @@
             array_buffer->CopyInto(base::make_span(big_buffer));
             message.array_buffer_contents_array.push_back(
                 mojom::SerializedArrayBufferContents::New(
-                    std::move(big_buffer)));
+                    std::move(big_buffer),
+                    array_buffer->GetIsResizableByUserJavaScript(),
+                    array_buffer->GetMaxByteLength()));
           }},
       payload);
 
@@ -279,9 +305,14 @@
         return absl::nullopt;
       if (message.array_buffer_contents_array.size() != 1)
         return absl::nullopt;
+      auto& array_buffer_contents = message.array_buffer_contents_array[0];
+      absl::optional<size_t> max_byte_length;
+      if (array_buffer_contents->is_resizable_by_user_javascript) {
+        max_byte_length.emplace(array_buffer_contents->max_byte_length);
+      }
       return absl::make_optional(
           WebMessagePayload(std::make_unique<BigBufferArrayBuffer>(
-              std::move(message.array_buffer_contents_array[0]->contents))));
+              std::move(array_buffer_contents->contents), max_byte_length)));
     }
   }
 
diff --git a/third_party/blink/common/messaging/string_message_codec_unittest.cc b/third_party/blink/common/messaging/string_message_codec_unittest.cc
index 901e7f19..f56d757 100644
--- a/third_party/blink/common/messaging/string_message_codec_unittest.cc
+++ b/third_party/blink/common/messaging/string_message_codec_unittest.cc
@@ -118,9 +118,12 @@
                 // Copy data into a new array_buffer_contents_array slot.
                 mojo_base::BigBuffer big_buffer(array_buffer->GetLength());
                 array_buffer->CopyInto(big_buffer);
+                constexpr bool is_resizable_by_user_js = false;
+                constexpr size_t max_byte_length = 0;
                 transferable_message.array_buffer_contents_array.push_back(
                     mojom::SerializedArrayBufferContents::New(
-                        std::move(big_buffer)));
+                        std::move(big_buffer), is_resizable_by_user_js,
+                        max_byte_length));
               }
               EXPECT_TRUE(
                   serializer.WriteValue(context, message_as_array_buffer)
diff --git a/third_party/blink/public/common/messaging/string_message_codec.h b/third_party/blink/public/common/messaging/string_message_codec.h
index 07abce9..4155ac5 100644
--- a/third_party/blink/public/common/messaging/string_message_codec.h
+++ b/third_party/blink/public/common/messaging/string_message_codec.h
@@ -28,6 +28,14 @@
   // Returns the length of the payload.
   virtual size_t GetLength() const = 0;
 
+  // Represents an ArrayBuffer resizable by user JavaScript.
+  virtual bool GetIsResizableByUserJavaScript() const = 0;
+
+  // Returns the maximum length of the payload. If representing a resizable
+  // ArrayBuffer, this is >= GetLength(). Otherwise, if representing a
+  // fixed-length ArrayBuffer, this is == GetLength().
+  virtual size_t GetMaxByteLength() const = 0;
+
   // Convert the underlying buffer to a span if possible. Or return empty if
   // can't (like Java ByteArray). JNI API does not provide a way to get a
   // pointer to the underlying array memory, so another API |CopyInto| should be
@@ -41,8 +49,12 @@
   virtual void CopyInto(base::span<uint8_t> dest) const = 0;
 
   // Create a new WebMessageArrayBufferPayload from BigBuffer.
+  //
+  // If max_byte_length is not nullopt, then it must be >= buffer's length. The
+  // created BigBuffer represents a resizable ArrayBuffer.
   static std::unique_ptr<WebMessageArrayBufferPayload> CreateFromBigBuffer(
-      mojo_base::BigBuffer buffer);
+      mojo_base::BigBuffer buffer,
+      absl::optional<size_t> max_byte_length);
 
   // Create a new WebMessageArrayBufferPayload from vector for testing.
   static std::unique_ptr<WebMessageArrayBufferPayload> CreateForTesting(
diff --git a/third_party/blink/public/mojom/array_buffer/array_buffer_contents.mojom b/third_party/blink/public/mojom/array_buffer/array_buffer_contents.mojom
index fdd7da2..4d613f774 100644
--- a/third_party/blink/public/mojom/array_buffer/array_buffer_contents.mojom
+++ b/third_party/blink/public/mojom/array_buffer/array_buffer_contents.mojom
@@ -10,4 +10,10 @@
 // ArrayBufferContents.
 struct SerializedArrayBufferContents {
   mojo_base.mojom.BigBuffer contents;
+  // If is_resizable_by_user_javascript is false, then max_byte_length is
+  // unused.
+  //
+  // TODO(crbug.com/657632): Use a wrapped uint64 or uint64? once supported.
+  bool is_resizable_by_user_javascript = false;
+  uint64 max_byte_length = 0;
 };
diff --git a/third_party/blink/renderer/bindings/core/v8/idl_types.h b/third_party/blink/renderer/bindings/core/v8/idl_types.h
index 859728c..86b5eb9 100644
--- a/third_party/blink/renderer/bindings/core/v8/idl_types.h
+++ b/third_party/blink/renderer/bindings/core/v8/idl_types.h
@@ -240,6 +240,10 @@
 template <typename T>
 struct IDLBufferSourceTypeNoSizeLimit {};
 
+// [AllowResizable]
+template <typename T>
+struct IDLAllowResizable {};
+
 // IDL optional types
 //
 // IDLOptional represents an optional argument and supports a conversion from
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_buffer_sources.cc b/third_party/blink/renderer/bindings/core/v8/native_value_traits_buffer_sources.cc
index d22f6f23..8da4921 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_buffer_sources.cc
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_buffer_sources.cc
@@ -51,34 +51,20 @@
   kDoNotCheck,
 };
 
+enum class ResizableAllowance { kDisallowResizable, kAllowResizable };
+
 // The basic recipe of NativeValueTraits<T>::NativeValue function
 // implementation for buffer source types.
 template <typename RecipeTrait,
-          auto(*ToBlinkValue)(v8::Isolate*, v8::Local<v8::Value>),
+          auto (*ToBlinkValue)(v8::Isolate*, v8::Local<v8::Value>),
           Nullablity nullablity,
           BufferSizeCheck buffer_size_check,
+          ResizableAllowance allow_resizable,
           typename ScriptWrappableOrBufferSourceTypeName,
           bool (*IsSharedBuffer)(v8::Local<v8::Value>) = nullptr>
 auto NativeValueImpl(v8::Isolate* isolate,
                      v8::Local<v8::Value> value,
                      ExceptionState& exception_state) {
-  auto blink_value = ToBlinkValue(isolate, value);
-  if (LIKELY(RecipeTrait::IsNonNull(blink_value))) {
-    if constexpr (buffer_size_check == BufferSizeCheck::kCheck) {
-      if (DoesExceedSizeLimit(isolate, RecipeTrait::ByteLength(blink_value),
-                              exception_state)) {
-        return RecipeTrait::NullValue();
-      }
-    }
-    return RecipeTrait::ToReturnType(blink_value);
-  }
-
-  if constexpr (nullablity == Nullablity::kIsNullable) {
-    if (LIKELY(value->IsNullOrUndefined())) {
-      return RecipeTrait::NullValue();
-    }
-  }
-
   const char* buffer_source_type_name = nullptr;
   if constexpr (std::is_base_of_v<ScriptWrappable,
                                   ScriptWrappableOrBufferSourceTypeName>) {
@@ -89,6 +75,33 @@
     buffer_source_type_name = ScriptWrappableOrBufferSourceTypeName::GetName();
   }
 
+  auto blink_value = ToBlinkValue(isolate, value);
+  if (LIKELY(RecipeTrait::IsNonNull(blink_value))) {
+    if constexpr (allow_resizable == ResizableAllowance::kDisallowResizable) {
+      if (RecipeTrait::IsResizable(blink_value)) {
+        exception_state.ThrowTypeError(
+            ExceptionMessages::ResizableArrayBufferNotAllowed(
+                buffer_source_type_name));
+        return RecipeTrait::NullValue();
+      }
+    }
+
+    if constexpr (buffer_size_check == BufferSizeCheck::kCheck) {
+      if (DoesExceedSizeLimit(isolate, RecipeTrait::ByteLength(blink_value),
+                              exception_state)) {
+        return RecipeTrait::NullValue();
+      }
+    }
+
+    return RecipeTrait::ToReturnType(blink_value);
+  }
+
+  if constexpr (nullablity == Nullablity::kIsNullable) {
+    if (LIKELY(value->IsNullOrUndefined())) {
+      return RecipeTrait::NullValue();
+    }
+  }
+
   if constexpr (IsSharedBuffer != nullptr) {
     if (IsSharedBuffer(value)) {
       exception_state.ThrowTypeError(
@@ -106,32 +119,16 @@
 // The basic recipe of NativeValueTraits<T>::ArgumentValue function
 // implementation for buffer source types.
 template <typename RecipeTrait,
-          auto(*ToBlinkValue)(v8::Isolate*, v8::Local<v8::Value>),
+          auto (*ToBlinkValue)(v8::Isolate*, v8::Local<v8::Value>),
           Nullablity nullablity,
           BufferSizeCheck buffer_size_check,
+          ResizableAllowance allow_resizable,
           typename ScriptWrappableOrBufferSourceTypeName,
           bool (*IsSharedBuffer)(v8::Local<v8::Value>) = nullptr>
 auto ArgumentValueImpl(v8::Isolate* isolate,
                        int argument_index,
                        v8::Local<v8::Value> value,
                        ExceptionState& exception_state) {
-  auto blink_value = ToBlinkValue(isolate, value);
-  if (LIKELY(RecipeTrait::IsNonNull(blink_value))) {
-    if constexpr (buffer_size_check == BufferSizeCheck::kCheck) {
-      if (DoesExceedSizeLimit(isolate, RecipeTrait::ByteLength(blink_value),
-                              exception_state)) {
-        return RecipeTrait::NullValue();
-      }
-    }
-    return RecipeTrait::ToReturnType(blink_value);
-  }
-
-  if constexpr (nullablity == Nullablity::kIsNullable) {
-    if (LIKELY(value->IsNullOrUndefined())) {
-      return RecipeTrait::NullValue();
-    }
-  }
-
   const char* buffer_source_type_name = nullptr;
   if constexpr (std::is_base_of_v<ScriptWrappable,
                                   ScriptWrappableOrBufferSourceTypeName>) {
@@ -142,6 +139,33 @@
     buffer_source_type_name = ScriptWrappableOrBufferSourceTypeName::GetName();
   }
 
+  auto blink_value = ToBlinkValue(isolate, value);
+  if (LIKELY(RecipeTrait::IsNonNull(blink_value))) {
+    if constexpr (allow_resizable == ResizableAllowance::kDisallowResizable) {
+      if (RecipeTrait::IsResizable(blink_value)) {
+        exception_state.ThrowTypeError(
+            ExceptionMessages::ResizableArrayBufferNotAllowed(
+                buffer_source_type_name));
+        return RecipeTrait::NullValue();
+      }
+    }
+
+    if constexpr (buffer_size_check == BufferSizeCheck::kCheck) {
+      if (DoesExceedSizeLimit(isolate, RecipeTrait::ByteLength(blink_value),
+                              exception_state)) {
+        return RecipeTrait::NullValue();
+      }
+    }
+
+    return RecipeTrait::ToReturnType(blink_value);
+  }
+
+  if constexpr (nullablity == Nullablity::kIsNullable) {
+    if (LIKELY(value->IsNullOrUndefined())) {
+      return RecipeTrait::NullValue();
+    }
+  }
+
   if constexpr (IsSharedBuffer != nullptr) {
     if (IsSharedBuffer(value)) {
       exception_state.ThrowTypeError(
@@ -217,10 +241,15 @@
 
 template <typename T, typename unused = void>
 struct RecipeTrait {
-  static bool IsNonNull(const T* buffer) { return buffer; }
+  static bool IsNonNull(const T* buffer_view) { return buffer_view; }
   static T* NullValue() { return nullptr; }
-  static T* ToReturnType(T* buffer) { return buffer; }
-  static size_t ByteLength(const T* buffer) { return buffer->byteLength(); }
+  static T* ToReturnType(T* buffer_view) { return buffer_view; }
+  static size_t ByteLength(const T* buffer_view) {
+    return buffer_view->byteLength();
+  }
+  static bool IsResizable(const T* buffer_view) {
+    return buffer_view->BufferBase()->IsResizableByUserJavaScript();
+  }
 };
 
 template <typename T>
@@ -230,6 +259,9 @@
   static T* NullValue() { return nullptr; }
   static T* ToReturnType(T* buffer) { return buffer; }
   static size_t ByteLength(const T* buffer) { return buffer->ByteLength(); }
+  static bool IsResizable(const T* buffer) {
+    return buffer->IsResizableByUserJavaScript();
+  }
 };
 
 template <typename T>
@@ -260,6 +292,12 @@
   static size_t ByteLength(v8::Local<v8::Value> buffer) {
     return buffer.As<typename ABVTrait<T>::V8ViewType>()->ByteLength();
   }
+  static bool IsResizable(v8::Local<v8::Value> buffer) {
+    return buffer.As<typename ABVTrait<T>::V8ViewType>()
+        ->Buffer()
+        ->GetBackingStore()
+        ->IsResizableByUserJavaScript();
+  }
 };
 
 // ToBlinkValue implementation for the recipe functions
@@ -432,6 +470,7 @@
     ExceptionState& exception_state) {
   return NativeValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
                          Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable,
                          DOMArrayBuffer>(isolate, value, exception_state);
 }
 
@@ -442,6 +481,7 @@
     ExceptionState& exception_state) {
   return ArgumentValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
                            Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            DOMArrayBuffer>(isolate, argument_index, value,
                                            exception_state);
 }
@@ -454,6 +494,7 @@
     ExceptionState& exception_state) {
   return NativeValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
                          Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable,
                          DOMArrayBuffer>(isolate, value, exception_state);
 }
 
@@ -464,19 +505,46 @@
     ExceptionState& exception_state) {
   return ArgumentValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
                            Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            DOMArrayBuffer>(isolate, argument_index, value,
                                            exception_state);
 }
 
+// [AllowResizable] ArrayBuffer
+
+DOMArrayBuffer*
+NativeValueTraits<IDLAllowResizable<DOMArrayBuffer>>::NativeValue(
+    v8::Isolate* isolate,
+    v8::Local<v8::Value> value,
+    ExceptionState& exception_state) {
+  return NativeValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
+                         Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kAllowResizable, DOMArrayBuffer>(
+      isolate, value, exception_state);
+}
+
+DOMArrayBuffer*
+NativeValueTraits<IDLAllowResizable<DOMArrayBuffer>>::ArgumentValue(
+    v8::Isolate* isolate,
+    int argument_index,
+    v8::Local<v8::Value> value,
+    ExceptionState& exception_state) {
+  return ArgumentValueImpl<RecipeTrait<DOMArrayBuffer>, ToDOMArrayBuffer,
+                           Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kAllowResizable, DOMArrayBuffer>(
+      isolate, argument_index, value, exception_state);
+}
+
 // SharedArrayBuffer
 
 DOMSharedArrayBuffer* NativeValueTraits<DOMSharedArrayBuffer>::NativeValue(
     v8::Isolate* isolate,
     v8::Local<v8::Value> value,
     ExceptionState& exception_state) {
-  return NativeValueImpl<RecipeTrait<DOMSharedArrayBuffer>,
-                         ToDOMSharedArrayBuffer, Nullablity::kIsNotNullable,
-                         BufferSizeCheck::kCheck, DOMSharedArrayBuffer>(
+  return NativeValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, DOMSharedArrayBuffer>(
       isolate, value, exception_state);
 }
 
@@ -485,9 +553,10 @@
     int argument_index,
     v8::Local<v8::Value> value,
     ExceptionState& exception_state) {
-  return ArgumentValueImpl<RecipeTrait<DOMSharedArrayBuffer>,
-                           ToDOMSharedArrayBuffer, Nullablity::kIsNotNullable,
-                           BufferSizeCheck::kCheck, DOMSharedArrayBuffer>(
+  return ArgumentValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, DOMSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
 
@@ -498,9 +567,10 @@
     v8::Isolate* isolate,
     v8::Local<v8::Value> value,
     ExceptionState& exception_state) {
-  return NativeValueImpl<RecipeTrait<DOMSharedArrayBuffer>,
-                         ToDOMSharedArrayBuffer, Nullablity::kIsNullable,
-                         BufferSizeCheck::kCheck, DOMSharedArrayBuffer>(
+  return NativeValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, DOMSharedArrayBuffer>(
       isolate, value, exception_state);
 }
 
@@ -510,9 +580,37 @@
     int argument_index,
     v8::Local<v8::Value> value,
     ExceptionState& exception_state) {
-  return ArgumentValueImpl<RecipeTrait<DOMSharedArrayBuffer>,
-                           ToDOMSharedArrayBuffer, Nullablity::kIsNullable,
-                           BufferSizeCheck::kCheck, DOMSharedArrayBuffer>(
+  return ArgumentValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, DOMSharedArrayBuffer>(
+      isolate, argument_index, value, exception_state);
+}
+
+// [AllowResizable] SharedArrayBuffer
+
+DOMSharedArrayBuffer*
+NativeValueTraits<IDLAllowResizable<DOMSharedArrayBuffer>>::NativeValue(
+    v8::Isolate* isolate,
+    v8::Local<v8::Value> value,
+    ExceptionState& exception_state) {
+  return NativeValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kAllowResizable, DOMSharedArrayBuffer>(
+      isolate, value, exception_state);
+}
+
+DOMSharedArrayBuffer*
+NativeValueTraits<IDLAllowResizable<DOMSharedArrayBuffer>>::ArgumentValue(
+    v8::Isolate* isolate,
+    int argument_index,
+    v8::Local<v8::Value> value,
+    ExceptionState& exception_state) {
+  return ArgumentValueImpl<
+      RecipeTrait<DOMSharedArrayBuffer>, ToDOMSharedArrayBuffer,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kAllowResizable, DOMSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
 
@@ -524,6 +622,7 @@
     ExceptionState& exception_state) {
   return NativeValueImpl<RecipeTrait<DOMArrayBufferBase>, ToDOMArrayBufferBase,
                          Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable,
                          BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, value, exception_state);
 }
@@ -536,6 +635,7 @@
   return ArgumentValueImpl<RecipeTrait<DOMArrayBufferBase>,
                            ToDOMArrayBufferBase, Nullablity::kIsNotNullable,
                            BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
@@ -550,6 +650,7 @@
   return ArgumentValueImpl<RecipeTrait<DOMArrayBufferBase>,
                            ToDOMArrayBufferBase, Nullablity::kIsNotNullable,
                            BufferSizeCheck::kDoNotCheck,
+                           ResizableAllowance::kDisallowResizable,
                            BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
@@ -563,6 +664,7 @@
     ExceptionState& exception_state) {
   return NativeValueImpl<RecipeTrait<DOMArrayBufferBase>, ToDOMArrayBufferBase,
                          Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable,
                          BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, value, exception_state);
 }
@@ -576,6 +678,7 @@
   return ArgumentValueImpl<RecipeTrait<DOMArrayBufferBase>,
                            ToDOMArrayBufferBase, Nullablity::kIsNullable,
                            BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
@@ -591,6 +694,7 @@
   return ArgumentValueImpl<RecipeTrait<DOMArrayBufferBase>,
                            ToDOMArrayBufferBase, Nullablity::kIsNullable,
                            BufferSizeCheck::kDoNotCheck,
+                           ResizableAllowance::kDisallowResizable,
                            BufferSourceTypeNameAllowSharedArrayBuffer>(
       isolate, argument_index, value, exception_state);
 }
@@ -604,11 +708,11 @@
     NativeValue(v8::Isolate* isolate,
                 v8::Local<v8::Value> value,
                 ExceptionState& exception_state) {
-  return NativeValueImpl<RecipeTrait<NotShared<T>>,
-                         ToDOMViewType<T, kNotShared>,
-                         Nullablity::kIsNotNullable, BufferSizeCheck::kCheck, T,
-                         ABVTrait<T>::IsShared>(isolate, value,
-                                                exception_state);
+  return NativeValueImpl<
+      RecipeTrait<NotShared<T>>, ToDOMViewType<T, kNotShared>,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, T, ABVTrait<T>::IsShared>(
+      isolate, value, exception_state);
 }
 
 template <typename T>
@@ -619,11 +723,11 @@
                   int argument_index,
                   v8::Local<v8::Value> value,
                   ExceptionState& exception_state) {
-  return ArgumentValueImpl<RecipeTrait<NotShared<T>>,
-                           ToDOMViewType<T, kNotShared>,
-                           Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
-                           T, ABVTrait<T>::IsShared>(isolate, argument_index,
-                                                     value, exception_state);
+  return ArgumentValueImpl<
+      RecipeTrait<NotShared<T>>, ToDOMViewType<T, kNotShared>,
+      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, T, ABVTrait<T>::IsShared>(
+      isolate, argument_index, value, exception_state);
 }
 
 // [AllowShared] ArrayBufferView
@@ -635,10 +739,11 @@
     NativeValue(v8::Isolate* isolate,
                 v8::Local<v8::Value> value,
                 ExceptionState& exception_state) {
-  return NativeValueImpl<
-      RecipeTrait<MaybeShared<T>>, ToDOMViewType<T, kMaybeShared>,
-      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck, T>(isolate, value,
-                                                              exception_state);
+  return NativeValueImpl<RecipeTrait<MaybeShared<T>>,
+                         ToDOMViewType<T, kMaybeShared>,
+                         Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable, T>(
+      isolate, value, exception_state);
 }
 
 template <typename T>
@@ -649,9 +754,10 @@
                   int argument_index,
                   v8::Local<v8::Value> value,
                   ExceptionState& exception_state) {
-  return ArgumentValueImpl<
-      RecipeTrait<MaybeShared<T>>, ToDOMViewType<T, kMaybeShared>,
-      Nullablity::kIsNotNullable, BufferSizeCheck::kCheck, T>(
+  return ArgumentValueImpl<RecipeTrait<MaybeShared<T>>,
+                           ToDOMViewType<T, kMaybeShared>,
+                           Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable, T>(
       isolate, argument_index, value, exception_state);
 }
 
@@ -666,8 +772,9 @@
                 ExceptionState& exception_state) {
   return NativeValueImpl<
       RecipeTrait<MaybeShared<T>>, ToDOMViewType<T, kMaybeShared>,
-      Nullablity::kIsNotNullable, BufferSizeCheck::kDoNotCheck, T>(
-      isolate, value, exception_state);
+      Nullablity::kIsNotNullable, BufferSizeCheck::kDoNotCheck,
+      ResizableAllowance::kDisallowResizable, T>(isolate, value,
+                                                 exception_state);
 }
 
 template <typename T>
@@ -680,8 +787,9 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<
       RecipeTrait<MaybeShared<T>>, ToDOMViewType<T, kMaybeShared>,
-      Nullablity::kIsNotNullable, BufferSizeCheck::kDoNotCheck, T>(
-      isolate, argument_index, value, exception_state);
+      Nullablity::kIsNotNullable, BufferSizeCheck::kDoNotCheck,
+      ResizableAllowance::kDisallowResizable, T>(isolate, argument_index, value,
+                                                 exception_state);
 }
 
 // Nullable ArrayBufferView
@@ -693,9 +801,10 @@
     NativeValue(v8::Isolate* isolate,
                 v8::Local<v8::Value> value,
                 ExceptionState& exception_state) {
-  return NativeValueImpl<RecipeTrait<NotShared<T>>,
-                         ToDOMViewType<T, kNotShared>, Nullablity::kIsNullable,
-                         BufferSizeCheck::kCheck, T, ABVTrait<T>::IsShared>(
+  return NativeValueImpl<
+      RecipeTrait<NotShared<T>>, ToDOMViewType<T, kNotShared>,
+      Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, T, ABVTrait<T>::IsShared>(
       isolate, value, exception_state);
 }
 
@@ -707,11 +816,11 @@
                   int argument_index,
                   v8::Local<v8::Value> value,
                   ExceptionState& exception_state) {
-  return ArgumentValueImpl<RecipeTrait<NotShared<T>>,
-                           ToDOMViewType<T, kNotShared>,
-                           Nullablity::kIsNullable, BufferSizeCheck::kCheck, T,
-                           ABVTrait<T>::IsShared>(isolate, argument_index,
-                                                  value, exception_state);
+  return ArgumentValueImpl<
+      RecipeTrait<NotShared<T>>, ToDOMViewType<T, kNotShared>,
+      Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+      ResizableAllowance::kDisallowResizable, T, ABVTrait<T>::IsShared>(
+      isolate, argument_index, value, exception_state);
 }
 
 // Nullable [AllowShared] ArrayBufferView
@@ -725,7 +834,8 @@
                 ExceptionState& exception_state) {
   return NativeValueImpl<RecipeTrait<MaybeShared<T>>,
                          ToDOMViewType<T, kMaybeShared>,
-                         Nullablity::kIsNullable, BufferSizeCheck::kCheck, T>(
+                         Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                         ResizableAllowance::kDisallowResizable, T>(
       isolate, value, exception_state);
 }
 
@@ -739,7 +849,8 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<RecipeTrait<MaybeShared<T>>,
                            ToDOMViewType<T, kMaybeShared>,
-                           Nullablity::kIsNullable, BufferSizeCheck::kCheck, T>(
+                           Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable, T>(
       isolate, argument_index, value, exception_state);
 }
 
@@ -755,8 +866,9 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<
       RecipeTrait<MaybeShared<T>>, ToDOMViewType<T, kMaybeShared>,
-      Nullablity::kIsNullable, BufferSizeCheck::kDoNotCheck, T>(
-      isolate, argument_index, value, exception_state);
+      Nullablity::kIsNullable, BufferSizeCheck::kDoNotCheck,
+      ResizableAllowance::kDisallowResizable, T>(isolate, argument_index, value,
+                                                 exception_state);
 }
 
 // [AllowShared, FlexibleArrayBufferView] ArrayBufferView
@@ -771,6 +883,7 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<RecipeTrait<T>, ToFlexibleArrayBufferView,
                            Nullablity::kIsNotNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            typename ABVTrait<T>::DOMViewType>(
       isolate, argument_index, value, exception_state);
 }
@@ -788,8 +901,9 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<
       RecipeTrait<T>, ToFlexibleArrayBufferView, Nullablity::kIsNotNullable,
-      BufferSizeCheck::kDoNotCheck, typename ABVTrait<T>::DOMViewType>(
-      isolate, argument_index, value, exception_state);
+      BufferSizeCheck::kDoNotCheck, ResizableAllowance::kDisallowResizable,
+      typename ABVTrait<T>::DOMViewType>(isolate, argument_index, value,
+                                         exception_state);
 }
 
 // Nullable [AllowShared, FlexibleArrayBufferView] ArrayBufferView
@@ -804,6 +918,7 @@
                   ExceptionState& exception_state) {
   return ArgumentValueImpl<RecipeTrait<T>, ToFlexibleArrayBufferView,
                            Nullablity::kIsNullable, BufferSizeCheck::kCheck,
+                           ResizableAllowance::kDisallowResizable,
                            typename ABVTrait<T>::DOMViewType>(
       isolate, argument_index, value, exception_state);
 }
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
index b054098..849062a 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
@@ -577,6 +577,19 @@
 };
 
 template <>
+struct CORE_EXPORT NativeValueTraits<IDLAllowResizable<DOMArrayBuffer>>
+    : public NativeValueTraitsBase<DOMArrayBuffer*> {
+  static DOMArrayBuffer* NativeValue(v8::Isolate* isolate,
+                                     v8::Local<v8::Value> value,
+                                     ExceptionState& exception_state);
+
+  static DOMArrayBuffer* ArgumentValue(v8::Isolate* isolate,
+                                       int argument_index,
+                                       v8::Local<v8::Value> value,
+                                       ExceptionState& exception_state);
+};
+
+template <>
 struct CORE_EXPORT NativeValueTraits<DOMSharedArrayBuffer>
     : public NativeValueTraitsBase<DOMSharedArrayBuffer*> {
   static DOMSharedArrayBuffer* NativeValue(v8::Isolate* isolate,
@@ -602,6 +615,19 @@
                                              ExceptionState& exception_state);
 };
 
+template <>
+struct CORE_EXPORT NativeValueTraits<IDLAllowResizable<DOMSharedArrayBuffer>>
+    : public NativeValueTraitsBase<DOMSharedArrayBuffer*> {
+  static DOMSharedArrayBuffer* NativeValue(v8::Isolate* isolate,
+                                           v8::Local<v8::Value> value,
+                                           ExceptionState& exception_state);
+
+  static DOMSharedArrayBuffer* ArgumentValue(v8::Isolate* isolate,
+                                             int argument_index,
+                                             v8::Local<v8::Value> value,
+                                             ExceptionState& exception_state);
+};
+
 // DOMArrayBufferBase is the common base class of DOMArrayBuffer and
 // DOMSharedArrayBuffer, so it behaves as "[AllowShared] ArrayBuffer" in
 // Web IDL.
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
index 480d532..62de65f 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
@@ -121,8 +121,8 @@
   }
   if (object->IsArrayBuffer()) {
     DOMArrayBuffer* array_buffer =
-        NativeValueTraits<DOMArrayBuffer>::NativeValue(isolate, object,
-                                                       exception_state);
+        NativeValueTraits<IDLAllowResizable<DOMArrayBuffer>>::NativeValue(
+            isolate, object, exception_state);
     if (exception_state.HadException())
       return false;
     if (transferables.array_buffers.Contains(array_buffer)) {
@@ -137,8 +137,8 @@
   }
   if (object->IsSharedArrayBuffer()) {
     DOMSharedArrayBuffer* shared_array_buffer =
-        NativeValueTraits<DOMSharedArrayBuffer>::NativeValue(isolate, object,
-                                                             exception_state);
+        NativeValueTraits<IDLAllowResizable<DOMSharedArrayBuffer>>::NativeValue(
+            isolate, object, exception_state);
     if (exception_state.HadException())
       return false;
     if (transferables.array_buffers.Contains(shared_array_buffer)) {
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
index 59d6e4ba..3d91f59 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
@@ -163,7 +163,6 @@
     ScriptPromiseResolver* resolver)
     : fail_encoder_initialization_for_test_(false),
       enforce_idle_encoding_for_test_(false),
-      image_(image),
       context_(context),
       encode_options_(options),
       function_type_(function_type),
@@ -172,7 +171,6 @@
       input_digest_(input_digest),
       callback_(callback),
       script_promise_resolver_(resolver) {
-  DCHECK(image);
   DCHECK(context);
 
   mime_type_ = ImageEncoderUtils::ToEncodingMimeType(
@@ -181,9 +179,9 @@
 
   // We use pixmap to access the image pixels. Make the image unaccelerated if
   // necessary.
-  image_ = image_->MakeUnaccelerated();
+  DCHECK(image);
+  image_ = image->MakeUnaccelerated();
 
-  DCHECK(image_);
   sk_sp<SkImage> skia_image =
       image_->PaintImageForCurrentFrame().GetSwSkImage();
   DCHECK(skia_image);
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 115d5d09..c5ca9c8 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -1153,11 +1153,10 @@
   CanvasAsyncBlobCreator* async_creator = nullptr;
   scoped_refptr<StaticBitmapImage> image_bitmap = Snapshot(kBackBuffer);
   if (image_bitmap) {
-    auto image_unaccelerated = image_bitmap->MakeUnaccelerated();
     auto* options = ImageEncodeOptions::Create();
     options->setType(ImageEncodingMimeTypeName(encoding_mime_type));
     async_creator = MakeGarbageCollected<CanvasAsyncBlobCreator>(
-        std::move(image_unaccelerated), options,
+        image_bitmap, options,
         CanvasAsyncBlobCreator::kHTMLCanvasToBlobCallback, callback, start_time,
         GetExecutionContext(),
         IdentifiabilityStudySettings::Get()->ShouldSampleType(
diff --git a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
index 36f25d2..5937e50 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
@@ -208,8 +208,10 @@
     // 2.3. Let result[i].x = CSS_positions[i].x + shift.x and
     // result[i].y = CSS_positions[i].y + shift.y.
     const float scaling_factor = ScalingFactorAt(items, i);
-    result_[i].x = css_positions_[i].x() + shift.x() * scaling_factor;
-    result_[i].y = css_positions_[i].y() + shift.y() * scaling_factor;
+    result_[i].x =
+        ClampTo<float>(css_positions_[i].x() + shift.x() * scaling_factor);
+    result_[i].y =
+        ClampTo<float>(css_positions_[i].y() + shift.y() * scaling_factor);
   }
 }
 
@@ -241,9 +243,9 @@
   const unsigned i = range.start_index;
   const unsigned j_plus_1 = range.end_index + 1;
   auto* element = To<SVGTextContentElement>(range.layout_object->GetNode());
-  const float text_length =
+  const float text_length = ClampTo<float>(
       element->textLength()->CurrentValue()->Value(SVGLengthContext(element)) *
-      ScalingFactorAt(items, i);
+      ScalingFactorAt(items, i));
   const SVGLengthAdjustType length_adjust =
       element->lengthAdjust()->CurrentEnumValue();
 
@@ -413,6 +415,7 @@
       // Take into account of baseline-shift.
       if (!horizontal_)
         shift.set_x(shift.x() + css_positions_[i].x());
+      shift.set_x(ClampTo<float>(shift.x()));
     }
     // 3.2. If resolved_y[index] is set, then let
     // shift.y = resolved_y[index] − result.y[index].
@@ -423,6 +426,7 @@
       // Take into account of baseline-shift.
       if (horizontal_)
         shift.set_y(shift.y() + css_positions_[i].y());
+      shift.set_y(ClampTo<float>(shift.y()));
     }
 
     // If this character is the first one in a <textPath>, reset the
@@ -665,6 +669,8 @@
               info.x = point_tangent.point.x() * scaling_factor;
               info.y = point_tangent.point.y() * scaling_factor;
             }
+            info.x = ClampTo<float>(*info.x);
+            info.y = ClampTo<float>(*info.y);
           }
         }
       } else {
@@ -698,8 +704,10 @@
           PointAndTangent point_tangent;
           path_mapper->PointAndNormalAtLength(path_mapper->length(),
                                               point_tangent);
-          path_end_x = point_tangent.point.x() * scaling_factor - *info.x;
-          path_end_y = point_tangent.point.y() * scaling_factor - *info.y;
+          path_end_x = ClampTo<float>(point_tangent.point.x() * scaling_factor -
+                                      *info.x);
+          path_end_y = ClampTo<float>(point_tangent.point.y() * scaling_factor -
+                                      *info.y);
         } else {
           // The 'current text position' should be at the next to the last
           // drawn character.
diff --git a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm_test.cc
index 2ed2aa0af..6a8396b 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm_test.cc
@@ -50,4 +50,18 @@
   // Pass if no crashes.
 }
 
+TEST_F(NGSVGTextLayoutAlgorithmTest, HugeScaleCrash) {
+  SetBodyInnerHTML(R"HTML(
+  <svg xmlns="http://www.w3.org/2000/svg" width="450" height="450">
+  <style>
+  #test-body-content {
+    scale: 16420065941240262705269076410170673060945878020586681613052798923953430637521913631296811416;
+  }
+  </style>
+  <text id="test-body-content" x="-10" y="14">A</text>
+  </svg>)HTML");
+  UpdateAllLifecyclePhasesForTest();
+  // Pass if no crashes.
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message.cc b/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
index a301728..04c8c61 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
@@ -62,7 +62,11 @@
 
     for (auto& item : message.array_buffer_contents_array) {
       mojo_base::BigBuffer& big_buffer = item->contents;
-      ArrayBufferContents contents(big_buffer.size(), 1,
+      absl::optional<size_t> max_byte_length;
+      if (item->is_resizable_by_user_javascript) {
+        max_byte_length = base::checked_cast<size_t>(item->max_byte_length);
+      }
+      ArrayBufferContents contents(big_buffer.size(), max_byte_length, 1,
                                    ArrayBufferContents::kNotShared,
                                    ArrayBufferContents::kDontInitialize);
       // Check if we allocated the backing store of the ArrayBufferContents
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.cc b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.cc
index 262e827..fd3750bb 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.cc
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.cc
@@ -113,8 +113,13 @@
     return false;
   auto contents_data = contents_view.data();
 
+  absl::optional<size_t> max_data_size;
+  if (data.is_resizable_by_user_javascript()) {
+    max_data_size = base::checked_cast<size_t>(data.max_byte_length());
+  }
   blink::ArrayBufferContents array_buffer_contents(
-      contents_data.size(), 1, blink::ArrayBufferContents::kNotShared,
+      contents_data.size(), max_data_size, 1,
+      blink::ArrayBufferContents::kNotShared,
       blink::ArrayBufferContents::kDontInitialize);
   if (contents_data.size() != array_buffer_contents.DataLength()) {
     return false;
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.h b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.h
index 643ad5e..2d43c2fb 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.h
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits.h
@@ -90,6 +90,15 @@
     return mojo_base::BigBuffer(
         base::make_span(allocation_start, array_buffer_contents.DataLength()));
   }
+  static bool is_resizable_by_user_javascript(
+      const blink::ArrayBufferContents& array_buffer_contents) {
+    return array_buffer_contents.IsResizableByUserJavaScript();
+  }
+  static size_t max_byte_length(
+      const blink::ArrayBufferContents& array_buffer_contents) {
+    return array_buffer_contents.MaxDataLength();
+  }
+
   static bool Read(blink::mojom::SerializedArrayBufferContentsDataView,
                    blink::ArrayBufferContents* out);
 };
diff --git a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
index ae25b305..b3e7c1c4 100644
--- a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
@@ -75,6 +75,8 @@
   std::unique_ptr<Canvas2DLayerBridge> bridge = MakeCanvas2DLayerBridge(size);
   element->SetResourceProviderForTesting(nullptr, std::move(bridge), size);
   ASSERT_EQ(context, element->RenderingContext());
+  ASSERT_TRUE(context->IsComposited());
+  ASSERT_TRUE(element->IsAccelerated());
 
   // Force the page to paint.
   element->PreFinalizeFrame();
@@ -82,9 +84,6 @@
   element->PostFinalizeFrame();
   UpdateAllLifecyclePhasesForTest();
 
-  ASSERT_TRUE(context->IsComposited());
-  ASSERT_TRUE(element->IsAccelerated());
-
   // Fetch the layer associated with the <canvas>, and check that it was
   // correctly configured in the layer tree.
   const cc::Layer* layer = context->CcLayer();
diff --git a/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.cc b/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.cc
index 72257f8..202fcb5 100644
--- a/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.cc
+++ b/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.cc
@@ -68,6 +68,7 @@
 
 ArrayBufferContents::ArrayBufferContents(
     size_t num_elements,
+    absl::optional<size_t> max_num_elements,
     size_t element_byte_size,
     SharingType is_shared,
     ArrayBufferContents::InitializationPolicy policy) {
@@ -79,17 +80,36 @@
     return;
   }
   size_t length = checked_length.ValueOrDie();
-  void* data = AllocateMemoryOrNull(length, policy);
-  if (!data) {
-    return;
-  }
-  auto deleter = [](void* data, size_t, void*) { FreeMemory(data); };
-  if (is_shared == kNotShared) {
-    backing_store_ =
-        v8::ArrayBuffer::NewBackingStore(data, length, deleter, nullptr);
+
+  if (!max_num_elements) {
+    // Create a fixed-length ArrayBuffer.
+    void* data = AllocateMemoryOrNull(length, policy);
+    if (!data) {
+      return;
+    }
+    auto deleter = [](void* data, size_t, void*) { FreeMemory(data); };
+    if (is_shared == kNotShared) {
+      backing_store_ =
+          v8::ArrayBuffer::NewBackingStore(data, length, deleter, nullptr);
+    } else {
+      backing_store_ = v8::SharedArrayBuffer::NewBackingStore(data, length,
+                                                              deleter, nullptr);
+    }
   } else {
+    // The resizable form of the constructor is currently only used for IPC
+    // transfers of ArrayBuffers, and SharedArrayBuffers cannot be transferred
+    // across agent clusters.
+    DCHECK_EQ(kNotShared, is_shared);
+    // Currently V8 does not support embedder-allocated resizable backing
+    // stores. It does not zero resizable allocations, which use a
+    // reserve-and-partially-commit pattern. Check that the caller is not
+    // expecting zeroed memory.
+    CHECK_EQ(kDontInitialize, policy);
+    auto max_checked_length =
+        base::CheckedNumeric<size_t>(*max_num_elements) * element_byte_size;
+    size_t max_length = max_checked_length.ValueOrDie();
     backing_store_ =
-        v8::SharedArrayBuffer::NewBackingStore(data, length, deleter, nullptr);
+        v8::ArrayBuffer::NewResizableBackingStore(length, max_length);
   }
 }
 
diff --git a/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h b/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h
index 6728537..eed68f28 100644
--- a/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h
+++ b/third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h
@@ -61,6 +61,19 @@
   ArrayBufferContents(size_t num_elements,
                       size_t element_byte_size,
                       SharingType is_shared,
+                      InitializationPolicy policy)
+      : ArrayBufferContents(num_elements,
+                            absl::nullopt,
+                            element_byte_size,
+                            is_shared,
+                            policy) {}
+  // If max_num_elements has a value, a backing store for a resizable
+  // ArrayBuffer is created. Otherwise a backing store for a fixed-length
+  // ArrayBuffer is created.
+  ArrayBufferContents(size_t num_elements,
+                      absl::optional<size_t> max_num_elements,
+                      size_t element_byte_size,
+                      SharingType is_shared,
                       InitializationPolicy);
 
   ArrayBufferContents(
@@ -99,9 +112,16 @@
   size_t DataLength() const {
     return backing_store_ ? backing_store_->ByteLength() : 0;
   }
+  size_t MaxDataLength() const {
+    return backing_store_ ? backing_store_->MaxByteLength() : 0;
+  }
   bool IsShared() const {
     return backing_store_ ? backing_store_->IsShared() : false;
   }
+  bool IsResizableByUserJavaScript() const {
+    return backing_store_ ? backing_store_->IsResizableByUserJavaScript()
+                          : false;
+  }
   bool IsValid() const { return backing_store_ && backing_store_->Data(); }
 
   std::shared_ptr<v8::BackingStore> BackingStore() const {
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h
index e31773e..9551143 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h
+++ b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer_base.h
@@ -33,6 +33,10 @@
 
   bool IsShared() const { return contents_.IsShared(); }
 
+  bool IsResizableByUserJavaScript() const {
+    return contents_.IsResizableByUserJavaScript();
+  }
+
   // ScriptWrappable overrides:
   v8::MaybeLocal<v8::Value> Wrap(ScriptState*) override {
     NOTREACHED();
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 1261f1d..f9f4621 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -189,10 +189,7 @@
 }
 
 bool CanvasRenderingContext2D::IsComposited() const {
-  if (Canvas2DLayerBridge* layer_bridge = canvas()->GetCanvas2DLayerBridge()) {
-    return layer_bridge->IsComposited();
-  }
-  return false;
+  return IsAccelerated();
 }
 
 void CanvasRenderingContext2D::Stop() {
diff --git a/third_party/blink/renderer/platform/bindings/exception_messages.cc b/third_party/blink/renderer/platform/bindings/exception_messages.cc
index 444f7b6..3e3403a9 100644
--- a/third_party/blink/renderer/platform/bindings/exception_messages.cc
+++ b/third_party/blink/renderer/platform/bindings/exception_messages.cc
@@ -271,6 +271,12 @@
                         expected_type);
 }
 
+String ExceptionMessages::ResizableArrayBufferNotAllowed(
+    const char* expected_type) {
+  return String::Format("The provided %s value must not be resizable.",
+                        expected_type);
+}
+
 String ExceptionMessages::ValueNotOfType(const char* expected_type) {
   return String::Format("The provided value is not of type '%s'.",
                         expected_type);
diff --git a/third_party/blink/renderer/platform/bindings/exception_messages.h b/third_party/blink/renderer/platform/bindings/exception_messages.h
index 83d7dcd8..7e092f3c 100644
--- a/third_party/blink/renderer/platform/bindings/exception_messages.h
+++ b/third_party/blink/renderer/platform/bindings/exception_messages.h
@@ -129,6 +129,8 @@
 
   static String SharedArrayBufferNotAllowed(const char* expected_type);
 
+  static String ResizableArrayBufferNotAllowed(const char* expected_type);
+
   static String ValueNotOfType(const char* expected_type);
 
   static String InputArrayTooLong(unsigned expected_size, unsigned actual_size);
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
index 2e6b5d1..7d6f4379 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
@@ -151,25 +151,6 @@
   return ShouldAccelerate();
 }
 
-bool Canvas2DLayerBridge::IsComposited() const {
-  if (IsHibernating()) {
-    return false;
-  }
-
-  if (UNLIKELY(!resource_host_)) {
-    return false;
-  }
-
-  CanvasResourceProvider* resource_provider =
-      resource_host_->ResourceProvider();
-  if (UNLIKELY(!resource_provider)) {
-    return false;
-  }
-
-  return resource_provider->SupportsDirectCompositing() &&
-         !resource_host_->LowLatencyEnabled();
-}
-
 static void HibernateWrapper(base::WeakPtr<Canvas2DLayerBridge> bridge,
                              base::TimeTicks /*idleDeadline*/) {
   if (bridge) {
@@ -286,13 +267,14 @@
 
   if (resource_provider && resource_provider->IsValid()) {
 #if DCHECK_IS_ON()
-    // If resource provider is composited, a layer should already exist.
+    // If resource provider is accelerated, a layer should already exist.
     // unless this is a canvas in low latency mode.
     // If this DCHECK fails, it probably means that
     // CanvasRenderingContextHost::GetOrCreateCanvasResourceProvider() was
     // called on a 2D context before this function.
-    if (IsComposited()) {
-      DCHECK(!!layer_);
+    if (IsAccelerated()) {
+      DCHECK(!!layer_ ||
+             (resource_host_ && resource_host_->LowLatencyEnabled()));
     }
 #endif
     return resource_provider;
@@ -327,7 +309,7 @@
   // TODO crbug/1090081: Check possibility to move DidDraw inside Clear.
   DidDraw();
 
-  if (IsComposited() && !layer_) {
+  if (IsAccelerated() && !layer_) {
     layer_ = cc::TextureLayer::CreateForMailbox(this);
     layer_->SetIsDrawable(true);
     layer_->SetHitTestable(true);
@@ -337,7 +319,6 @@
                                cc::PaintFlags::FilterQuality::kNone);
     layer_->SetHDRConfiguration(resource_host_->GetHDRMode(),
                                 resource_host_->GetHDRMetadata());
-    layer_->SetFlipped(!resource_provider->IsOriginTopLeft());
   }
   // After the page becomes visible and successfully restored the canvas
   // resource provider, set |lose_context_in_background_| to false.
@@ -771,7 +752,7 @@
     constexpr unsigned kMaxCanvasAnimationBacklog = 2;
     if (frames_since_last_commit_ >=
         static_cast<int>(kMaxCanvasAnimationBacklog)) {
-      if (IsComposited() && !rate_limiter_) {
+      if (IsAccelerated() && !rate_limiter_) {
         rate_limiter_ = std::make_unique<SharedContextRateLimiter>(
             kMaxCanvasAnimationBacklog);
       }
@@ -783,7 +764,7 @@
 }
 
 void Canvas2DLayerBridge::DoPaintInvalidation(const gfx::Rect& dirty_rect) {
-  if (layer_ && IsComposited()) {
+  if (layer_ && raster_mode_ == RasterMode::kGPU) {
     layer_->SetNeedsDisplayRect(dirty_rect);
   }
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
index ec1b2136..ed344ceb 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
@@ -96,8 +96,6 @@
   virtual void DidRestoreCanvasMatrixClipStack(cc::PaintCanvas*) {}
   virtual bool IsAccelerated() const;
 
-  bool IsComposited() const;
-
   // This may recreate CanvasResourceProvider
   cc::PaintCanvas* GetPaintCanvas();
   bool IsValid();
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
index a1f5fde..0fcb0f5 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
@@ -1016,29 +1016,4 @@
   EXPECT_FALSE(bridge->HasRateLimiterForTesting());
 }
 
-TEST_F(Canvas2DLayerBridgeTest, SoftwareCanvasIsCompositedIfImageChromium) {
-  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
-  ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(true);
-  const_cast<gpu::Capabilities&>(SharedGpuContext::ContextProviderWrapper()
-                                     ->ContextProvider()
-                                     ->GetCapabilities())
-      .gpu_memory_buffer_formats.Put(gfx::BufferFormat::BGRA_8888);
-  std::unique_ptr<Canvas2DLayerBridge> bridge =
-      MakeBridge(gfx::Size(300, 150), RasterMode::kCPU, kNonOpaque);
-  EXPECT_TRUE(bridge->IsValid());
-  DrawSomething(bridge.get());
-  EXPECT_FALSE(bridge->IsAccelerated());
-  EXPECT_TRUE(bridge->IsComposited());
-}
-
-TEST_F(Canvas2DLayerBridgeTest, SoftwareCanvasNotCompositedIfNotImageChromium) {
-  ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(false);
-  std::unique_ptr<Canvas2DLayerBridge> bridge =
-      MakeBridge(gfx::Size(300, 150), RasterMode::kCPU, kNonOpaque);
-  EXPECT_TRUE(bridge->IsValid());
-  DrawSomething(bridge.get());
-  EXPECT_FALSE(bridge->IsAccelerated());
-  EXPECT_FALSE(bridge->IsComposited());
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index f0b976c3..d2f2e89 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -705,10 +705,7 @@
   auto surface = SkSurface::MakeRasterDirect(CreateSkImageInfo(),
                                              gpu_memory_buffer_->memory(0),
                                              gpu_memory_buffer_->stride(0));
-
-  SkPixmap pixmap;
-  image->peekPixels(&pixmap);
-  surface->writePixels(pixmap, 0, 0);
+  surface->getCanvas()->drawImage(image, 0, 0);
   auto* sii =
       ContextProviderWrapper()->ContextProvider()->SharedImageInterface();
   gpu_memory_buffer_->Unmap();
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7d4502a6..36e06c76 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -910,6 +910,7 @@
 crbug.com/1044742 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-border-image.html [ Failure ]
 crbug.com/1044742 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-filter.html [ Failure ]
 crbug.com/1044742 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-stacking-context-001.html [ Failure ]
+crbug.com/1044742 external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-video.html [ Failure ]
 
 crbug.com/1271275 external/wpt/css/compositing/background-blending/background-blend-mode-plus-lighter.html [ Failure ]
 
@@ -2991,8 +2992,6 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 [ Mac12 ] external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-video.html [ Failure ]
-crbug.com/626703 [ Win ] external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-video.html [ Failure ]
 crbug.com/626703 [ Win10.20h2 ] external/wpt/web-animations/idlharness.window.html [ Crash Failure ]
 crbug.com/626703 external/wpt/html/rendering/widgets/the-select-element/option-add-label-quirks.html [ Failure ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/html/semantics/popovers/light-dismiss-event-ordering.tentative.html [ Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 9463d08..fe15e91e 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1680,5 +1680,17 @@
     ],
     "args": ["--enable-features=SplitUserMediaQueues"],
     "expires": "Jul 1, 2023"
+  },
+  {
+    "prefix": "js-resizable-arraybuffer",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": [
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html"
+    ],
+    "exclusive_tests": [
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html"
+    ],
+    "args": ["--js-flags=--harmony-rab-gsab"],
+    "expires": "Jul 1, 2023"
   }
 ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
index 0f8195157..4fb7f113 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
@@ -12,7 +12,6 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-contain-png-001-ref.html">
-    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-2000">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
index 14834316..738c015a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
@@ -12,7 +12,6 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-contain-png-002-ref.html">
-    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-2000">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
index 0e2a388..36031175 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
@@ -12,7 +12,6 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-fill-png-001-ref.html">
-    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-3200">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
index 43bcced..a332c37 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
@@ -12,7 +12,6 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-fill-png-002-ref.html">
-    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-3200">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html
new file mode 100644
index 0000000..2b21a145
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>postMessage transfer ArrayBuffer cross origin iframe</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='/common/get-host-info.sub.js'></script>
+
+<script>
+
+async_test(t => {
+  const oopif = document.createElement('iframe');
+
+  window.addEventListener('message', t.step_func((e) => {
+    if (e.data === 'started') {
+      const rab = new ArrayBuffer(32, { maxByteLength: 1024 });
+      oopif.contentWindow.postMessage(rab, '*', [rab]);
+    } else {
+      assert_equals(e.data, 'byteLength=32,maxByteLength=1024,resizable=true');
+      t.done();
+    }
+  }));
+
+  window.addEventListener('load', () => {
+    oopif.src = `${get_host_info().HTTP_REMOTE_ORIGIN}/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html`;
+    document.body.appendChild(oopif);
+  });
+}, 'postMessaging resizable ArrayBuffer to OOPIF');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
new file mode 100644
index 0000000..378c953
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+
+window.addEventListener('message', (e) => {
+  const buffer = e.data;
+  e.source.postMessage(`byteLength=${buffer.byteLength},maxByteLength=${buffer.maxByteLength},resizable=${buffer.resizable}`, '*');
+});
+
+window.parent.postMessage('started', '*');
+
+</script>
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
index e2c967a..5f61902 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..5d03ab2a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
new file mode 100644
index 0000000..9597811
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
new file mode 100644
index 0000000..2d91917
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
new file mode 100644
index 0000000..ef045b8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
new file mode 100644
index 0000000..9597811
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
new file mode 100644
index 0000000..68e5798
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
new file mode 100644
index 0000000..c8b2b51
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
new file mode 100644
index 0000000..7a299d1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
new file mode 100644
index 0000000..9176a3a1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
new file mode 100644
index 0000000..220dc263
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
new file mode 100644
index 0000000..4090cebd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
new file mode 100644
index 0000000..66da85c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
new file mode 100644
index 0000000..cb7b5b4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
new file mode 100644
index 0000000..c8acdf4d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
new file mode 100644
index 0000000..86aa0df
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
new file mode 100644
index 0000000..6892e25b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
new file mode 100644
index 0000000..450d929c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
new file mode 100644
index 0000000..ea6faa4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
new file mode 100644
index 0000000..3d6f2cb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
new file mode 100644
index 0000000..6027fc7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
new file mode 100644
index 0000000..36fe0c2
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
new file mode 100644
index 0000000..92335fbd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
new file mode 100644
index 0000000..54909a8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png
similarity index 61%
rename from third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png
rename to third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png
index 6530304..e4d0a9d 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
new file mode 100644
index 0000000..2bfe7f7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..3d15b57
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..8d0f8d8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..c231417
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
deleted file mode 100644
index 5f61902..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..7fc316c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..7fc316c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..1344ecc
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
index 87ec10d..4cb0a2d 100644
--- a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
index ae0a6ac..8204b2c2 100644
--- a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
index e2c967a..5f61902 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..5d03ab2a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
new file mode 100644
index 0000000..9597811
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
new file mode 100644
index 0000000..2d91917
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
new file mode 100644
index 0000000..ef045b8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
new file mode 100644
index 0000000..9597811
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
new file mode 100644
index 0000000..68e5798
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
new file mode 100644
index 0000000..c8b2b51
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
new file mode 100644
index 0000000..7a299d1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
new file mode 100644
index 0000000..9176a3a1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
new file mode 100644
index 0000000..220dc263
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
new file mode 100644
index 0000000..4090cebd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
new file mode 100644
index 0000000..66da85c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
new file mode 100644
index 0000000..cb7b5b4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
new file mode 100644
index 0000000..c8acdf4d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
new file mode 100644
index 0000000..86aa0df
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
new file mode 100644
index 0000000..6892e25b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
new file mode 100644
index 0000000..450d929c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
new file mode 100644
index 0000000..ea6faa4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
new file mode 100644
index 0000000..3d6f2cb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
new file mode 100644
index 0000000..6027fc7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
new file mode 100644
index 0000000..36fe0c2
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
new file mode 100644
index 0000000..92335fbd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
new file mode 100644
index 0000000..54909a8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png
similarity index 61%
copy from third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png
copy to third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png
index 6530304..e4d0a9d 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/color-spin/canvas-primary-squares-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
new file mode 100644
index 0000000..2bfe7f7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..3d15b57
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..8d0f8d8
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..c231417
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
new file mode 100644
index 0000000..77ae8468
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
deleted file mode 100644
index 5f61902..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..7fc316c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..7fc316c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
new file mode 100644
index 0000000..1344ecc
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
index 92e03005..6a9b251 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
index abf0b0d..aef4be0 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
index 761864c..ad7239b 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png
deleted file mode 100644
index 904c452..0000000
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png
deleted file mode 100644
index b75d52bc..0000000
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index 9e2444f..577c247 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
index 5210665..6770f89 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
index 73668a6..9af4d8d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
index 13b9281..0d159ed 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
index 5210665..6770f89 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
index ee3fa6f0..c824cba 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
index c1d9b25b..bf48c62d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
index 5bc891c..6744078 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
index 867acb27..ba54ccd 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
index 4e5b644..16af243d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
index 6e61b30..02ae9d4 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
index 23fe32a..f863fcd 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
index f1e886ee..db826b4 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
index 0368d30d..2f59f7f 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
index c621520..574a796 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
index dd4d416..4de189f 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
index aa13f9c..42f81d5a 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
index aa13f9c..42f81d5a 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
index 4dd743b..3189d77 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
index 0476678..f75fe963 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
index 2a1980a..a2bc636 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
index a5c05a8..8d82275 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
index a095fc9..f65ba0d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
index 63d3411..dec5aeb 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
index 0c84723..9e27acf 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
index 114e9dc..777f415b 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
index ecf3bcc5..f400434 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
index 14efaf2..5bb2acd0 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
index 46cf7c2..900f5a7 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
index 0ad74dc..a434e66 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png b/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
index ef8cc96..50044ee 100644
--- a/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt b/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt
deleted file mode 100644
index f546a917..0000000
--- a/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Verifies that overlay is correctly rendered with emulation scale > 1.
-The test passes if the image URL below is 300x600 image containing a 270x420 brown rectangle, without any green or red.
-data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAJYCAYAAADYJts/AAAAAXNSR0IArs4c6QAAB9JJREFUeJzt20EKhEAMAMGdxf9/OT5BBUVaqu6BnJpcsmZmfgAB/7cXADhLsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjK2yxNrPbAG8Gk3vSy7sIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8jYLk/MPLAGwDEXFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARk7HLgMrk9KayMAAAAASUVORK5CYII=
-
diff --git a/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
index e4d0a9d..5f0004e 100644
--- a/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
+++ b/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt
deleted file mode 100644
index 9051839..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling background of LayoutNGView #document",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF"
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='canvas'",
-      "position": [8, 8],
-      "bounds": [100, 100]
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt
deleted file mode 100644
index dce35520..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling background of LayoutNGView #document",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF"
-    },
-    {
-      "name": "LayoutHTMLCanvas (positioned) CANVAS id='canvas'",
-      "position": [50, 50],
-      "bounds": [500, 500]
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
index 2bfe7f7..4359ac3 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt
deleted file mode 100644
index dbfdd46..0000000
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling background of LayoutNGView #document",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [50, 50, 600, 500]
-      ]
-    },
-    {
-      "name": "LayoutHTMLCanvas (positioned) CANVAS id='canvas'",
-      "position": [50, 50],
-      "bounds": [500, 500],
-      "backgroundColor": "#003300"
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index f5ec0f3..5598dfc 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -1,235 +1,56 @@
 {
   "layers": [
     {
-      "name": "Scrolling background of LayoutNGView #document",
+      "name": "Scrolling background of LayoutView #document",
       "bounds": [785, 928],
       "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF"
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-oversolid color'",
-      "position": [145, 63],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-overimage'",
-      "position": [287, 63],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-overcanvas'",
-      "position": [429, 63],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-overvideo'",
-      "position": [571, 63],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-insolid color'",
-      "position": [145, 113],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-inimage'",
-      "position": [287, 113],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-incanvas'",
-      "position": [429, 113],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-invideo'",
-      "position": [571, 113],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-outsolid color'",
-      "position": [145, 163],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-outimage'",
-      "position": [287, 163],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-outcanvas'",
-      "position": [429, 163],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-outvideo'",
-      "position": [571, 163],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-atopsolid color'",
-      "position": [145, 213],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-atopimage'",
-      "position": [287, 213],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-atopcanvas'",
-      "position": [429, 213],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-atopvideo'",
-      "position": [571, 213],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-oversolid color'",
-      "position": [145, 263],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-overimage'",
-      "position": [287, 263],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-overcanvas'",
-      "position": [429, 263],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-overvideo'",
-      "position": [571, 263],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-insolid color'",
-      "position": [145, 313],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-inimage'",
-      "position": [287, 313],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-incanvas'",
-      "position": [429, 313],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-invideo'",
-      "position": [571, 313],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-outsolid color'",
-      "position": [145, 363],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-outimage'",
-      "position": [287, 363],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-outcanvas'",
-      "position": [429, 363],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-outvideo'",
-      "position": [571, 363],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-atopsolid color'",
-      "position": [145, 413],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-atopimage'",
-      "position": [287, 413],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-atopcanvas'",
-      "position": [429, 413],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='destination-atopvideo'",
-      "position": [571, 413],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='lightersolid color'",
-      "position": [145, 463],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='lighterimage'",
-      "position": [287, 463],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='lightercanvas'",
-      "position": [429, 463],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='lightervideo'",
-      "position": [571, 463],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='copysolid color'",
-      "position": [145, 513],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='copyimage'",
-      "position": [287, 513],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='copycanvas'",
-      "position": [429, 513],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='copyvideo'",
-      "position": [571, 513],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='xorsolid color'",
-      "position": [145, 563],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='xorimage'",
-      "position": [287, 563],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='xorcanvas'",
-      "position": [429, 563],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='xorvideo'",
-      "position": [571, 563],
-      "bounds": [130, 40]
-    },
-    {
-      "name": "LayoutHTMLCanvas CANVAS id='source-canvas'",
-      "position": [16, 751],
-      "bounds": [150, 60]
+      "backgroundColor": "#FFFFFF",
+      "invalidations": [
+        [569, 562, 133, 42],
+        [569, 512, 133, 42],
+        [569, 462, 133, 42],
+        [569, 412, 133, 42],
+        [569, 362, 133, 42],
+        [569, 312, 133, 42],
+        [569, 262, 133, 42],
+        [569, 212, 133, 42],
+        [569, 162, 133, 42],
+        [569, 112, 133, 42],
+        [569, 62, 133, 42],
+        [427, 562, 133, 42],
+        [427, 512, 133, 42],
+        [427, 462, 133, 42],
+        [427, 412, 133, 42],
+        [427, 362, 133, 42],
+        [427, 312, 133, 42],
+        [427, 262, 133, 42],
+        [427, 212, 133, 42],
+        [427, 162, 133, 42],
+        [427, 112, 133, 42],
+        [427, 62, 133, 42],
+        [285, 562, 133, 42],
+        [285, 512, 133, 42],
+        [285, 462, 133, 42],
+        [285, 412, 133, 42],
+        [285, 362, 133, 42],
+        [285, 312, 133, 42],
+        [285, 262, 133, 42],
+        [285, 212, 133, 42],
+        [285, 162, 133, 42],
+        [285, 112, 133, 42],
+        [285, 62, 133, 42],
+        [143, 562, 133, 42],
+        [143, 512, 133, 42],
+        [143, 462, 133, 42],
+        [143, 412, 133, 42],
+        [143, 362, 133, 42],
+        [143, 312, 133, 42],
+        [143, 262, 133, 42],
+        [143, 212, 133, 42],
+        [143, 162, 133, 42],
+        [143, 112, 133, 42],
+        [143, 62, 133, 42]
+      ]
     },
     {
       "name": "LayoutVideo VIDEO id='video'",
diff --git a/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index 9b27031..e53a7e8 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index da2f7b8c..4eb24ef 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index 91c1688..fb5bce8 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
index e0d5c881..932e2c9 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
index 3126f45..8615a13 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
index 9bad6262..10c466a 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png
deleted file mode 100644
index f55c833..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png
deleted file mode 100644
index 1021936..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
deleted file mode 100644
index e2872f31..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
deleted file mode 100644
index e2872f31..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png
deleted file mode 100644
index 6a9b251..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-incremental-repaint-expected.png
deleted file mode 100644
index f0252fb..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-incremental-repaint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
deleted file mode 100644
index ad7239b..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index c0ced16..fa3bcbc 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png b/third_party/blink/web_tests/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
rename to third_party/blink/web_tests/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png b/third_party/blink/web_tests/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png
rename to third_party/blink/web_tests/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/paint-subrect-expected.png b/third_party/blink/web_tests/virtual/exotic-color-space/images/paint-subrect-expected.png
similarity index 100%
rename from third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/paint-subrect-expected.png
rename to third_party/blink/web_tests/virtual/exotic-color-space/images/paint-subrect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/README.md b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/README.md
new file mode 100644
index 0000000..3eaae58
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/README.md
@@ -0,0 +1 @@
+This virtual test suite will be removed once resizable ArrayBuffers ship in V8.
diff --git a/third_party/libaddressinput/README.chromium b/third_party/libaddressinput/README.chromium
index 4afd47c..d130f28b 100644
--- a/third_party/libaddressinput/README.chromium
+++ b/third_party/libaddressinput/README.chromium
@@ -1,8 +1,7 @@
 Name: libaddressinput
 URL: https://github.com/google/libaddressinput
-Version: 0
-Date: 16 May 2022
-Revision: 64bff363610097d3a44d0fe19912b53460e2ab78
+Version: e8712e415627f22d0b00ebee8db99547077f39bd
+Date: 2022-12-22
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b80e239e..477ec05 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -32319,6 +32319,7 @@
   <int value="1049" label="PrivacySandboxAdTopicsEnabled"/>
   <int value="1050" label="PrivacySandboxSiteEnabledAdsEnabled"/>
   <int value="1051" label="PrivacySandboxAdMeasurementEnabled"/>
+  <int value="1052" label="AppStoreRatingEnabled"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -36829,6 +36830,26 @@
   <int value="68" label="SRSXB33_1"/>
   <int value="69" label="SRSXB33_2"/>
   <int value="70" label="SRSXB33_3"/>
+  <int value="71" label="BoatRockerz255Pro"/>
+  <int value="72" label="BoseQuietComfort35II"/>
+  <int value="73" label="BoseQuietComfort35II_1"/>
+  <int value="74" label="JBLTUNE230NCTWS"/>
+  <int value="75" label="JBLTUNE230NCTWS_1"/>
+  <int value="76" label="JBLTUNE230NCTWS_2"/>
+  <int value="77" label="JBLTUNE230NCTWS_3"/>
+  <int value="78" label="OnePlusBuds"/>
+  <int value="79" label="OnePlusBuds_1"/>
+  <int value="80" label="OnePlusBuds_2"/>
+  <int value="81" label="RealMeBudsQ2TWS"/>
+  <int value="82" label="RealMeTechLifeBudsT100"/>
+  <int value="83" label="RealMeTechLifeBudsT100_1"/>
+  <int value="84" label="RealMeTechLifeBudsT100_2"/>
+  <int value="85" label="SRSXB13"/>
+  <int value="86" label="SRSXB13_1"/>
+  <int value="87" label="SRSXB13_2"/>
+  <int value="88" label="SRSXB13_3"/>
+  <int value="89" label="SRSXB13_4"/>
+  <int value="90" label="SRSXB13_5"/>
 </enum>
 
 <enum name="FastPairVersion">
@@ -57465,6 +57486,7 @@
       label="WalletRequiresFirstSyncSetupComplete:disabled"/>
   <int value="-1921593903" label="ImeInputLogicHmm:disabled"/>
   <int value="-1920912991" label="PermissionChip:disabled"/>
+  <int value="-1920869766" label="OobeJelly:disabled"/>
   <int value="-1920349954" label="FontManagerEarlyInit:disabled"/>
   <int value="-1920177029" label="FeedVideoInlinePlayback:enabled"/>
   <int value="-1920004248" label="FeedBackToTop:disabled"/>
@@ -61508,6 +61530,7 @@
       label="AutofillSendExperimentIdsInPaymentsRPCs:disabled"/>
   <int value="418769094" label="MixedContentSiteSetting:enabled"/>
   <int value="420160748" label="CornerShortcuts:enabled"/>
+  <int value="420356964" label="OobeJelly:enabled"/>
   <int value="420682005" label="AdaptiveButtonInTopToolbar:enabled"/>
   <int value="421986951" label="UseRealColorSpaceForAndroidVideo:disabled"/>
   <int value="422307097" label="PhysicalWeb:disabled"/>
@@ -61550,7 +61573,6 @@
   <int value="453017472"
       label="IntentBlockExternalFormRedirectsNoGesture:disabled"/>
   <int value="453102772" label="OfflinePagesLoadSignalCollecting:disabled"/>
-  <int value="453308897" label="OobeMaterialNext:enabled"/>
   <int value="453427119" label="EcheSWADisableStunServer:enabled"/>
   <int value="453601558" label="SupportToolScreenshot:enabled"/>
   <int value="454106312" label="UsernameFirstFlowFilling:enabled"/>
@@ -61559,7 +61581,6 @@
       label="disable-gesture-requirement-for-media-playback"/>
   <int value="455754036" label="MirroringService:disabled"/>
   <int value="457301531" label="SiteIsolationForGuests:disabled"/>
-  <int value="457686085" label="OobeMaterialNext:disabled"/>
   <int value="457881889" label="enable-autoplay-muted-videos"/>
   <int value="458410433" label="disable-views-rect-based-targeting"/>
   <int value="460136092" label="MidiManagerAndroid:disabled"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 8437818..c440415 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -4769,7 +4769,7 @@
 
 <histogram
     name="Android.WebView.Startup.NonblockingServiceConnectionDelay.{ServiceName}"
-    units="ms" expires_after="2023-02-01">
+    units="ms" expires_after="2023-09-01">
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index 422567cd8..24cd244 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -203,6 +203,9 @@
         summary="subsequent pairing protocol"/>
   </token>
   <token key="FastPairTrackedModelID">
+    <variant name="BoatRockerz255Pro"/>
+    <variant name="BoseQuietComfort35II"/>
+    <variant name="BoseQuietComfort35II_1"/>
     <variant name="JBLLIVE300TWS"/>
     <variant name="JBLLIVE300TWS_1"/>
     <variant name="JBLLIVE300TWS_2"/>
@@ -234,8 +237,15 @@
     <variant name="JBLTUNE225TWS_3"/>
     <variant name="JBLTUNE225TWS_4"/>
     <variant name="JBLTUNE225TWS_5"/>
+    <variant name="JBLTUNE230NCTWS_1"/>
+    <variant name="JBLTUNE230NCTWS_2"/>
+    <variant name="JBLTUNE230NCTWS_3"/>
+    <variant name="JBLTUNE230NCTWS_4"/>
     <variant name="NothingEar1"/>
     <variant name="NothingEar1_2"/>
+    <variant name="OnePlusBuds"/>
+    <variant name="OnePlusBuds_1"/>
+    <variant name="OnePlusBuds_2"/>
     <variant name="OnePlusBudsZ"/>
     <variant name="OnePlusBudsZ_1"/>
     <variant name="OnePlusBudsZ_2"/>
@@ -255,6 +265,10 @@
     <variant name="RealMeBudsAirPro"/>
     <variant name="RealMeBudsAirPro_1"/>
     <variant name="RealMeBudsAirPro_2"/>
+    <variant name="RealMeBudsQ2TWS"/>
+    <variant name="RealMeTechLifeBudsT100"/>
+    <variant name="RealMeTechLifeBudsT100_1"/>
+    <variant name="RealMeTechLifeBudsT100_2"/>
     <variant name="SonyWF1000XM3"/>
     <variant name="SonyWF1000XM3_1"/>
     <variant name="SonyWF1000XM3_2"/>
@@ -265,6 +279,12 @@
     <variant name="SonyWF1000XM3_7"/>
     <variant name="SonyWH1000XM3"/>
     <variant name="SonyWH1000XM3_1"/>
+    <variant name="SRSXB13"/>
+    <variant name="SRSXB13_1"/>
+    <variant name="SRSXB13_2"/>
+    <variant name="SRSXB13_3"/>
+    <variant name="SRSXB13_4"/>
+    <variant name="SRSXB13_5"/>
     <variant name="SRSXB23"/>
     <variant name="SRSXB23_1"/>
     <variant name="SRSXB23_2"/>
@@ -452,6 +472,9 @@
         summary="subsequent pairing protocol"/>
   </token>
   <token key="FastPairTrackedModelID">
+    <variant name="BoatRockerz255Pro"/>
+    <variant name="BoseQuietComfort35II"/>
+    <variant name="BoseQuietComfort35II_1"/>
     <variant name="JBLLIVE300TWS"/>
     <variant name="JBLLIVE300TWS_1"/>
     <variant name="JBLLIVE300TWS_2"/>
@@ -483,8 +506,15 @@
     <variant name="JBLTUNE225TWS_3"/>
     <variant name="JBLTUNE225TWS_4"/>
     <variant name="JBLTUNE225TWS_5"/>
+    <variant name="JBLTUNE230NCTWS_1"/>
+    <variant name="JBLTUNE230NCTWS_2"/>
+    <variant name="JBLTUNE230NCTWS_3"/>
+    <variant name="JBLTUNE230NCTWS_4"/>
     <variant name="NothingEar1"/>
     <variant name="NothingEar1_2"/>
+    <variant name="OnePlusBuds"/>
+    <variant name="OnePlusBuds_1"/>
+    <variant name="OnePlusBuds_2"/>
     <variant name="OnePlusBudsZ"/>
     <variant name="OnePlusBudsZ_1"/>
     <variant name="OnePlusBudsZ_2"/>
@@ -504,6 +534,10 @@
     <variant name="RealMeBudsAirPro"/>
     <variant name="RealMeBudsAirPro_1"/>
     <variant name="RealMeBudsAirPro_2"/>
+    <variant name="RealMeBudsQ2TWS"/>
+    <variant name="RealMeTechLifeBudsT100"/>
+    <variant name="RealMeTechLifeBudsT100_1"/>
+    <variant name="RealMeTechLifeBudsT100_2"/>
     <variant name="SonyWF1000XM3"/>
     <variant name="SonyWF1000XM3_1"/>
     <variant name="SonyWF1000XM3_2"/>
@@ -514,6 +548,12 @@
     <variant name="SonyWF1000XM3_7"/>
     <variant name="SonyWH1000XM3"/>
     <variant name="SonyWH1000XM3_1"/>
+    <variant name="SRSXB13"/>
+    <variant name="SRSXB13_1"/>
+    <variant name="SRSXB13_2"/>
+    <variant name="SRSXB13_3"/>
+    <variant name="SRSXB13_4"/>
+    <variant name="SRSXB13_5"/>
     <variant name="SRSXB23"/>
     <variant name="SRSXB23_1"/>
     <variant name="SRSXB23_2"/>
@@ -1162,6 +1202,9 @@
     UI, and when the learn more button is pressed on the associate account UI.
   </summary>
   <token key="FastPairTrackedModelID">
+    <variant name="BoatRockerz255Pro"/>
+    <variant name="BoseQuietComfort35II"/>
+    <variant name="BoseQuietComfort35II_1"/>
     <variant name="JBLLIVE300TWS"/>
     <variant name="JBLLIVE300TWS_1"/>
     <variant name="JBLLIVE300TWS_2"/>
@@ -1193,8 +1236,15 @@
     <variant name="JBLTUNE225TWS_3"/>
     <variant name="JBLTUNE225TWS_4"/>
     <variant name="JBLTUNE225TWS_5"/>
+    <variant name="JBLTUNE230NCTWS_1"/>
+    <variant name="JBLTUNE230NCTWS_2"/>
+    <variant name="JBLTUNE230NCTWS_3"/>
+    <variant name="JBLTUNE230NCTWS_4"/>
     <variant name="NothingEar1"/>
     <variant name="NothingEar1_2"/>
+    <variant name="OnePlusBuds"/>
+    <variant name="OnePlusBuds_1"/>
+    <variant name="OnePlusBuds_2"/>
     <variant name="OnePlusBudsZ"/>
     <variant name="OnePlusBudsZ_1"/>
     <variant name="OnePlusBudsZ_2"/>
@@ -1214,6 +1264,10 @@
     <variant name="RealMeBudsAirPro"/>
     <variant name="RealMeBudsAirPro_1"/>
     <variant name="RealMeBudsAirPro_2"/>
+    <variant name="RealMeBudsQ2TWS"/>
+    <variant name="RealMeTechLifeBudsT100"/>
+    <variant name="RealMeTechLifeBudsT100_1"/>
+    <variant name="RealMeTechLifeBudsT100_2"/>
     <variant name="SonyWF1000XM3"/>
     <variant name="SonyWF1000XM3_1"/>
     <variant name="SonyWF1000XM3_2"/>
@@ -1224,6 +1278,12 @@
     <variant name="SonyWF1000XM3_7"/>
     <variant name="SonyWH1000XM3"/>
     <variant name="SonyWH1000XM3_1"/>
+    <variant name="SRSXB13"/>
+    <variant name="SRSXB13_1"/>
+    <variant name="SRSXB13_2"/>
+    <variant name="SRSXB13_3"/>
+    <variant name="SRSXB13_4"/>
+    <variant name="SRSXB13_5"/>
     <variant name="SRSXB23"/>
     <variant name="SRSXB23_1"/>
     <variant name="SRSXB23_2"/>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index f0f864b..fa5dfb3 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -2931,7 +2931,7 @@
 
 <histogram
     name="Extensions.SettingsOverridden.BackToGoogleNtpOverriddenDialogResult"
-    enum="SettingsOverriddenDialogResult" expires_after="2022-10-04">
+    enum="SettingsOverriddenDialogResult" expires_after="2023-12-15">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -2945,7 +2945,7 @@
 
 <histogram
     name="Extensions.SettingsOverridden.BackToGoogleSearchOverriddenDialogResult"
-    enum="SettingsOverriddenDialogResult" expires_after="2022-12-04">
+    enum="SettingsOverriddenDialogResult" expires_after="2023-12-15">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -2959,7 +2959,7 @@
 
 <histogram
     name="Extensions.SettingsOverridden.BackToOtherSearchOverriddenDialogResult"
-    enum="SettingsOverriddenDialogResult" expires_after="2023-01-01">
+    enum="SettingsOverriddenDialogResult" expires_after="2023-12-15">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -2973,7 +2973,7 @@
 
 <histogram
     name="Extensions.SettingsOverridden.GenericNtpOverriddenDialogResult"
-    enum="SettingsOverriddenDialogResult" expires_after="2022-10-04">
+    enum="SettingsOverriddenDialogResult" expires_after="2023-12-15">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -2986,7 +2986,7 @@
 
 <histogram
     name="Extensions.SettingsOverridden.GenericSearchOverriddenDialogResult"
-    enum="SettingsOverriddenDialogResult" expires_after="2022-12-11">
+    enum="SettingsOverriddenDialogResult" expires_after="2023-12-15">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 84b989d..b5679d1 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -96,6 +96,57 @@
   </summary>
 </histogram>
 
+<histogram name="Network.Ash.Cellular.Apn.DisableCustomApn.ApnTypes"
+    enum="ApnTypes" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>
+    Records the APN types of an APN before modification. This is emitted each
+    time a user disables a custom APN.
+  </summary>
+</histogram>
+
+<histogram name="Network.Ash.Cellular.Apn.DisableCustomApn.Result"
+    enum="BooleanSuccess" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>Records the result of an attempt to disable a custom APN.</summary>
+</histogram>
+
+<histogram name="Network.Ash.Cellular.Apn.EnableCustomApn.ApnTypes"
+    enum="ApnTypes" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>
+    Records the APN types of an APN before modification. This is emitted each
+    time a user enable a custom APN.
+  </summary>
+</histogram>
+
+<histogram name="Network.Ash.Cellular.Apn.EnableCustomApn.Result"
+    enum="BooleanSuccess" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>Records the result of an attempt to enable a custom APN.</summary>
+</histogram>
+
+<histogram name="Network.Ash.Cellular.Apn.ModifyCustomApn.ApnTypes"
+    enum="ApnTypes" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>
+    Records the APN types of an APN before modification. This is emitted each
+    time a user modifies a custom APN.
+  </summary>
+</histogram>
+
+<histogram name="Network.Ash.Cellular.Apn.ModifyCustomApn.Result"
+    enum="BooleanSuccess" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>Records the result of an attempt to modify a custom APN.</summary>
+</histogram>
+
 <histogram name="Network.Ash.Cellular.Apn.RemoveCustomApn.ApnTypes"
     enum="ApnTypes" expires_after="2023-10-18">
   <owner>gordonseto@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index 5955d0a..9e61695 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -117,6 +117,21 @@
   </summary>
 </histogram>
 
+<histogram name="Startup.Android.Cold.TimeToFirstNavigationCommit2.Tabbed"
+    units="ms" expires_after="2023-06-04">
+  <owner>pasko@chromium.org</owner>
+  <owner>agrieve@chromium.org</owner>
+  <summary>
+    Experimental. Same as
+    Startup.Android.Cold.TimeToFirstNavigationCommit.Tabbed with two
+    modifications.
+
+    The first difference is to record navigation commits happening before
+    post-native initialization. The second modifications is not to record
+    samples when FRE is shown.
+  </summary>
+</histogram>
+
 <histogram name="Startup.Android.Cold.TimeToFirstVisibleContent" units="ms"
     expires_after="never">
 <!-- expires-never: guiding metric (internal: go/chrome-browser-guiding-metrics) -->
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index f86fa4a..396de62 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "1afc434b55f61464bb6572c6debaf5097c10c819",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/1cb29c1d2443c26ce562ad4fac05ed6c365b97dc/trace_processor_shell.exe"
+            "hash": "0befcfb83f92421c85513c0d86328dd1fbffd5ba",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "d9e9df1c54ec0b0fa8946662398887877977d57e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/1cb29c1d2443c26ce562ad4fac05ed6c365b97dc/trace_processor_shell"
+            "hash": "642e3e46788e22f52302b04aea6422f7810718e1",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "6ed855f2f81b0d2f715f4350aa44fc55c87380f7",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/1cb29c1d2443c26ce562ad4fac05ed6c365b97dc/trace_processor_shell"
+            "hash": "e84516277d57cf556b47707d89511acc85bd8aeb",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc
index 9b1d37e..e745dd7 100644
--- a/ui/ozone/common/gl_ozone_egl.cc
+++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -13,6 +13,7 @@
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/gl_utils.h"
+#include "ui/gl/presenter.h"
 
 namespace ui {
 
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 1efbcbf..0d26515 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -405,6 +405,8 @@
     "test/mock_surface.h",
     "test/mock_wayland_platform_window_delegate.cc",
     "test/mock_wayland_platform_window_delegate.h",
+    "test/mock_wayland_zcr_color_manager.cc",
+    "test/mock_wayland_zcr_color_manager.h",
     "test/mock_wp_presentation.cc",
     "test/mock_wp_presentation.h",
     "test/mock_xdg_shell.cc",
@@ -475,6 +477,14 @@
     "test/test_viewporter.h",
     "test/test_wayland_server_thread.cc",
     "test/test_wayland_server_thread.h",
+    "test/test_wayland_zcr_color_management_output.cc",
+    "test/test_wayland_zcr_color_management_output.h",
+    "test/test_wayland_zcr_color_management_surface.cc",
+    "test/test_wayland_zcr_color_management_surface.h",
+    "test/test_wayland_zcr_color_space.cc",
+    "test/test_wayland_zcr_color_space.h",
+    "test/test_wayland_zcr_color_space_creator.cc",
+    "test/test_wayland_zcr_color_space_creator.h",
     "test/test_wp_pointer_gestures.cc",
     "test/test_wp_pointer_gestures.h",
     "test/test_xdg_popup.cc",
@@ -514,6 +524,7 @@
   deps = [
     ":wayland",
     "//base:base",
+    "//ui/base/wayland:color_manager_util",
     "//ui/base/wayland:wayland_display_util",
     "//ui/ozone:platform",
     "//ui/ozone:test_support",
@@ -569,6 +580,7 @@
     "host/wayland_window_unittest.cc",
     "host/wayland_zaura_output_unittest.cc",
     "host/wayland_zaura_shell_unittest.cc",
+    "host/wayland_zcr_color_manager_unittest.cc",
     "host/wayland_zwp_pointer_gestures_unittest.cc",
     "host/zwp_text_input_wrapper_v1_unittest.cc",
     "test/wayland_drag_drop_test.cc",
diff --git a/ui/ozone/platform/wayland/host/wayland_zcr_color_manager_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zcr_color_manager_unittest.cc
new file mode 100644
index 0000000..1c9f810
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_zcr_color_manager_unittest.cc
@@ -0,0 +1,199 @@
+// 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.
+
+#include <chrome-color-management-client-protocol.h>
+
+#include "base/check.h"
+#include "base/files/file_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/display/test/test_screen.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/common/wayland_object.h"
+#include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_output.h"
+#include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
+#include "ui/ozone/platform/wayland/host/wayland_zcr_color_management_output.h"
+#include "ui/ozone/platform/wayland/host/wayland_zcr_color_manager.h"
+#include "ui/ozone/platform/wayland/host/wayland_zcr_color_space.h"
+#include "ui/ozone/platform/wayland/test/mock_surface.h"
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h"
+#include "ui/ozone/platform/wayland/test/wayland_test.h"
+
+using ::testing::Values;
+
+namespace ui {
+namespace {
+
+base::ScopedFD MakeFD() {
+  base::FilePath temp_path;
+  EXPECT_TRUE(base::CreateTemporaryFile(&temp_path));
+  auto file =
+      base::File(temp_path, base::File::FLAG_READ | base::File::FLAG_WRITE |
+                                base::File::FLAG_CREATE_ALWAYS);
+  return base::ScopedFD(file.TakePlatformFile());
+}
+
+class WaylandZcrColorManagerTest : public WaylandTest {
+ public:
+  WaylandZcrColorManagerTest() = default;
+  WaylandZcrColorManagerTest(const WaylandZcrColorManagerTest&) = delete;
+  WaylandZcrColorManagerTest& operator=(const WaylandZcrColorManagerTest&) =
+      delete;
+  ~WaylandZcrColorManagerTest() override = default;
+
+  void SetUp() override {
+    WaylandTest::SetUp();
+    ASSERT_TRUE(connection_->zcr_color_manager());
+  }
+
+  WaylandWindow* window() { return window_.get(); }
+};
+
+}  // namespace
+
+TEST_P(WaylandZcrColorManagerTest, CreateColorManagementOutput) {
+  // Set default values for the output.
+  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
+    wl::TestOutput* output = server->output();
+    output->SetRect({800, 600});
+    output->SetScale(1);
+    output->Flush();
+  });
+
+  auto* output_manager = connection_->wayland_output_manager();
+  WaylandOutput* wayland_output = output_manager->GetPrimaryOutput();
+  ASSERT_TRUE(wayland_output);
+  EXPECT_TRUE(wayland_output->IsReady());
+
+  auto* color_management_output = wayland_output->color_management_output();
+  ASSERT_TRUE(color_management_output);
+  // Set HDR10 on server output, notify color management output with event.
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_outputs();
+    for (auto* mock_params : params_vector) {
+      mock_params->SetGfxColorSpace(gfx::ColorSpace::CreateHDR10());
+      zcr_color_management_output_v1_send_color_space_changed(
+          mock_params->resource());
+    }
+  });
+  // Allow output to respond to server event, then send color space events.
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_outputs();
+    for (auto* mock_params : params_vector) {
+      auto* zcr_color_space = mock_params->GetZcrColorSpace();
+      // assert that the color space is the same as the one in output.
+      EXPECT_EQ(zcr_color_space->GetGfxColorSpace(),
+                gfx::ColorSpace::CreateHDR10());
+      // send HDR10 over wayland.
+      zcr_color_space_v1_send_names(zcr_color_space->resource(), 5, 5, 3);
+      zcr_color_space_v1_send_done(zcr_color_space->resource());
+    }
+  });
+
+  // Check that the received color space is equal to the one on server output.
+  auto* gfx_color_space = color_management_output->gfx_color_space();
+  gfx_color_space->ToString();
+  EXPECT_EQ(*gfx_color_space, gfx::ColorSpace::CreateHDR10());
+}
+
+TEST_P(WaylandZcrColorManagerTest, CreateColorManagementSurface) {
+  auto* surface = window_.get()->root_surface();
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_surfaces();
+    for (auto* mock_params : params_vector) {
+      EXPECT_EQ(gfx::ColorSpace::CreateSRGB(), mock_params->GetGfxColorSpace());
+    }
+  });
+
+  // Updated buffer handle needed for ApplyPendingState() to set color_space
+  EXPECT_TRUE(connection_->buffer_manager_host());
+  auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
+  buffer_manager_gpu_->Initialize(std::move(interface_ptr), {}, false, true,
+                                  false, 0);
+
+  // Setup wl_buffers.
+  constexpr uint32_t buffer_id = 1;
+  gfx::Size buffer_size(1024, 768);
+  auto length = 1024 * 768 * 4;
+  buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
+                                            buffer_id);
+  base::RunLoop().RunUntilIdle();
+
+  // preload color_space in color manager cache, since first call with a
+  // new color_space always returns null.
+  connection_->zcr_color_manager()->GetColorSpace(
+      gfx::ColorSpace::CreateHDR10());
+  connection_->RoundTripQueue();
+  surface->set_color_space(gfx::ColorSpace::CreateHDR10());
+  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
+      surface, buffer_id));
+  surface->ApplyPendingState();
+  // Assert that surface on server has correct color_space.
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_surfaces();
+    EXPECT_EQ(gfx::ColorSpace::CreateHDR10(),
+              params_vector.front()->GetGfxColorSpace());
+  });
+}
+
+TEST_P(WaylandZcrColorManagerTest, DoNotSetInvaliColorSpace) {
+  auto* surface = window_.get()->root_surface();
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_surfaces();
+    for (auto* mock_params : params_vector) {
+      EXPECT_EQ(gfx::ColorSpace::CreateSRGB(), mock_params->GetGfxColorSpace());
+    }
+  });
+
+  // Updated buffer handle needed for ApplyPendingState() to set color_space
+  EXPECT_TRUE(connection_->buffer_manager_host());
+  auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
+  buffer_manager_gpu_->Initialize(std::move(interface_ptr), {}, false, true,
+                                  false, 0);
+
+  // Setup wl_buffers.
+  constexpr uint32_t buffer_id = 1;
+  gfx::Size buffer_size(1024, 768);
+  auto length = 1024 * 768 * 4;
+  buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
+                                            buffer_id);
+  base::RunLoop().RunUntilIdle();
+
+  gfx::ColorSpace invalid_space =
+      gfx::ColorSpace(gfx::ColorSpace::PrimaryID::INVALID,
+                      gfx::ColorSpace::TransferID::INVALID);
+  // Attempt to set an invalid color space on the surface and expect that the
+  // original default color space remains unchanged on the surface.
+  connection_->zcr_color_manager()->GetColorSpace(invalid_space);
+  connection_->RoundTripQueue();
+  surface->set_color_space(invalid_space);
+  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
+      surface, buffer_id));
+  surface->ApplyPendingState();
+  // Assert that surface on server has correct color_space.
+  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
+    auto params_vector =
+        server->zcr_color_manager_v1()->color_management_surfaces();
+    EXPECT_EQ(gfx::ColorSpace::CreateSRGB(),
+              params_vector.front()->GetGfxColorSpace());
+  });
+}
+
+INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
+                         WaylandZcrColorManagerTest,
+                         Values(wl::ServerConfig{}));
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.cc b/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.cc
new file mode 100644
index 0000000..40ad018b
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.cc
@@ -0,0 +1,203 @@
+// 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.
+
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
+
+#include <chrome-color-management-server-protocol.h>
+#include <cstdint>
+#include <iterator>
+
+#include "base/ranges/algorithm.h"
+#include "ui/base/wayland/color_manager_util.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/global_object.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.h"
+
+namespace wl {
+
+namespace {
+
+constexpr uint32_t kZcrColorManagerVersion = 1;
+
+void CreateColorSpaceFromIcc(wl_client* client,
+                             wl_resource* resource,
+                             uint32_t id,
+                             int32_t fd) {
+  auto* zcr_color_manager = GetUserDataAs<MockZcrColorManagerV1>(resource);
+  zcr_color_manager->CreateColorSpaceFromIcc(client, resource, id, fd);
+}
+
+void CreateColorSpaceFromNames(wl_client* client,
+                               wl_resource* resource,
+                               uint32_t id,
+                               uint32_t eotf,
+                               uint32_t chromaticity,
+                               uint32_t whitepoint) {
+  wl_resource* color_space_resource =
+      CreateResourceWithImpl<TestZcrColorSpaceV1>(
+          client, &zcr_color_space_v1_interface, 1, &kTestZcrColorSpaceV1Impl,
+          0);
+  DCHECK(color_space_resource);
+  auto* zcr_color_manager = GetUserDataAs<MockZcrColorManagerV1>(resource);
+  auto* zcr_color_space =
+      GetUserDataAs<TestZcrColorSpaceV1>(color_space_resource);
+
+  auto chromaticity_id = gfx::ColorSpace::PrimaryID::INVALID;
+  const auto* maybe_primary = ui::wayland::kChromaticityMap.find(chromaticity);
+  if (maybe_primary != ui::wayland::kChromaticityMap.end()) {
+    chromaticity_id = maybe_primary->second;
+  }
+  auto eotf_id = gfx::ColorSpace::TransferID::INVALID;
+  const auto* maybe_eotf = ui::wayland::kEotfMap.find(eotf);
+  if (maybe_eotf != ui::wayland::kEotfMap.end()) {
+    eotf_id = maybe_eotf->second;
+  }
+
+  zcr_color_space->SetGfxColorSpace(gfx::ColorSpace(chromaticity_id, eotf_id));
+  zcr_color_space->SetZcrColorManager(zcr_color_manager);
+  wl_resource* creator_resource =
+      CreateResourceWithImpl<TestZcrColorSpaceCreatorV1>(
+          client, &zcr_color_space_creator_v1_interface, 1, nullptr, id);
+  DCHECK(creator_resource);
+  zcr_color_space_creator_v1_send_created(creator_resource,
+                                          color_space_resource);
+
+  zcr_color_manager->StoreZcrColorSpace(zcr_color_space);
+  zcr_color_manager->CreateColorSpaceFromNames(client, resource, id, eotf,
+                                               chromaticity, whitepoint);
+}
+
+void CreateColorSpaceFromParams(wl_client* client,
+                                wl_resource* resource,
+                                uint32_t id,
+                                uint32_t eotf,
+                                uint32_t primary_r_x,
+                                uint32_t primary_r_y,
+                                uint32_t primary_g_x,
+                                uint32_t primary_g_y,
+                                uint32_t primary_b_x,
+                                uint32_t primary_b_y,
+                                uint32_t whitepoint_x,
+                                uint32_t whitepoint_y) {
+  wl_resource* color_space_resource =
+      CreateResourceWithImpl<TestZcrColorSpaceV1>(
+          client, &zcr_color_space_v1_interface, 1, &kTestZcrColorSpaceV1Impl,
+          0);
+  DCHECK(color_space_resource);
+  auto* zcr_color_manager = GetUserDataAs<MockZcrColorManagerV1>(resource);
+  auto* zcr_color_space =
+      GetUserDataAs<TestZcrColorSpaceV1>(color_space_resource);
+
+  zcr_color_space->SetGfxColorSpace(gfx::ColorSpace::CreateSRGB());
+  zcr_color_space->SetZcrColorManager(zcr_color_manager);
+  wl_resource* creator_resource =
+      CreateResourceWithImpl<TestZcrColorSpaceCreatorV1>(
+          client, &zcr_color_space_creator_v1_interface, 1, nullptr, id);
+  DCHECK(creator_resource);
+  zcr_color_space_creator_v1_send_created(creator_resource,
+                                          color_space_resource);
+
+  zcr_color_manager->StoreZcrColorSpace(zcr_color_space);
+  zcr_color_manager->CreateColorSpaceFromParams(
+      client, resource, id, eotf, primary_r_x, primary_r_y, primary_g_x,
+      primary_g_y, primary_b_x, primary_b_y, whitepoint_x, whitepoint_y);
+}
+
+void GetColorManagementOutput(wl_client* client,
+                              wl_resource* resource,
+                              uint32_t id,
+                              wl_resource* output) {
+  wl_resource* output_resource =
+      CreateResourceWithImpl<TestZcrColorManagementOutputV1>(
+          client, &zcr_color_management_output_v1_interface, 1,
+          &kTestZcrColorManagementOutputV1Impl, id);
+  DCHECK(output_resource);
+  auto* zcr_color_manager = GetUserDataAs<MockZcrColorManagerV1>(resource);
+  auto* color_manager_output =
+      GetUserDataAs<TestZcrColorManagementOutputV1>(output_resource);
+
+  gfx::ColorSpace color_space;
+  color_manager_output->SetGfxColorSpace(color_space);
+  color_manager_output->StoreZcrColorManager(zcr_color_manager);
+  zcr_color_manager->StoreZcrColorManagementOutput(color_manager_output);
+  zcr_color_manager->GetColorManagementOutput(client, resource, id, output);
+}
+
+void GetColorManagementSurface(wl_client* client,
+                               wl_resource* resource,
+                               uint32_t id,
+                               wl_resource* output) {
+  wl_resource* surface_resource =
+      CreateResourceWithImpl<TestZcrColorManagementSurfaceV1>(
+          client, &zcr_color_management_surface_v1_interface, 1,
+          &kTestZcrColorManagementSurfaceV1Impl, id);
+  DCHECK(surface_resource);
+  auto* zcr_color_manager = GetUserDataAs<MockZcrColorManagerV1>(resource);
+  auto* color_manager_surface =
+      GetUserDataAs<TestZcrColorManagementSurfaceV1>(surface_resource);
+
+  gfx::ColorSpace color_space;
+  color_manager_surface->SetGfxColorSpace(color_space);
+  color_manager_surface->StoreZcrColorManager(zcr_color_manager);
+  zcr_color_manager->StoreZcrColorManagementSurface(color_manager_surface);
+  zcr_color_manager->GetColorManagementSurface(client, resource, id, output);
+}
+
+}  // namespace
+
+const struct zcr_color_manager_v1_interface kMockZcrColorManagerV1Impl = {
+    &CreateColorSpaceFromIcc,    &CreateColorSpaceFromNames,
+    &CreateColorSpaceFromParams, &GetColorManagementOutput,
+    &GetColorManagementSurface,  &DestroyResource};
+
+MockZcrColorManagerV1::MockZcrColorManagerV1()
+    : GlobalObject(&zcr_color_manager_v1_interface,
+                   &kMockZcrColorManagerV1Impl,
+                   kZcrColorManagerVersion) {}
+
+MockZcrColorManagerV1::~MockZcrColorManagerV1() {
+  DCHECK(color_manager_outputs_.empty());
+  DCHECK(color_manager_surfaces_.empty());
+}
+
+void MockZcrColorManagerV1::StoreZcrColorManagementOutput(
+    TestZcrColorManagementOutputV1* params) {
+  color_manager_outputs_.push_back(params);
+}
+
+void MockZcrColorManagerV1::StoreZcrColorManagementSurface(
+    TestZcrColorManagementSurfaceV1* params) {
+  color_manager_surfaces_.push_back(params);
+}
+
+void MockZcrColorManagerV1::StoreZcrColorSpace(TestZcrColorSpaceV1* params) {
+  color_manager_color_spaces_.push_back(params);
+}
+
+void MockZcrColorManagerV1::OnZcrColorManagementOutputDestroyed(
+    TestZcrColorManagementOutputV1* params) {
+  auto it = base::ranges::find(color_manager_outputs_, params);
+  DCHECK(it != color_manager_outputs_.end());
+  color_manager_outputs_.erase(it);
+}
+
+void MockZcrColorManagerV1::OnZcrColorManagementSurfaceDestroyed(
+    TestZcrColorManagementSurfaceV1* params) {
+  auto it = base::ranges::find(color_manager_surfaces_, params);
+  DCHECK(it != color_manager_surfaces_.end());
+  color_manager_surfaces_.erase(it);
+}
+
+void MockZcrColorManagerV1::OnZcrColorSpaceDestroyed(
+    TestZcrColorSpaceV1* params) {
+  auto it = base::ranges::find(color_manager_color_spaces_, params);
+  DCHECK(it != color_manager_color_spaces_.end());
+  color_manager_color_spaces_.erase(it);
+}
+
+}  // namespace wl
diff --git a/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h b/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h
new file mode 100644
index 0000000..85c9d2c
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h
@@ -0,0 +1,98 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_WAYLAND_ZCR_COLOR_MANAGER_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_WAYLAND_ZCR_COLOR_MANAGER_H_
+
+#include <chrome-color-management-server-protocol.h>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/ozone/platform/wayland/test/global_object.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace wl {
+
+extern const struct zcr_color_manager_v1_interface kMockZcrColorManagerV1Impl;
+
+class TestZcrColorManagementOutputV1;
+class TestZcrColorManagementSurfaceV1;
+class TestZcrColorSpaceV1;
+
+// Manage zwp_linux_buffer_params_v1
+class MockZcrColorManagerV1 : public GlobalObject {
+ public:
+  MockZcrColorManagerV1();
+
+  MockZcrColorManagerV1(const MockZcrColorManagerV1&) = delete;
+  MockZcrColorManagerV1& operator=(const MockZcrColorManagerV1&) = delete;
+
+  ~MockZcrColorManagerV1() override;
+
+  MOCK_METHOD4(
+      CreateColorSpaceFromIcc,
+      void(wl_client* client, wl_resource* resource, uint32_t id, int32_t fd));
+  MOCK_METHOD6(CreateColorSpaceFromNames,
+               void(wl_client* client,
+                    wl_resource* resource,
+                    uint32_t id,
+                    uint32_t eotf,
+                    uint32_t chromaticity,
+                    uint32_t whitepoint));
+  MOCK_METHOD(void,
+              CreateColorSpaceFromParams,
+              (wl_client * client,
+               wl_resource* resource,
+               uint32_t id,
+               uint32_t eotf,
+               uint32_t primary_r_x,
+               uint32_t primary_r_y,
+               uint32_t primary_g_x,
+               uint32_t primary_g_y,
+               uint32_t primary_b_x,
+               uint32_t primary_b_y,
+               uint32_t whitepoint_x,
+               uint32_t whitepoint_y));
+  MOCK_METHOD4(GetColorManagementOutput,
+               void(wl_client* client,
+                    wl_resource* resource,
+                    uint32_t id,
+                    wl_resource* output));
+  MOCK_METHOD4(GetColorManagementSurface,
+               void(wl_client* client,
+                    wl_resource* resource,
+                    uint32_t id,
+                    wl_resource* surface));
+  MOCK_METHOD2(Destroy, void(wl_client* client, wl_resource* resource));
+
+  const std::vector<TestZcrColorManagementOutputV1*> color_management_outputs()
+      const {
+    return color_manager_outputs_;
+  }
+
+  const std::vector<TestZcrColorManagementSurfaceV1*>
+  color_management_surfaces() const {
+    return color_manager_surfaces_;
+  }
+  void StoreZcrColorManagementOutput(TestZcrColorManagementOutputV1* params);
+  void StoreZcrColorManagementSurface(TestZcrColorManagementSurfaceV1* params);
+  void StoreZcrColorSpace(TestZcrColorSpaceV1* params);
+
+  void OnZcrColorManagementOutputDestroyed(
+      TestZcrColorManagementOutputV1* params);
+  void OnZcrColorManagementSurfaceDestroyed(
+      TestZcrColorManagementSurfaceV1* params);
+  void OnZcrColorSpaceDestroyed(TestZcrColorSpaceV1* params);
+
+ private:
+  std::vector<TestZcrColorManagementOutputV1*> color_manager_outputs_;
+  std::vector<TestZcrColorManagementSurfaceV1*> color_manager_surfaces_;
+  std::vector<TestZcrColorSpaceV1*> color_manager_color_spaces_;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_MOCK_WAYLAND_ZCR_COLOR_MANAGER_H_
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
index 100f51f..cf432c48 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
@@ -144,6 +144,9 @@
     return false;
   if (!wp_pointer_gestures_.Initialize(display_.get()))
     return false;
+  if (!zcr_color_manager_v1_.Initialize(display_.get())) {
+    return false;
+  }
 
   client_ = wl_client_create(display_.get(), server_fd.release());
   if (!client_)
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index 4151878..b463345 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -17,6 +17,7 @@
 #include "base/threading/thread_checker.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/ozone/platform/wayland/test/global_object.h"
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
 #include "ui/ozone/platform/wayland/test/mock_wp_presentation.h"
 #include "ui/ozone/platform/wayland/test/mock_xdg_shell.h"
 #include "ui/ozone/platform/wayland/test/mock_zwp_linux_dmabuf.h"
@@ -154,6 +155,10 @@
 
   TestWpPointerGestures& wp_pointer_gestures() { return wp_pointer_gestures_; }
 
+  MockZcrColorManagerV1* zcr_color_manager_v1() {
+    return &zcr_color_manager_v1_;
+  }
+
   void set_output_delegate(OutputDelegate* delegate) {
     output_delegate_ = delegate;
   }
@@ -217,6 +222,7 @@
   TestZXdgOutputManager zxdg_output_manager_;
   MockXdgShell xdg_shell_;
   TestZAuraShell zaura_shell_;
+  MockZcrColorManagerV1 zcr_color_manager_v1_;
   TestZcrStylus zcr_stylus_;
   TestZcrTextInputExtensionV1 zcr_text_input_extension_v1_;
   TestZwpTextInputManagerV1 zwp_text_input_manager_v1_;
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.cc b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.cc
new file mode 100644
index 0000000..8dd6ab23
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h"
+
+#include "base/notreached.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h"
+
+namespace wl {
+
+namespace {
+
+void GetColorSpace(wl_client* client, wl_resource* resource, uint32_t id) {
+  wl_resource* color_space_resource =
+      CreateResourceWithImpl<TestZcrColorSpaceV1>(
+          client, &zcr_color_space_v1_interface, 1, &kTestZcrColorSpaceV1Impl,
+          id);
+  auto* color_management_output =
+      GetUserDataAs<TestZcrColorManagementOutputV1>(resource);
+  auto* zcr_color_space =
+      GetUserDataAs<TestZcrColorSpaceV1>(color_space_resource);
+  zcr_color_space->SetGfxColorSpace(
+      color_management_output->GetGfxColorSpace());
+  color_management_output->StoreZcrColorSpace(zcr_color_space);
+}
+
+}  // namespace
+
+const struct zcr_color_management_output_v1_interface
+    kTestZcrColorManagementOutputV1Impl = {&GetColorSpace, &DestroyResource};
+
+TestZcrColorManagementOutputV1::TestZcrColorManagementOutputV1(
+    wl_resource* resource)
+    : ServerObject(resource) {}
+
+TestZcrColorManagementOutputV1::~TestZcrColorManagementOutputV1() {
+  DCHECK(zcr_color_manager_);
+  zcr_color_manager_->OnZcrColorManagementOutputDestroyed(this);
+}
+
+void TestZcrColorManagementOutputV1::SetGfxColorSpace(
+    gfx::ColorSpace gfx_color_space) {
+  gfx_color_space_ = gfx_color_space;
+}
+
+void TestZcrColorManagementOutputV1::StoreZcrColorManager(
+    MockZcrColorManagerV1* zcr_color_manager) {
+  zcr_color_manager_ = zcr_color_manager;
+}
+
+void TestZcrColorManagementOutputV1::StoreZcrColorSpace(
+    TestZcrColorSpaceV1* zcr_color_space) {
+  zcr_color_space_ = zcr_color_space;
+}
+}  // namespace wl
\ No newline at end of file
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h
new file mode 100644
index 0000000..4f92cde
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_output.h
@@ -0,0 +1,55 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_OUTPUT_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_OUTPUT_H_
+
+#include <chrome-color-management-server-protocol.h>
+
+#include "base/files/scoped_file.h"
+#include "base/memory/raw_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace wl {
+
+extern const struct zcr_color_management_output_v1_interface
+    kTestZcrColorManagementOutputV1Impl;
+
+class MockZcrColorManagerV1;
+class TestZcrColorSpaceV1;
+
+class TestZcrColorManagementOutputV1 : public ServerObject {
+ public:
+  explicit TestZcrColorManagementOutputV1(wl_resource* resource);
+
+  TestZcrColorManagementOutputV1(const TestZcrColorManagementOutputV1&) =
+      delete;
+  TestZcrColorManagementOutputV1& operator=(
+      const TestZcrColorManagementOutputV1&) = delete;
+
+  ~TestZcrColorManagementOutputV1() override;
+
+  void GetColorSpace(wl_client* client, wl_resource* resource, uint32_t id);
+  void Destroy(wl_client* client, wl_resource* resource);
+
+  gfx::ColorSpace GetGfxColorSpace() const { return gfx_color_space_; }
+  void SetGfxColorSpace(gfx::ColorSpace gfx_color_space);
+  void StoreZcrColorManager(MockZcrColorManagerV1* zcr_color_manager);
+  TestZcrColorSpaceV1* GetZcrColorSpace() const { return zcr_color_space_; }
+  void StoreZcrColorSpace(TestZcrColorSpaceV1* zcr_color_space);
+
+ private:
+  gfx::ColorSpace gfx_color_space_;
+  raw_ptr<MockZcrColorManagerV1> zcr_color_manager_ = nullptr;
+  raw_ptr<TestZcrColorSpaceV1> zcr_color_space_ = nullptr;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_OUTPUT_H_
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.cc b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.cc
new file mode 100644
index 0000000..5f3baf57
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.cc
@@ -0,0 +1,82 @@
+// 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.
+
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h"
+
+#include "base/notreached.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h"
+
+namespace wl {
+
+namespace {
+
+void SetAlphaMode(wl_client* client,
+                  wl_resource* resource,
+                  uint32_t alpha_mode) {
+  auto* color_management_surface =
+      GetUserDataAs<TestZcrColorManagementSurfaceV1>(resource);
+  DCHECK(color_management_surface);
+}
+
+void SetExtendedDynamicRange(wl_client* client,
+                             wl_resource* resource,
+                             uint32_t value) {
+  auto* color_management_surface =
+      GetUserDataAs<TestZcrColorManagementSurfaceV1>(resource);
+  DCHECK(color_management_surface);
+}
+
+void SetColorSpace(wl_client* client,
+                   wl_resource* resource,
+                   wl_resource* color_space_resource,
+                   uint32_t render_intent) {
+  auto* color_management_surface =
+      GetUserDataAs<TestZcrColorManagementSurfaceV1>(resource);
+  DCHECK(color_space_resource);
+  auto* zcr_color_space =
+      GetUserDataAs<TestZcrColorSpaceV1>(color_space_resource);
+  color_management_surface->SetGfxColorSpace(
+      zcr_color_space->GetGfxColorSpace());
+}
+
+void SetDefaultColorSpace(wl_client* client, wl_resource* resource) {
+  auto* color_management_surface =
+      GetUserDataAs<TestZcrColorManagementSurfaceV1>(resource);
+  color_management_surface->SetGfxColorSpace(gfx::ColorSpace::CreateSRGB());
+}
+
+}  // namespace
+
+const struct zcr_color_management_surface_v1_interface
+    kTestZcrColorManagementSurfaceV1Impl = {
+        &SetAlphaMode, &SetExtendedDynamicRange, &SetColorSpace,
+        &SetDefaultColorSpace, &DestroyResource};
+
+TestZcrColorManagementSurfaceV1::TestZcrColorManagementSurfaceV1(
+    wl_resource* resource)
+    : ServerObject(resource) {}
+
+TestZcrColorManagementSurfaceV1::~TestZcrColorManagementSurfaceV1() {
+  DCHECK(zcr_color_manager_);
+  zcr_color_manager_->OnZcrColorManagementSurfaceDestroyed(this);
+}
+
+gfx::ColorSpace TestZcrColorManagementSurfaceV1::GetGfxColorSpace() {
+  return gfx_color_space_;
+}
+
+void TestZcrColorManagementSurfaceV1::SetGfxColorSpace(
+    gfx::ColorSpace gfx_color_space) {
+  gfx_color_space_ = gfx_color_space;
+}
+
+void TestZcrColorManagementSurfaceV1::StoreZcrColorManager(
+    MockZcrColorManagerV1* zcr_color_manager) {
+  zcr_color_manager_ = zcr_color_manager;
+}
+
+}  // namespace wl
\ No newline at end of file
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h
new file mode 100644
index 0000000..7ce1e9c
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_management_surface.h
@@ -0,0 +1,61 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_SURFACE_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_SURFACE_H_
+
+#include <chrome-color-management-server-protocol.h>
+
+#include "base/files/scoped_file.h"
+#include "base/memory/raw_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace wl {
+
+extern const struct zcr_color_management_surface_v1_interface
+    kTestZcrColorManagementSurfaceV1Impl;
+
+class MockZcrColorManagerV1;
+
+class TestZcrColorManagementSurfaceV1 : public ServerObject {
+ public:
+  explicit TestZcrColorManagementSurfaceV1(wl_resource* resource);
+
+  TestZcrColorManagementSurfaceV1(const TestZcrColorManagementSurfaceV1&) =
+      delete;
+  TestZcrColorManagementSurfaceV1& operator=(
+      const TestZcrColorManagementSurfaceV1&) = delete;
+
+  ~TestZcrColorManagementSurfaceV1() override;
+
+  void SetAlphaMode(wl_client* client,
+                    wl_resource* resource,
+                    uint32_t alpha_mode);
+  void SetExtendedDynamicRange(wl_client* client,
+                               wl_resource* resource,
+                               uint32_t value);
+  void SetColorSpace(wl_client* client,
+                     wl_resource* resource,
+                     wl_resource* color_space_resource,
+                     uint32_t render_intent);
+  void SetDefaultColorSpace(wl_client* client, wl_resource* resource);
+  void Destroy(wl_client* client, wl_resource* resource);
+
+  gfx::ColorSpace GetGfxColorSpace();
+  void SetGfxColorSpace(gfx::ColorSpace gfx_color_space);
+  void StoreZcrColorManager(MockZcrColorManagerV1* zcr_color_manager);
+
+ private:
+  gfx::ColorSpace gfx_color_space_;
+  raw_ptr<MockZcrColorManagerV1> zcr_color_manager_ = nullptr;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_MANAGEMENT_SURFACE_H_
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.cc b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.cc
new file mode 100644
index 0000000..0fe157d
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.cc
@@ -0,0 +1,42 @@
+// 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.
+
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h"
+
+#include "ui/ozone/platform/wayland/test/mock_wayland_zcr_color_manager.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+namespace wl {
+
+namespace {
+
+void GetInformation(wl_client* client, wl_resource* resource) {
+  auto* zcr_color_space = GetUserDataAs<TestZcrColorSpaceV1>(resource);
+  DCHECK(zcr_color_space);
+}
+
+}  // namespace
+
+const struct zcr_color_space_v1_interface kTestZcrColorSpaceV1Impl = {
+    &GetInformation, &DestroyResource};
+
+TestZcrColorSpaceV1::TestZcrColorSpaceV1(wl_resource* resource)
+    : ServerObject(resource) {}
+
+TestZcrColorSpaceV1::~TestZcrColorSpaceV1() {
+  if (zcr_color_manager_) {
+    zcr_color_manager_->OnZcrColorSpaceDestroyed(this);
+  }
+}
+
+void TestZcrColorSpaceV1::SetGfxColorSpace(gfx::ColorSpace gfx_color_space) {
+  gfx_color_space_ = gfx_color_space;
+}
+
+void TestZcrColorSpaceV1::SetZcrColorManager(
+    MockZcrColorManagerV1* zcr_color_manager) {
+  zcr_color_manager_ = zcr_color_manager;
+}
+
+}  // namespace wl
\ No newline at end of file
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h
new file mode 100644
index 0000000..30e9291
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space.h
@@ -0,0 +1,50 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_SPACE_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_SPACE_H_
+
+#include <chrome-color-management-server-protocol.h>
+
+#include "base/files/scoped_file.h"
+#include "base/memory/raw_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/gfx/color_space.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace wl {
+
+extern const struct zcr_color_space_v1_interface kTestZcrColorSpaceV1Impl;
+
+class MockZcrColorManagerV1;
+
+// Manage zcr_color_space_v1_interface
+class TestZcrColorSpaceV1 : public ServerObject {
+ public:
+  explicit TestZcrColorSpaceV1(wl_resource* resource);
+
+  TestZcrColorSpaceV1(const TestZcrColorSpaceV1&) = delete;
+  TestZcrColorSpaceV1& operator=(const TestZcrColorSpaceV1&) = delete;
+
+  ~TestZcrColorSpaceV1() override;
+
+  void GetInformation(wl_client* client, wl_resource* resource);
+  void Destroy(wl_client* client, wl_resource* resource);
+
+  gfx::ColorSpace GetGfxColorSpace() const { return gfx_color_space_; }
+  void SetGfxColorSpace(gfx::ColorSpace gfx_color_space);
+
+  void SetZcrColorManager(MockZcrColorManagerV1* zcr_color_manager);
+
+ private:
+  gfx::ColorSpace gfx_color_space_;
+  raw_ptr<MockZcrColorManagerV1> zcr_color_manager_ = nullptr;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLANDS_ZCR_COLOR_SPACE_H_
\ No newline at end of file
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.cc b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.cc
new file mode 100644
index 0000000..d58cf22
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.cc
@@ -0,0 +1,14 @@
+// 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.
+
+#include "ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.h"
+
+namespace wl {
+
+TestZcrColorSpaceCreatorV1::TestZcrColorSpaceCreatorV1(wl_resource* resource)
+    : ServerObject(resource) {}
+
+TestZcrColorSpaceCreatorV1::~TestZcrColorSpaceCreatorV1() = default;
+
+}  // namespace wl
\ No newline at end of file
diff --git a/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.h b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.h
new file mode 100644
index 0000000..7d1ef0ba
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wayland_zcr_color_space_creator.h
@@ -0,0 +1,30 @@
+// 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 UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_SPACE_CREATOR_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_SPACE_CREATOR_H_
+
+#include <chrome-color-management-server-protocol.h>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+struct wl_resource;
+
+namespace wl {
+
+class TestZcrColorSpaceCreatorV1 : public ServerObject {
+ public:
+  explicit TestZcrColorSpaceCreatorV1(wl_resource* resource);
+
+  TestZcrColorSpaceCreatorV1(const TestZcrColorSpaceCreatorV1&) = delete;
+  TestZcrColorSpaceCreatorV1& operator=(const TestZcrColorSpaceCreatorV1&) =
+      delete;
+
+  ~TestZcrColorSpaceCreatorV1() override;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WAYLAND_ZCR_COLOR_SPACE_CREATOR_H_
diff --git a/ui/ozone/platform/wayland/test/wayland_test.h b/ui/ozone/platform/wayland/test/wayland_test.h
index ad27dd8..94389be 100644
--- a/ui/ozone/platform/wayland/test/wayland_test.h
+++ b/ui/ozone/platform/wayland/test/wayland_test.h
@@ -14,6 +14,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/buildflags.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine.h"
 #include "ui/ozone/common/features.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
@@ -116,7 +117,7 @@
   std::unique_ptr<WaylandWindow> window_;
   gfx::AcceleratedWidget widget_ = gfx::kNullAcceleratedWidget;
   std::vector<base::test::FeatureRef> enabled_features_{
-      ui::kWaylandOverlayDelegation};
+      features::kLacrosColorManagement, ui::kWaylandOverlayDelegation};
   std::vector<base::test::FeatureRef> disabled_features_;
 
  private:
diff --git a/ui/views/corewm/tooltip_lacros.cc b/ui/views/corewm/tooltip_lacros.cc
index c050d04..35fe9cb 100644
--- a/ui/views/corewm/tooltip_lacros.cc
+++ b/ui/views/corewm/tooltip_lacros.cc
@@ -78,11 +78,8 @@
   // Add the distance between `parent_window` and its toplevel window to
   // `position_` since Ash-side server will use this position as relative to
   // wayland toplevel window.
-  // TODO(elkurin): Use WaylandWindow instead of ToplevelWindow/Popup when it's
-  // supported on ozone.
-  // TODO(elkurin): The position is wrong for some popup such as dictionary
-  // popup shown on right-clicking a word. Should use the nearest toplevel
-  // window or popup window instead of always referring to GetToplevelWindow().
+  // TODO(crbug.com/1385219): Use WaylandWindow instead of ToplevelWindow/Popup
+  // when it's supported on ozone.
   aura::Window::ConvertPointToTarget(
       parent_window_, parent_window_->GetToplevelWindow(), &position_);
   trigger_ = trigger;
diff --git a/ui/views/window/caption_button_types.h b/ui/views/window/caption_button_types.h
index 5a825b8..536f5534 100644
--- a/ui/views/window/caption_button_types.h
+++ b/ui/views/window/caption_button_types.h
@@ -24,7 +24,6 @@
   // specific to their use case (e.g. tab search caption button in the browser
   // window frame).
   CAPTION_BUTTON_ICON_CUSTOM,
-  CAPTION_BUTTON_ICON_FLOAT,
   CAPTION_BUTTON_ICON_COUNT,
 };
 
diff --git a/ui/views/window/frame_caption_button.cc b/ui/views/window/frame_caption_button.cc
index 25a6410..94e78b0a 100644
--- a/ui/views/window/frame_caption_button.cc
+++ b/ui/views/window/frame_caption_button.cc
@@ -387,8 +387,6 @@
      u"CAPTION_BUTTON_ICON_LOCATION"},
     {views::CaptionButtonIcon::CAPTION_BUTTON_ICON_MENU,
      u"CAPTION_BUTTON_ICON_MENU"},
-    {views::CaptionButtonIcon::CAPTION_BUTTON_ICON_FLOAT,
-     u"CAPTION_BUTTON_ICON_FLOAT"},
     {views::CaptionButtonIcon::CAPTION_BUTTON_ICON_ZOOM,
      u"CAPTION_BUTTON_ICON_ZOOM"},
     {views::CaptionButtonIcon::CAPTION_BUTTON_ICON_CENTER,
diff --git a/weblayer/browser/file_select_helper.cc b/weblayer/browser/file_select_helper.cc
index 4763f34..c7fd8dc0 100644
--- a/weblayer/browser/file_select_helper.cc
+++ b/weblayer/browser/file_select_helper.cc
@@ -118,6 +118,8 @@
 void FileSelectHelper::RunFileChooserEnd() {
   if (listener_)
     listener_->FileSelectionCanceled();
+
+  select_file_dialog_->ListenerDestroyed();
   select_file_dialog_.reset();
   Release();
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
index f948b858..d6dec550 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
@@ -362,11 +362,13 @@
     public void shutdown() {
         StrictModeWorkaround.apply();
         mInDestroy = true;
+
+        BrowserImplJni.get().prepareForShutdown(mNativeBrowser);
+
         for (Object tab : getTabs()) {
             destroyTabImpl((TabImpl) tab);
         }
         mBrowserFragmentImpl.shutdown();
-
         BrowserImplJni.get().deleteBrowser(mNativeBrowser);
 
         mVisibleSecurityStateObservers.clear();