diff --git a/DEPS b/DEPS
index 1383c019..c072f231 100644
--- a/DEPS
+++ b/DEPS
@@ -300,7 +300,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': '84caf3486b60526bb5e4b681554ac161b1f2146f',
+  'skia_revision': 'fd2d32a6282569004008fd9d1633c2dc48b0dac4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -351,7 +351,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'e1a4e081aa57b3e044c7f30c3118cb6015e397d6',
+  'freetype_revision': '416d4c25f1e15d2494d373982a511928f635e705',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -387,7 +387,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': 'c1c0fb8f95c2e733fd55967cf1426351c445854b',
+  'devtools_frontend_revision': '8822c5165ca15f0a3378db6e55c433cee75a3394',
   # 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.
@@ -427,7 +427,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': 'ac4b5f2bd2df26a2c17b59f9b3279b6aa03ffef8',
+  'dawn_revision': '074ddbc10f503bb621459de9e647c804b5a2de19',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -451,7 +451,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'ff412edde1370a77b5109b1ca899ab63c94a6639',
+  'nearby_revision': 'b698c00b5db7fc1bf8e15c24ae5a640251952084',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -803,7 +803,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '39bdf43ae3f7043e2708e887051eeb2c1316e937',
+    '761f59cca813520136f9eb680123235e26600f55',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1195,7 +1195,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' + '@' + 'f990189313f6c748658a87ddeb016351ebb15763',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '6343cc4f4ef081162bb229aa947f157d9e2bff79',
       'condition': 'checkout_chromeos',
   },
 
@@ -1227,13 +1227,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'a1e578320b09a600894b6b11bc4e7d5f31627c6c',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '42515353c9edfe0ef0b7318fe81b59a530ba3d3c',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '0e8789221cac48397ea71f36acb9d23548bb68e2',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'ee938f364b24b8ed6ec335deffc617253593812b',
     'condition': 'checkout_src_internal',
   },
 
@@ -1692,7 +1692,7 @@
     Var('chromium_git') + '/openscreen' + '@' + '3c35814555c25564487cba6d7ea9cc44191e107d',
 
   'src/third_party/openxr/src': {
-    'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
+    'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '58a00cf85c39ad5ec4dc43a769624e420c06179a',
     'condition': 'checkout_openxr',
   },
 
@@ -1700,7 +1700,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd9d0730f824491aeae712e41d43cf348272eb55e',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '287c9bfb7d7107806ee430453f2e8db09fe9ed8a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1882,7 +1882,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'db412d7f763a7598a79f6044518b23506827f58e',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6ac65a373b810a95f5a9afce513bca6f8eaeb104',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + 'a79bc6ee47446865a229e69d835ddcd0b9d39c8e',
@@ -1975,7 +1975,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@f00b1dc3d6110a3b8bff7a51834a5f5426216555',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@e77bdeb901c402c9d1a8d784210caee582d22e56',
     'condition': 'checkout_src_internal',
   },
 
@@ -2005,7 +2005,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'VjrS96hE95CfVTQlXK21BZwwtk6SBz7yPYqCWwpEAmkC',
+        'version': 'lFX7CVqVW_xUU-AP0INwDvinhdEas2EdQPrRPdYXAEIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2016,7 +2016,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'JWnJece1g2Y9kaCFW3udsuS3QNa18vcoRZqpc0Y_ueUC',
+        'version': 'vubTMXYIJr9B1CVfkOfQ7GwNv9AHPLPhJOjpvXNoTqIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3944,7 +3944,7 @@
   # Dependencies from src_internal
   'src/chromeos/assistant/internal': {
       'url': Var('chrome_git') + '/chrome/assistant.git' + '@' +
-        '128a20b1e80a95b33d229722a6e3717cb3cd7c60',
+        '08d44936de7762bc0a928cab6345f9ef9964641a',
       'condition': 'checkout_src_internal and checkout_chromeos',
     },
 
@@ -3961,7 +3961,7 @@
   'src/ui/gl/resources/angle-metal': {
     'packages': [{
        'package': 'chromium/gpu/angle-metal-shader-libraries',
-       'version': 'ixOfHKV8hd24AUIOOdBb8_wyTBlppezjgOOIaGdkQqoC',
+       'version': 'QHFpoeeUkDciaMpIq9hBoGtzqOwN4mszmqhtqpN6NUUC',
     }],
     'dep_type': 'cipd',
     'condition': 'checkout_mac or checkout_ios',
@@ -5616,8 +5616,6 @@
   'src/third_party/devtools-frontend-internal',
   'src/third_party/openscreen/src',
   'src/third_party/vulkan-deps',
-  # src-internal has its own DEPS file to pull additional internal repos
-  'src-internal',
   # clank has its own DEPS file, does not need to be in trybot_analyze_config
   # since the roller does not run tests.
   'src/clank',
diff --git a/ash/capture_mode/capture_mode_behavior.cc b/ash/capture_mode/capture_mode_behavior.cc
index 58a7515..396234b 100644
--- a/ash/capture_mode/capture_mode_behavior.cc
+++ b/ash/capture_mode/capture_mode_behavior.cc
@@ -12,12 +12,15 @@
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_metrics.h"
 #include "ash/capture_mode/capture_mode_types.h"
+#include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/game_capture_bar_view.h"
 #include "ash/capture_mode/normal_capture_bar_view.h"
 #include "ash/constants/ash_features.h"
 #include "ash/projector/projector_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/wm/mru_window_tracker.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
@@ -28,17 +31,15 @@
 
 namespace {
 
-// Full size of capture mode bar view, the width of which will be
-// adjusted based on the current active behavior.
-constexpr gfx::Size kFullBarSize{376, 64};
+// Width of the full capture bar, which includes all the elements of the normal
+// capture bar.
+constexpr int kFullCaptureBarWidth = 376;
 
-// Size of the game capture bar.
-constexpr gfx::Size kGameCaptureBarSize{260, 64};
+// Width of the game capture bar.
+constexpr int kGameCaptureBarWidth = 260;
 
-// Distance from the bottom of the capture bar to the bottom of the display, top
-// of the hotseat or top of the shelf depending on the shelf alignment or
-// hotseat visibility.
-constexpr int kDistanceFromShelfOrHotseatTopDp = 16;
+// Height of the capture bar.
+constexpr int kCaptureBarHeight = 64;
 
 // Returns the current configs before been overwritten by the client-initiated
 // capture mode session
@@ -141,7 +142,7 @@
 
  protected:
   int GetCaptureBarWidth() const override {
-    return kFullBarSize.width() - capture_mode::kButtonSize.width() -
+    return kFullCaptureBarWidth - capture_mode::kButtonSize.width() -
            capture_mode::kSpaceBetweenCaptureModeTypeButtons;
   }
 
@@ -194,9 +195,14 @@
   }
 
  protected:
-  int GetCaptureBarWidth() const override {
-    return kGameCaptureBarSize.width();
+  gfx::Rect GetBarAnchorBoundsInScreen(aura::Window* root) const override {
+    const aura::Window* selected_window =
+        Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)[0];
+    CHECK(selected_window);
+    return selected_window->GetBoundsInScreen();
   }
+
+  int GetCaptureBarWidth() const override { return kGameCaptureBarWidth; }
 };
 
 }  // namespace
@@ -322,10 +328,21 @@
 }
 
 gfx::Rect CaptureModeBehavior::GetCaptureBarBounds(aura::Window* root) const {
-  DCHECK(root);
+  auto bounds = GetBarAnchorBoundsInScreen(root);
+  const int bar_y = bounds.bottom() - capture_mode::kCaptureBarBottomPadding -
+                    kCaptureBarHeight;
+  bounds.ClampToCenteredSize(
+      gfx::Size(GetCaptureBarWidth(), kCaptureBarHeight));
+  bounds.set_y(bar_y);
+  return bounds;
+}
 
+gfx::Rect CaptureModeBehavior::GetBarAnchorBoundsInScreen(
+    aura::Window* root) const {
+  CHECK(root);
   auto bounds = root->GetBoundsInScreen();
-  int bar_y = bounds.bottom();
+  int new_bottom = bounds.bottom();
+
   Shelf* shelf = Shelf::ForWindow(root);
   if (shelf->IsHorizontalAlignment()) {
     // Get the widget which has the shelf icons. This is the hotseat widget if
@@ -336,19 +353,14 @@
     views::Widget* shelf_widget =
         hotseat_extended ? static_cast<views::Widget*>(shelf->hotseat_widget())
                          : static_cast<views::Widget*>(shelf->shelf_widget());
-    bar_y = shelf_widget->GetWindowBoundsInScreen().y();
+    new_bottom = shelf_widget->GetWindowBoundsInScreen().y();
   }
-
-  gfx::Size bar_size = kFullBarSize;
-  bar_size.set_width(GetCaptureBarWidth());
-  bar_y -= (kDistanceFromShelfOrHotseatTopDp + bar_size.height());
-  bounds.ClampToCenteredSize(bar_size);
-  bounds.set_y(bar_y);
+  bounds.set_height(new_bottom - bounds.y());
   return bounds;
 }
 
 int CaptureModeBehavior::GetCaptureBarWidth() const {
-  return kFullBarSize.width();
+  return kFullCaptureBarWidth;
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_behavior.h b/ash/capture_mode/capture_mode_behavior.h
index d62b00a..b35ec8f 100644
--- a/ash/capture_mode/capture_mode_behavior.h
+++ b/ash/capture_mode/capture_mode_behavior.h
@@ -103,8 +103,15 @@
  protected:
   explicit CaptureModeBehavior(const CaptureModeSessionConfigs& configs);
 
-  // Called by `GetCaptureBarBounds` to adjust the width of the bar on different
-  // types of behavior.
+  // Returns the anchor bounds of the bar in screen coordinates, which depends
+  // on the anchor window of the bar. The anchor window can be the given `root`
+  // window or the selected window depending on the actual type of the behavior.
+  // And we will exclude the shelf/hotseat if the bar is anchored to the root
+  // window when necessary.
+  virtual gfx::Rect GetBarAnchorBoundsInScreen(aura::Window* root) const;
+
+  // Called by `GetCaptureBarBounds` to adjust the width of the bar on the
+  // actual type of the behavior.
   virtual int GetCaptureBarWidth() const;
 
   // Capture mode session configs to be used for the current capture mode
diff --git a/ash/capture_mode/capture_mode_constants.h b/ash/capture_mode/capture_mode_constants.h
index 3f5252d6..3e1eee3 100644
--- a/ash/capture_mode/capture_mode_constants.h
+++ b/ash/capture_mode/capture_mode_constants.h
@@ -122,6 +122,11 @@
 constexpr float kOuterHightlightBorderThickness =
     1.5 * views::kHighlightBorderThickness;
 
+// Distance from the bottom of the capture bar to the bottom of the anchor
+// bounds of the bar. See `GetBarAnchorBoundsInScreen` for more details of the
+// bar's anchor bounds.
+constexpr int kCaptureBarBottomPadding = 16;
+
 }  // namespace ash::capture_mode
 
 #endif  // ASH_CAPTURE_MODE_CAPTURE_MODE_CONSTANTS_H_
diff --git a/ash/capture_mode/capture_mode_game_dashboard_unittests.cc b/ash/capture_mode/capture_mode_game_dashboard_unittests.cc
index 7e7447e..37def1f 100644
--- a/ash/capture_mode/capture_mode_game_dashboard_unittests.cc
+++ b/ash/capture_mode/capture_mode_game_dashboard_unittests.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/capture_mode/capture_mode_constants.h"
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_metrics.h"
 #include "ash/capture_mode/capture_mode_session.h"
@@ -23,6 +24,8 @@
       delete;
   ~GameDashboardCaptureModeTest() override = default;
 
+  aura::Window* window() const { return window_.get(); }
+
   // AshTestBase:
   void SetUp() override {
     base::SysInfo::SetChromeOSVersionInfoForTest(
@@ -98,4 +101,19 @@
   EXPECT_TRUE(GetCloseButton());
 }
 
+TEST_F(GameDashboardCaptureModeTest, CaptureBarPosition) {
+  StartGameCaptureModeSession();
+  views::Widget* bar_widget = GetCaptureModeBarWidget();
+  ASSERT_TRUE(bar_widget);
+
+  const gfx::Rect window_bounds = window()->GetBoundsInScreen();
+  const gfx::Rect bar_bounds = bar_widget->GetWindowBoundsInScreen();
+  // Checks that the game capture bar is inside the window. And centered above a
+  // constant distance from the bottom of the window.
+  EXPECT_TRUE(window_bounds.Contains(bar_bounds));
+  EXPECT_EQ(bar_bounds.CenterPoint().x(), window_bounds.CenterPoint().x());
+  EXPECT_EQ(bar_bounds.bottom() + capture_mode::kCaptureBarBottomPadding,
+            window_bounds.bottom());
+}
+
 }  // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 4df2c5a59..ef6be23 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1772,7 +1772,7 @@
 // Requires jelly-colors flag to also be enabled.
 BASE_FEATURE(kPrintManagementJelly,
              "PrintManagementJelly",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables the new OS Printer Settings UI.
 BASE_FEATURE(kPrinterSettingsRevamp,
@@ -1954,7 +1954,7 @@
 // jelly-colors flag to also be enabled.
 BASE_FEATURE(kScanningAppJelly,
              "ScanningAppJelly",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables screensaver customized running time.
 BASE_FEATURE(kScreenSaverDuration,
diff --git a/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.cc b/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.cc
index 2c4921ca..da3270fa 100644
--- a/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.cc
+++ b/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.cc
@@ -42,5 +42,19 @@
   return additional_data_packet_encrypted_bytes_;
 }
 
+bool FakeFastPairDataEncryptor::VerifyEncryptedAdditionalData(
+    const std::array<uint8_t, kHmacVerifyLenBytes> hmacSha256First8Bytes,
+    std::array<uint8_t, kNonceSizeBytes> nonce,
+    const std::vector<uint8_t>& encrypted_additional_data) {
+  return verify_;
+}
+
+std::vector<uint8_t>
+FakeFastPairDataEncryptor::EncryptAdditionalDataWithSecretKey(
+    std::array<uint8_t, kNonceSizeBytes> nonce,
+    const std::vector<uint8_t>& additional_data) {
+  return encrypted_additional_data_;
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.h b/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.h
index 12493e4f..92d25c8 100644
--- a/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.h
+++ b/ash/quick_pair/fast_pair_handshake/fake_fast_pair_data_encryptor.h
@@ -34,11 +34,17 @@
     passkey_ = std::move(passkey);
   }
 
-  void additional_encrypted_bytes(
-      std::vector<uint8_t> additional_encrypted_bytes) {
+  void additional_data_packet_encrypted_bytes(
+      std::vector<uint8_t> additional_data_packet_encrypted_bytes) {
     additional_data_packet_encrypted_bytes_ =
-        std::move(additional_encrypted_bytes);
+        std::move(additional_data_packet_encrypted_bytes);
   }
+  void encrypted_additional_data(
+      std::vector<uint8_t> encrypted_additional_data) {
+    encrypted_additional_data_ = std::move(encrypted_additional_data);
+  }
+
+  void verify_additional_data(bool verify) { verify_ = verify; }
 
   // FastPairDataEncryptor:
   const std::array<uint8_t, kBlockSizeBytes> EncryptBytes(
@@ -55,6 +61,13 @@
   std::vector<uint8_t> CreateAdditionalDataPacket(
       std::array<uint8_t, kNonceSizeBytes> nonce,
       const std::vector<uint8_t>& additional_data) override;
+  bool VerifyEncryptedAdditionalData(
+      const std::array<uint8_t, kHmacVerifyLenBytes> hmacSha256First8Bytes,
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& encrypted_additional_data) override;
+  std::vector<uint8_t> EncryptAdditionalDataWithSecretKey(
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& additional_data) override;
 
  private:
   absl::optional<std::array<uint8_t, 64>> public_key_ = absl::nullopt;
@@ -62,6 +75,8 @@
   std::vector<uint8_t> additional_data_packet_encrypted_bytes_ = {};
   absl::optional<DecryptedResponse> response_ = absl::nullopt;
   absl::optional<DecryptedPasskey> passkey_ = absl::nullopt;
+  std::vector<uint8_t> encrypted_additional_data_ = {};
+  bool verify_ = false;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h
index 41f53b4..f2af020 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h
@@ -17,6 +17,7 @@
 
 inline constexpr int kBlockSizeBytes = 16;
 constexpr int kNonceSizeBytes = 8;
+constexpr uint8_t kHmacVerifyLenBytes = 8;
 
 namespace ash {
 namespace quick_pair {
@@ -52,6 +53,19 @@
       std::array<uint8_t, kNonceSizeBytes> nonce,
       const std::vector<uint8_t>& additional_data) = 0;
 
+  // Verifies the authenticity of `encrypted_additional_data` by generating an
+  // HMAC SHA-256 hash and comparing it to `hmacSha256`.
+  virtual bool VerifyEncryptedAdditionalData(
+      const std::array<uint8_t, kHmacVerifyLenBytes> hmacSha256First8Bytes,
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& encrypted_additional_data) = 0;
+
+  // Encrypts `additional_data` according to the Fast Pair spec:
+  // https://developers.google.com/nearby/fast-pair/specifications/characteristics#AdditionalData
+  virtual std::vector<uint8_t> EncryptAdditionalDataWithSecretKey(
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& additional_data) = 0;
+
   virtual ~FastPairDataEncryptor() = default;
 };
 
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.cc
index aea4af68..0dbb312 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.cc
@@ -213,8 +213,8 @@
     std::array<uint8_t, kNonceSizeBytes> nonce,
     const std::vector<uint8_t>& additional_data) {
   const std::vector<uint8_t> encrypted_additional_data =
-      fast_pair_encryption::EncryptAdditionalData(secret_key_, nonce,
-                                                  additional_data);
+      EncryptAdditionalDataWithSecretKey(nonce, additional_data);
+
   const std::array<uint8_t, fast_pair_encryption::kHmacSizeBytes> hmac =
       fast_pair_encryption::GenerateHmacSha256(secret_key_, nonce,
                                                encrypted_additional_data);
@@ -238,5 +238,29 @@
   return additional_data_packet;
 }
 
+bool FastPairDataEncryptorImpl::VerifyEncryptedAdditionalData(
+    const std::array<uint8_t, kHmacVerifyLenBytes> hmacSha256First8Bytes,
+    std::array<uint8_t, kNonceSizeBytes> nonce,
+    const std::vector<uint8_t>& encrypted_additional_data) {
+  const std::array<uint8_t, fast_pair_encryption::kHmacSizeBytes>
+      hmac_calculated = fast_pair_encryption::GenerateHmacSha256(
+          secret_key_, nonce, encrypted_additional_data);
+  CHECK(hmac_calculated.size() >= kHmacVerifyLenBytes);
+  for (size_t i = 0; i < kHmacVerifyLenBytes; i++) {
+    if (hmacSha256First8Bytes[i] != hmac_calculated[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::vector<uint8_t>
+FastPairDataEncryptorImpl::EncryptAdditionalDataWithSecretKey(
+    std::array<uint8_t, kNonceSizeBytes> nonce,
+    const std::vector<uint8_t>& additional_data) {
+  return fast_pair_encryption::EncryptAdditionalData(secret_key_, nonce,
+                                                     additional_data);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h
index 5d44a48..54205b8 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl.h
@@ -78,6 +78,14 @@
   std::vector<uint8_t> CreateAdditionalDataPacket(
       std::array<uint8_t, kNonceSizeBytes> nonce,
       const std::vector<uint8_t>& additional_data) override;
+  bool VerifyEncryptedAdditionalData(
+      const std::array<uint8_t, kHmacVerifyLenBytes> hmacSha256First8Bytes,
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& encrypted_additional_data) override;
+  std::vector<uint8_t> EncryptAdditionalDataWithSecretKey(
+      std::array<uint8_t, kNonceSizeBytes> nonce,
+      const std::vector<uint8_t>& additional_data) override;
+
   ~FastPairDataEncryptorImpl() override;
 
  protected:
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl_unittest.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl_unittest.cc
index 6f25ccd..4a56169 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl_unittest.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor_impl_unittest.cc
@@ -415,6 +415,70 @@
   }
 }
 
+TEST_P(FastPairDataEncryptorImplTest, VerifyEncryptedAdditionalData_Success) {
+  // Values from Fast Pair Spec successful test:
+  // https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#hmac-sha256
+  std::vector<uint8_t> encrypted_additional_data{
+      0xEE, 0x4A, 0x24, 0x83, 0x73, 0x80, 0x52, 0xE4, 0x4E,
+      0x9B, 0x2A, 0x14, 0x5E, 0x5D, 0xDF, 0xAA, 0x44, 0xB9,
+      0xE5, 0x53, 0x6A, 0xF4, 0x38, 0xE1, 0xE5, 0xC6};
+
+  std::array<uint8_t, kPrivateKeyByteSize> secret_key = {
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
+
+  std::array<uint8_t, kNonceSizeBytes> nonce = {0x00, 0x01, 0x02, 0x03,
+                                                0x04, 0x05, 0x06, 0x07};
+
+  std::array<uint8_t, kHmacVerifyLenBytes> expected = {0x55, 0xEC, 0x5E, 0x60,
+                                                       0x55, 0xAF, 0x6E, 0x92};
+
+  // Set up
+  std::vector<uint8_t> secret_key_vec(secret_key.data(),
+                                      secret_key.data() + secret_key.size());
+  SuccessfulSetUp(secret_key_vec);
+
+  // Test only if pairing protocol is Subsequent, which occurs in
+  // SuccessfulSetUp() when GetParam() == 0, so that the device's account key is
+  // used as the secret key in `data_encryptor_`.
+  if (!GetParam()) {
+    EXPECT_TRUE(data_encryptor_->VerifyEncryptedAdditionalData(
+        expected, nonce, encrypted_additional_data));
+  }
+}
+
+TEST_P(FastPairDataEncryptorImplTest, VerifyEncryptedAdditionalData_Failure) {
+  // Values from Fast Pair Spec successful test:
+  // https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#hmac-sha256
+  std::vector<uint8_t> encrypted_additional_data{
+      0xEE, 0x4A, 0x24, 0x83, 0x73, 0x80, 0x52, 0xE4, 0x4E,
+      0x9B, 0x2A, 0x14, 0x5E, 0x5D, 0xDF, 0xAA, 0x44, 0xB9,
+      0xE5, 0x53, 0x6A, 0xF4, 0x38, 0xE1, 0xE5, 0xC6};
+
+  std::array<uint8_t, kPrivateKeyByteSize> secret_key = {
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
+
+  std::array<uint8_t, kNonceSizeBytes> nonce = {0x00, 0x01, 0x02, 0x03,
+                                                0x04, 0x05, 0x06, 0x07};
+
+  std::array<uint8_t, kHmacVerifyLenBytes> expected = {0x00, 0x01, 0x02, 0x03,
+                                                       0x04, 0x05, 0x06, 0x07};
+
+  // Set up
+  std::vector<uint8_t> secret_key_vec(secret_key.data(),
+                                      secret_key.data() + secret_key.size());
+  SuccessfulSetUp(secret_key_vec);
+
+  // Test only if pairing protocol is Subsequent, which occurs in
+  // SuccessfulSetUp() when GetParam() == 0, so that the device's account key is
+  // used as the secret key in `data_encryptor_`.
+  if (!GetParam()) {
+    EXPECT_FALSE(data_encryptor_->VerifyEncryptedAdditionalData(
+        expected, nonce, encrypted_additional_data));
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(FastPairDataEncryptorImplTest,
                          FastPairDataEncryptorImplTest,
                          testing::Bool());
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index df7f857..89c60e2a 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -205,9 +205,6 @@
   void SetUp() override {
     AshTestBase::SetUp();
     observer_ = std::make_unique<TestShelfObserver>(GetPrimaryShelf());
-    shelf_view_test_ = std::make_unique<ShelfViewTestAPI>(
-        GetPrimaryShelf()->GetShelfViewForTesting());
-    shelf_view_test_->SetAnimationDuration(base::Milliseconds(1));
   }
 
   void TearDown() override {
@@ -217,11 +214,8 @@
 
   TestShelfObserver* observer() { return observer_.get(); }
 
-  ShelfViewTestAPI* shelf_view_test() { return shelf_view_test_.get(); }
-
  private:
   std::unique_ptr<TestShelfObserver> observer_;
-  std::unique_ptr<ShelfViewTestAPI> shelf_view_test_;
 };
 
 // A ShelfItemDelegate that tracks selections and reports a custom action.
@@ -275,43 +269,35 @@
 };
 
 TEST_F(ShelfObserverIconTest, AddRemove) {
+  SetShelfAnimationDuration(base::Milliseconds(1));
+
   ShelfItem item;
   item.id = ShelfID("foo");
   item.type = TYPE_APP;
   EXPECT_FALSE(observer()->icon_positions_changed());
   const int shelf_item_index = ShelfModel::Get()->Add(
       item, std::make_unique<TestShelfItemDelegate>(item.id));
-  shelf_view_test()->RunMessageLoopUntilAnimationsDone();
+  WaitForShelfAnimation();
   EXPECT_TRUE(observer()->icon_positions_changed());
   observer()->Reset();
 
   EXPECT_FALSE(observer()->icon_positions_changed());
   ShelfModel::Get()->RemoveItemAt(shelf_item_index);
-  shelf_view_test()->RunMessageLoopUntilAnimationsDone();
+  WaitForShelfAnimation();
   EXPECT_TRUE(observer()->icon_positions_changed());
   observer()->Reset();
 }
 
-// Flaky on linux-chromeos-rel: https://crbug.com/1444582
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_AddRemoveWithMultipleDisplays \
-  DISABLED_AddRemoveWithMultipleDisplays
-#else
-#define MAYBE_AddRemoveWithMultipleDisplays AddRemoveWithMultipleDisplays
-#endif
 // Make sure creating/deleting an window on one displays notifies a
 // shelf on external display as well as one on primary.
-TEST_F(ShelfObserverIconTest, MAYBE_AddRemoveWithMultipleDisplays) {
-  ui::ScopedAnimationDurationScaleMode animation_scale(
-      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-
+TEST_F(ShelfObserverIconTest, AddRemoveWithMultipleDisplays) {
   UpdateDisplay("500x400,500x400");
+  SetShelfAnimationDuration(base::Milliseconds(1));
+
   observer()->Reset();
 
-  Shelf* second_shelf = Shelf::ForWindow(Shell::GetAllRootWindows()[1]);
-  TestShelfObserver second_observer(second_shelf);
-  ShelfViewTestAPI second_shelf_test_api(
-      second_shelf->GetShelfViewForTesting());
+  TestShelfObserver second_observer(
+      Shelf::ForWindow(Shell::GetAllRootWindows()[1]));
 
   ShelfItem item;
   item.id = ShelfID("foo");
@@ -322,8 +308,7 @@
   // Add item and wait for all animations to finish.
   const int shelf_item_index = ShelfModel::Get()->Add(
       item, std::make_unique<TestShelfItemDelegate>(item.id));
-  shelf_view_test()->RunMessageLoopUntilAnimationsDone();
-  second_shelf_test_api.RunMessageLoopUntilAnimationsDone();
+  WaitForShelfAnimation();
 
   EXPECT_TRUE(observer()->icon_positions_changed());
   EXPECT_TRUE(second_observer.icon_positions_changed());
@@ -336,8 +321,7 @@
 
   // Remove the item, and wait for all the animations to complete.
   ShelfModel::Get()->RemoveItemAt(shelf_item_index);
-  shelf_view_test()->RunMessageLoopUntilAnimationsDone();
-  second_shelf_test_api.RunMessageLoopUntilAnimationsDone();
+  WaitForShelfAnimation();
 
   EXPECT_TRUE(observer()->icon_positions_changed());
   EXPECT_TRUE(second_observer.icon_positions_changed());
diff --git a/ash/system/brightness/display_detailed_view_pixeltest.cc b/ash/system/brightness/display_detailed_view_pixeltest.cc
index 146db82..1232fc5 100644
--- a/ash/system/brightness/display_detailed_view_pixeltest.cc
+++ b/ash/system/brightness/display_detailed_view_pixeltest.cc
@@ -48,7 +48,7 @@
 
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "qs_display_detailed_view",
-      /*revision_number=*/2, detailed_view));
+      /*revision_number=*/3, detailed_view));
 }
 
 }  // namespace ash
diff --git a/ash/system/unified/feature_tile.cc b/ash/system/unified/feature_tile.cc
index 6e9a340a..a6336d3 100644
--- a/ash/system/unified/feature_tile.cc
+++ b/ash/system/unified/feature_tile.cc
@@ -16,6 +16,7 @@
 #include "ui/base/models/image_model.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/text_constants.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/background.h"
@@ -26,6 +27,7 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/flex_layout_view.h"
+#include "ui/views/layout/layout_types.h"
 #include "ui/views/view_class_properties.h"
 
 using views::FlexLayout;
@@ -154,11 +156,15 @@
                                       ? views::LayoutOrientation::kHorizontal
                                       : views::LayoutOrientation::kVertical);
   title_container->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
-  title_container->SetCrossAxisAlignment(views::LayoutAlignment::kStart);
+  title_container->SetCrossAxisAlignment(
+      is_compact ? views::LayoutAlignment::kStart
+                 : views::LayoutAlignment::kStretch);
   title_container->SetPreferredSize(is_compact ? kCompactTitleContainerSize
                                                : kTitlesContainerSize);
 
   label_ = title_container->AddChildView(std::make_unique<views::Label>());
+  label_->SetHorizontalAlignment(is_compact ? gfx::ALIGN_CENTER
+                                            : gfx::ALIGN_LEFT);
   label_->SetAutoColorReadabilityEnabled(false);
   label_->SetFontList(ash::TypographyProvider::Get()->ResolveTypographyToken(
       ash::TypographyToken::kCrosButton2));
@@ -168,12 +174,14 @@
     // TODO(b/259459827): verify multi-line text is rendering correctly, not
     // clipping and center aligned.
     label_->SetMultiLine(true);
+    label_->SetMaxLines(2);  // Elide after 2 lines.
     label_->SetLineHeight(kCompactTitleLineHeight);
     label_->SetFontList(ash::TypographyProvider::Get()->ResolveTypographyToken(
         ash::TypographyToken::kCrosAnnotation2));
   } else {
     sub_label_ =
         title_container->AddChildView(std::make_unique<views::Label>());
+    sub_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     sub_label_->SetAutoColorReadabilityEnabled(false);
     sub_label_->SetFontList(
         ash::TypographyProvider::Get()->ResolveTypographyToken(
diff --git a/ash/system/unified/feature_tile_pixeltest.cc b/ash/system/unified/feature_tile_pixeltest.cc
index a9ebe338..57490c5b 100644
--- a/ash/system/unified/feature_tile_pixeltest.cc
+++ b/ash/system/unified/feature_tile_pixeltest.cc
@@ -91,6 +91,13 @@
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "toggled",
       /*revision_number=*/0, widget_.get()));
+
+  // Test eliding.
+  tile->SetLabel(u"A very very long label");
+  tile->SetSubLabel(u"A very very long label");
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "elided",
+      /*revision_number=*/0, widget_.get()));
 }
 
 TEST_F(FeatureTilePixelTest, CompactTile) {
@@ -117,6 +124,12 @@
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "toggled",
       /*revision_number=*/0, widget_.get()));
+
+  // Test eliding.
+  tile->SetLabel(u"A very very long label");
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "elided",
+      /*revision_number=*/0, widget_.get()));
 }
 
 }  // namespace
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index 11304fe..6783e3f 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -563,6 +563,13 @@
       OverviewEndAction::kTests, type);
 }
 
+void AshTestBase::SetShelfAnimationDuration(base::TimeDelta duration) {
+  for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
+    ShelfViewTestAPI(root_window_controller->shelf()->GetShelfViewForTesting())
+        .SetAnimationDuration(duration);
+  }
+}
+
 void AshTestBase::WaitForShelfAnimation() {
   for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
     ShelfViewTestAPI(root_window_controller->shelf()->GetShelfViewForTesting())
diff --git a/ash/test/ash_test_base.h b/ash/test/ash_test_base.h
index 6043c002..985b9837 100644
--- a/ash/test/ash_test_base.h
+++ b/ash/test/ash_test_base.h
@@ -236,6 +236,9 @@
   bool ExitOverview(
       OverviewEnterExitType type = OverviewEnterExitType::kNormal);
 
+  // Sets shelf animation duration for all displays.
+  void SetShelfAnimationDuration(base::TimeDelta duration);
+
   // Waits for shelf animation in all displays.
   void WaitForShelfAnimation();
 
diff --git a/ash/user_education/user_education_help_bubble_controller.cc b/ash/user_education/user_education_help_bubble_controller.cc
index 711bf78..d2663b52 100644
--- a/ash/user_education/user_education_help_bubble_controller.cc
+++ b/ash/user_education/user_education_help_bubble_controller.cc
@@ -44,7 +44,8 @@
     HelpBubbleId help_bubble_id,
     user_education::HelpBubbleParams help_bubble_params,
     ui::ElementIdentifier element_id,
-    ui::ElementContext element_context) {
+    ui::ElementContext element_context,
+    base::OnceClosure close_callback) {
   // Prohibit showing multiple help bubbles concurrently.
   if (help_bubble_) {
     return false;
@@ -67,17 +68,19 @@
   }
 
   // Subscribe to be notified when the `help_bubble_` closes. Once closed, free
-  // `help_bubble_` related memory.
+  // `help_bubble_` related memory and run the provided `close_callback`.
   help_bubble_close_subscription_ =
       help_bubble_->AddOnCloseCallback(base::BindOnce(
           [](UserEducationHelpBubbleController* self,
+             base::OnceClosure close_callback,
              user_education::HelpBubble* help_bubble) {
             CHECK_EQ(self->help_bubble_.get(), help_bubble);
             self->help_bubble_.reset();
             self->help_bubble_close_subscription_ =
                 base::CallbackListSubscription();
+            std::move(close_callback).Run();
           },
-          base::Unretained(this)));
+          base::Unretained(this), std::move(close_callback)));
 
   // Indicate success.
   return true;
diff --git a/ash/user_education/user_education_help_bubble_controller.h b/ash/user_education/user_education_help_bubble_controller.h
index b777e2c..97fe3132 100644
--- a/ash/user_education/user_education_help_bubble_controller.h
+++ b/ash/user_education/user_education_help_bubble_controller.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "base/callback_list.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 
 namespace ui {
@@ -45,12 +46,14 @@
   // specified `help_bubble_params` for the tracked element associated with the
   // specified `element_id` in the specified `element_context`. A help bubble
   // may not be created under certain circumstances, e.g. if there is already a
-  // help bubble showing or if there is an ongoing tutorial running.
+  // help bubble showing or if there is an ongoing tutorial running. Iff a help
+  // bubble was created, `close_callback` is run when the help bubble is closed.
   // NOTE: Currently only the primary user profile is supported.
   bool CreateHelpBubble(HelpBubbleId help_bubble_id,
                         user_education::HelpBubbleParams help_bubble_params,
                         ui::ElementIdentifier element_id,
-                        ui::ElementContext element_context);
+                        ui::ElementContext element_context,
+                        base::OnceClosure close_callback = base::DoNothing());
 
  private:
   // The delegate owned by the `UserEducationController` which facilitates
diff --git a/ash/user_education/user_education_help_bubble_controller_unittest.cc b/ash/user_education/user_education_help_bubble_controller_unittest.cc
index a8922d6..7d95e906 100644
--- a/ash/user_education/user_education_help_bubble_controller_unittest.cc
+++ b/ash/user_education/user_education_help_bubble_controller_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/user_education/mock_user_education_delegate.h"
 #include "ash/user_education/user_education_ash_test_base.h"
 #include "ash/user_education/user_education_types.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/user_education/common/help_bubble.h"
 #include "components/user_education/common/help_bubble_params.h"
@@ -152,10 +153,16 @@
 
   // The `controller()` should indicate to the caller success when attempting to
   // create a new `help_bubble` since the previous `help_bubble` was closed.
+  // Note that this time a `close_callback` is provided.
+  base::MockOnceClosure close_callback;
   EXPECT_TRUE(controller()->CreateHelpBubble(
-      HelpBubbleId::kTest, HelpBubbleParams(), kElementId, element_context));
+      HelpBubbleId::kTest, HelpBubbleParams(), kElementId, element_context,
+      close_callback.Get()));
   Mock::VerifyAndClearExpectations(user_education_delegate());
 
+  // Expect that closing the `help_bubble` will invoke `close_callback`.
+  EXPECT_CALL(close_callback, Run);
+
   // Close the `help_bubble`.
   ASSERT_TRUE(help_bubble);
   help_bubble->Close();
diff --git a/ash/webui/scanning/resources/scanning_shared_css.html b/ash/webui/scanning/resources/scanning_shared_css.html
index 3d3651f..089b989 100644
--- a/ash/webui/scanning/resources/scanning_shared_css.html
+++ b/ash/webui/scanning/resources/scanning_shared_css.html
@@ -11,7 +11,7 @@
     }
 
     :host-context(body.jelly-enabled) {
-      --scanning-progress-bar: var(--cros-sys-progress);
+      --scanning-progress-bar: var(--cros-sys-on_progress_container);
       --scanning-progress-bar-track:  var(--cros-sys-progress_container);
       --scanning-scrollbar-rgb: var(--google-grey-600-rgb);
       --scanning-scrollbar: var(--cros-sys-scrollbar);
diff --git a/ash/wm/desks/templates/saved_desk_animations.cc b/ash/wm/desks/templates/saved_desk_animations.cc
index 38f3baa..7b95fdd 100644
--- a/ash/wm/desks/templates/saved_desk_animations.cc
+++ b/ash/wm/desks/templates/saved_desk_animations.cc
@@ -25,9 +25,6 @@
       .SetPreemptionStrategy(
           ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
       .Once()
-      .SetDuration(base::TimeDelta())
-      .SetOpacity(layer, 0.0f)
-      .Then()
       .SetDuration(base::Milliseconds(duration_in_ms))
       .SetOpacity(layer, 1.0f, gfx::Tween::LINEAR);
 }
@@ -64,6 +61,10 @@
     layer->SetOpacity(1.f);
     return;
   }
+  // If the layer is already at, or animating to opaque, then we don't animate.
+  if (layer->GetTargetOpacity() == 1.0f) {
+    return;
+  }
 
   FadeInLayer(layer, kFadeInDurationMs);
 }
diff --git a/ash/wm/desks/templates/saved_desk_save_desk_button.cc b/ash/wm/desks/templates/saved_desk_save_desk_button.cc
index 65a7c52..fccb50d7 100644
--- a/ash/wm/desks/templates/saved_desk_save_desk_button.cc
+++ b/ash/wm/desks/templates/saved_desk_save_desk_button.cc
@@ -15,6 +15,7 @@
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/controls/focus_ring.h"
 #include "ui/views/highlight_border.h"
+#include "ui/views/view.h"
 #include "ui/views/view_utils.h"
 
 namespace ash {
@@ -45,8 +46,15 @@
       chromeos::features::IsJellyrollEnabled()
           ? views::HighlightBorder::Type::kHighlightBorderNoShadow
           : views::HighlightBorder::Type::kHighlightBorder2));
-  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kSaveDeskCornerRadius});
-  layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
+
+  View* background_view = AddChildView(std::make_unique<views::View>());
+  background_view->SetPaintToLayer();
+
+  background_view->layer()->SetRoundedCornerRadius(
+      gfx::RoundedCornersF{kSaveDeskCornerRadius});
+  background_view->layer()->SetBackgroundBlur(
+      ColorProvider::kBackgroundBlurSigma);
+  background_view->layer()->SetFillsBoundsOpaquely(false);
 }
 
 SavedDeskSaveDeskButton::~SavedDeskSaveDeskButton() = default;
diff --git a/ash/wm/window_cycle/window_cycle_view.cc b/ash/wm/window_cycle/window_cycle_view.cc
index 23ecbf4c..aff965f 100644
--- a/ash/wm/window_cycle/window_cycle_view.cc
+++ b/ash/wm/window_cycle/window_cycle_view.cc
@@ -59,6 +59,11 @@
 // Shield horizontal inset.
 constexpr int kBackgroundHorizontalInsetDp = 8;
 
+// Shield horizontal inset when Jellyroll is enabled.
+// TODO(b/282753971): Rename this to `kBackgroundHorizontalInsetDp` and remove
+// `kBackgroundHorizontalInsetDp` above.
+constexpr int kBackgroundHorizontalInsetDpCrOSNext = 40;
+
 // Vertical padding between the alt-tab bandshield and the window previews.
 constexpr int kInsideBorderVerticalPaddingDp = 60;
 
@@ -673,7 +678,9 @@
 
 int WindowCycleView::CalculateMaxWidth() const {
   return root_window_->GetBoundsInScreen().size().width() -
-         2 * kBackgroundHorizontalInsetDp;
+         2 * (chromeos::features::IsJellyrollEnabled()
+                  ? kBackgroundHorizontalInsetDpCrOSNext
+                  : kBackgroundHorizontalInsetDp);
 }
 
 gfx::Rect WindowCycleView::GetContentContainerBounds() const {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index cbbde9e..5334be8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -4092,6 +4092,7 @@
       "android/java/src/org/chromium/base/EarlyTraceEvent.java",
       "android/java/src/org/chromium/base/EventLog.java",
       "android/java/src/org/chromium/base/FeatureList.java",
+      "android/java/src/org/chromium/base/FeatureMap.java",
       "android/java/src/org/chromium/base/Features.java",
       "android/java/src/org/chromium/base/FieldTrialList.java",
       "android/java/src/org/chromium/base/FileUtils.java",
@@ -4246,6 +4247,7 @@
       "android/java/src/org/chromium/base/EarlyTraceEvent.java",
       "android/java/src/org/chromium/base/EventLog.java",
       "android/java/src/org/chromium/base/FeatureList.java",
+      "android/java/src/org/chromium/base/FeatureMap.java",
       "android/java/src/org/chromium/base/Features.java",
       "android/java/src/org/chromium/base/FieldTrialList.java",
       "android/java/src/org/chromium/base/FileUtils.java",
diff --git a/base/android/feature_map.cc b/base/android/feature_map.cc
index 129ff299..a4aa03f 100644
--- a/base/android/feature_map.cc
+++ b/base/android/feature_map.cc
@@ -4,11 +4,16 @@
 
 #include "base/android/feature_map.h"
 
+#include <jni.h>
 #include <stddef.h>
 
 #include <memory>
 #include <string>
 
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/base_jni_headers/FeatureMap_jni.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/notreached.h"
 
 namespace base::android {
@@ -36,4 +41,89 @@
                         << feature_name;
 }
 
+static jboolean JNI_FeatureMap_IsEnabled(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const android::JavaParamRef<jstring>& jfeature_name) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  return base::FeatureList::IsEnabled(*feature);
+}
+
+static ScopedJavaLocalRef<jstring> JNI_FeatureMap_GetFieldTrialParamByFeature(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const JavaParamRef<jstring>& jfeature_name,
+    const JavaParamRef<jstring>& jparam_name) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
+  const std::string& param_value =
+      base::GetFieldTrialParamValueByFeature(*feature, param_name);
+  return ConvertUTF8ToJavaString(env, param_value);
+}
+
+static jint JNI_FeatureMap_GetFieldTrialParamByFeatureAsInt(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const JavaParamRef<jstring>& jfeature_name,
+    const JavaParamRef<jstring>& jparam_name,
+    const jint jdefault_value) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
+  return base::GetFieldTrialParamByFeatureAsInt(*feature, param_name,
+                                                jdefault_value);
+}
+
+static jdouble JNI_FeatureMap_GetFieldTrialParamByFeatureAsDouble(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const JavaParamRef<jstring>& jfeature_name,
+    const JavaParamRef<jstring>& jparam_name,
+    const jdouble jdefault_value) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
+  return base::GetFieldTrialParamByFeatureAsDouble(*feature, param_name,
+                                                   jdefault_value);
+}
+
+static jboolean JNI_FeatureMap_GetFieldTrialParamByFeatureAsBoolean(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const JavaParamRef<jstring>& jfeature_name,
+    const JavaParamRef<jstring>& jparam_name,
+    const jboolean jdefault_value) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
+  return base::GetFieldTrialParamByFeatureAsBool(*feature, param_name,
+                                                 jdefault_value);
+}
+
+static ScopedJavaLocalRef<jobjectArray>
+JNI_FeatureMap_GetFlattedFieldTrialParamsForFeature(
+    JNIEnv* env,
+    jlong jfeature_map,
+    const JavaParamRef<jstring>& jfeature_name) {
+  FeatureMap* feature_map = reinterpret_cast<FeatureMap*>(jfeature_map);
+  base::FieldTrialParams params;
+  std::vector<std::string> keys_and_values;
+  const base::Feature* feature = feature_map->FindFeatureExposedToJava(
+      StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
+  if (feature && base::GetFieldTrialParamsByFeature(*feature, &params)) {
+    for (const auto& param_pair : params) {
+      keys_and_values.push_back(param_pair.first);
+      keys_and_values.push_back(param_pair.second);
+    }
+  }
+  return base::android::ToJavaArrayOfStrings(env, keys_and_values);
+}
+
 }  // namespace base::android
diff --git a/base/android/java/src/org/chromium/base/FeatureMap.java b/base/android/java/src/org/chromium/base/FeatureMap.java
new file mode 100644
index 0000000..297dfe7
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/FeatureMap.java
@@ -0,0 +1,171 @@
+// Copyright 2023 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.base;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.build.annotations.MainDex;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Java accessor for state of feature flags and their field trial parameters.
+ *
+ * This class provides methods to access values of feature flags listed in a native feature list
+ * and to access their field trial parameters.
+ *
+ * This class needs to be derived for each native feature list (such as a component's feature list)
+ * and the derived class must implement the abstract {@link #getNativeMap()} by calling a JNI method
+ * to get the pointer to the base::android::FeatureMap. The derived class will provide Java code
+ * access to the list of base::Features passed to the base::android::FeatureMap.
+ */
+@JNINamespace("base::android")
+@MainDex
+public abstract class FeatureMap {
+    private long mNativeMapPtr;
+    protected FeatureMap() {}
+
+    /**
+     * Should return the native pointer to the specific base::FeatureMap for the component/layer.
+     */
+    protected abstract long getNativeMap();
+
+    /**
+     * Returns whether the specified feature is enabled or not.
+     *
+     * Calling this has the side effect of bucketing this client, which may cause an experiment to
+     * be marked as active.
+     *
+     * Should be called only after native is loaded. If {@link FeatureList#isInitialized()} returns
+     * true, this method is safe to call.  In tests, this will return any values set through
+     * {@link FeatureList#setTestFeatures(Map)}, even before native is loaded.
+     *
+     * @param featureName The name of the feature to query.
+     * @return Whether the feature is enabled or not.
+     */
+    public boolean isEnabled(String featureName) {
+        Boolean testValue = FeatureList.getTestValueForFeature(featureName);
+        if (testValue != null) return testValue;
+        ensureNativeMapInit();
+        return FeatureMapJni.get().isEnabled(mNativeMapPtr, featureName);
+    }
+
+    /**
+     * Returns a field trial param for the specified feature.
+     *
+     * @param featureName The name of the feature to retrieve a param for.
+     * @param paramName The name of the param for which to get as an integer.
+     * @return The parameter value as a String. The string is empty if the feature does not exist or
+     *   the specified parameter does not exist.
+     */
+    public String getFieldTrialParamByFeature(String featureName, String paramName) {
+        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
+        if (testValue != null) return testValue;
+        if (FeatureList.hasTestFeatures()) return "";
+        ensureNativeMapInit();
+        return FeatureMapJni.get().getFieldTrialParamByFeature(
+                mNativeMapPtr, featureName, paramName);
+    }
+
+    /**
+     * Returns a field trial param as a boolean for the specified feature.
+     *
+     * @param featureName The name of the feature to retrieve a param for.
+     * @param paramName The name of the param for which to get as an integer.
+     * @param defaultValue The boolean value to use if the param is not available.
+     * @return The parameter value as a boolean. Default value if the feature does not exist or the
+     *         specified parameter does not exist or its string value is neither "true" nor "false".
+     */
+    public boolean getFieldTrialParamByFeatureAsBoolean(
+            String featureName, String paramName, boolean defaultValue) {
+        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
+        if (testValue != null) return Boolean.valueOf(testValue);
+        if (FeatureList.hasTestFeatures()) return defaultValue;
+        ensureNativeMapInit();
+        return FeatureMapJni.get().getFieldTrialParamByFeatureAsBoolean(
+                mNativeMapPtr, featureName, paramName, defaultValue);
+    }
+
+    /**
+     * Returns a field trial param as an int for the specified feature.
+     *
+     * @param featureName The name of the feature to retrieve a param for.
+     * @param paramName The name of the param for which to get as an integer.
+     * @param defaultValue The integer value to use if the param is not available.
+     * @return The parameter value as an int. Default value if the feature does not exist or the
+     *         specified parameter does not exist or its string value does not represent an int.
+     */
+    public int getFieldTrialParamByFeatureAsInt(
+            String featureName, String paramName, int defaultValue) {
+        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
+        if (testValue != null) return Integer.valueOf(testValue);
+        if (FeatureList.hasTestFeatures()) return defaultValue;
+        ensureNativeMapInit();
+        return FeatureMapJni.get().getFieldTrialParamByFeatureAsInt(
+                mNativeMapPtr, featureName, paramName, defaultValue);
+    }
+
+    /**
+     * Returns a field trial param as a double for the specified feature.
+     *
+     * @param featureName The name of the feature to retrieve a param for.
+     * @param paramName The name of the param for which to get as an integer.
+     * @param defaultValue The double value to use if the param is not available.
+     * @return The parameter value as a double. Default value if the feature does not exist or the
+     *         specified parameter does not exist or its string value does not represent a double.
+     */
+    public double getFieldTrialParamByFeatureAsDouble(
+            String featureName, String paramName, double defaultValue) {
+        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
+        if (testValue != null) return Double.valueOf(testValue);
+        if (FeatureList.hasTestFeatures()) return defaultValue;
+        ensureNativeMapInit();
+        return FeatureMapJni.get().getFieldTrialParamByFeatureAsDouble(
+                mNativeMapPtr, featureName, paramName, defaultValue);
+    }
+
+    /**
+     * Returns all the field trial parameters for the specified feature.
+     */
+    public Map<String, String> getFieldTrialParamsForFeature(String featureName) {
+        Map<String, String> testValues =
+                FeatureList.getTestValuesForAllFieldTrialParamsForFeature(featureName);
+        if (testValues != null) return testValues;
+        if (FeatureList.hasTestFeatures()) return Collections.emptyMap();
+
+        ensureNativeMapInit();
+        Map<String, String> result = new HashMap<>();
+        String[] flattenedParams = FeatureMapJni.get().getFlattedFieldTrialParamsForFeature(
+                mNativeMapPtr, featureName);
+        for (int i = 0; i < flattenedParams.length; i += 2) {
+            result.put(flattenedParams[i], flattenedParams[i + 1]);
+        }
+        return result;
+    }
+
+    private void ensureNativeMapInit() {
+        assert FeatureList.isNativeInitialized();
+
+        if (mNativeMapPtr == 0) {
+            mNativeMapPtr = getNativeMap();
+            assert mNativeMapPtr != 0;
+        }
+    }
+
+    @NativeMethods
+    interface Natives {
+        boolean isEnabled(long featureMap, String featureName);
+        String getFieldTrialParamByFeature(long featureMap, String featureName, String paramName);
+        int getFieldTrialParamByFeatureAsInt(
+                long featureMap, String featureName, String paramName, int defaultValue);
+        double getFieldTrialParamByFeatureAsDouble(
+                long featureMap, String featureName, String paramName, double defaultValue);
+        boolean getFieldTrialParamByFeatureAsBoolean(
+                long featureMap, String featureName, String paramName, boolean defaultValue);
+        String[] getFlattedFieldTrialParamsForFeature(long featureMap, String featureName);
+    }
+}
diff --git a/base/files/file_win.cc b/base/files/file_win.cc
index a7187780..71c4a25 100644
--- a/base/files/file_win.cc
+++ b/base/files/file_win.cc
@@ -359,8 +359,6 @@
     case ERROR_DISK_CORRUPT:  // The disk structure is corrupted and unreadable.
       return FILE_ERROR_IO;
     default:
-      UmaHistogramSparse("PlatformFile.UnknownErrors.Windows",
-                         static_cast<int>(last_error));
       // This function should only be called for errors.
       DCHECK_NE(static_cast<DWORD>(ERROR_SUCCESS), last_error);
       return FILE_ERROR_FAILED;
diff --git a/base/i18n/number_formatting.cc b/base/i18n/number_formatting.cc
index 0a3f82d6..025a565 100644
--- a/base/i18n/number_formatting.cc
+++ b/base/i18n/number_formatting.cc
@@ -65,6 +65,12 @@
 }
 
 std::u16string FormatDouble(double number, int fractional_digits) {
+  return FormatDouble(number, fractional_digits, fractional_digits);
+}
+
+std::u16string FormatDouble(double number,
+                            int min_fractional_digits,
+                            int max_fractional_digits) {
   icu::NumberFormat* number_format =
       g_number_format_float.Get().number_format.get();
 
@@ -72,8 +78,8 @@
     // As a fallback, just return the raw number in a string.
     return ASCIIToUTF16(StringPrintf("%f", number));
   }
-  number_format->setMaximumFractionDigits(fractional_digits);
-  number_format->setMinimumFractionDigits(fractional_digits);
+  number_format->setMaximumFractionDigits(max_fractional_digits);
+  number_format->setMinimumFractionDigits(min_fractional_digits);
   icu::UnicodeString ustr;
   number_format->format(number, ustr);
 
diff --git a/base/i18n/number_formatting.h b/base/i18n/number_formatting.h
index 61b45494..f35e5b1 100644
--- a/base/i18n/number_formatting.h
+++ b/base/i18n/number_formatting.h
@@ -17,12 +17,24 @@
 // Ex: FormatNumber(1234567) => "1,234,567" in English, "1.234.567" in German
 BASE_I18N_EXPORT std::u16string FormatNumber(int64_t number);
 
-// Return a number formatted with separators in the user's locale.
+// Return a number formatted with separators in the user's locale, with
+// `fractional_digits` digits after the decimal point.
 // Ex: FormatDouble(1234567.8, 1)
 //         => "1,234,567.8" in English, "1.234.567,8" in German
 BASE_I18N_EXPORT std::u16string FormatDouble(double number,
                                              int fractional_digits);
 
+// Return a number formatted with separators in the user's locale, with up to
+// `max_fractional_digits` digits after the decimal point, and eliminating
+// trailing zeroes after `min_fractional_digits`.
+// Ex: FormatDouble(1234567.8, 0, 4)
+//         => "1,234,567.8" in English, "1.234.567,8" in German
+// Ex: FormatDouble(1234567.888888, 0, 4)
+//         => "1,234,567.8889" in English, "1.234.567,8889" in German
+BASE_I18N_EXPORT std::u16string FormatDouble(double number,
+                                             int min_fractional_digits,
+                                             int max_fractional_digits);
+
 // Return a percentage formatted with space and symbol in the user's locale.
 // Ex: FormatPercent(12) => "12%" in English, "12 %" in Romanian
 BASE_I18N_EXPORT std::u16string FormatPercent(int number);
diff --git a/base/i18n/number_formatting_unittest.cc b/base/i18n/number_formatting_unittest.cc
index da699c1..1598f8b 100644
--- a/base/i18n/number_formatting_unittest.cc
+++ b/base/i18n/number_formatting_unittest.cc
@@ -45,7 +45,7 @@
   }
 }
 
-TEST(NumberFormattingTest, FormatDouble) {
+TEST(NumberFormattingTest, FormatDoubleWithFixedFractionalDigits) {
   static const struct {
     double number;
     int frac_digits;
@@ -91,6 +91,55 @@
   }
 }
 
+TEST(NumberFormattingTest, FormatDoubleWithFractionalDigitRange) {
+  static const struct {
+    double number;
+    int min_frac_digits;
+    int max_frac_digits;
+    const char* expected_english;
+    const char* expected_german;
+  } cases[] = {
+    {0.0, 0, 0, "0", "0"},
+#if !BUILDFLAG(IS_ANDROID)
+    // Bionic can't printf negative zero correctly.
+    {-0.0, 0, 4, "-0", "-0"},
+#endif
+    {1024.2, 0, 0, "1,024", "1.024"},
+    {-1024.223, 0, 2, "-1,024.22", "-1.024,22"},
+    {std::numeric_limits<double>::max(), 0, 6,
+     "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,"
+     "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+     "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+     "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+     "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+     "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,"
+     "000",
+     "179.769.313.486.231.570.000.000.000.000.000.000.000.000.000.000.000."
+     "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+     "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+     "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+     "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+     "000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000."
+     "000"},
+    {std::numeric_limits<double>::min(), 2, 2, "0.00", "0,00"},
+    {-42.7, 0, 3, "-42.7", "-42,7"},
+  };
+
+  test::ScopedRestoreICUDefaultLocale restore_locale;
+  for (const auto& i : cases) {
+    i18n::SetICUDefaultLocale("en");
+    ResetFormattersForTesting();
+    EXPECT_EQ(i.expected_english,
+              UTF16ToUTF8(FormatDouble(i.number, i.min_frac_digits,
+                                       i.max_frac_digits)));
+    i18n::SetICUDefaultLocale("de");
+    ResetFormattersForTesting();
+    EXPECT_EQ(i.expected_german,
+              UTF16ToUTF8(FormatDouble(i.number, i.min_frac_digits,
+                                       i.max_frac_digits)));
+  }
+}
+
 TEST(NumberFormattingTest, FormatPercent) {
   static const struct {
     int64_t number;
diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc
index e1ac670..e6d84c4b 100644
--- a/base/message_loop/message_pump_win.cc
+++ b/base/message_loop/message_pump_win.cc
@@ -715,7 +715,6 @@
     if (run_state_->should_quit)
       break;
 
-    run_state_->delegate->BeforeWait();
     more_work_is_plausible |= WaitForIOCompletion(0);
     if (run_state_->should_quit)
       break;
diff --git a/base/system/sys_info.cc b/base/system/sys_info.cc
index 9d52ed32..4029a86 100644
--- a/base/system/sys_info.cc
+++ b/base/system/sys_info.cc
@@ -28,6 +28,8 @@
 // Updated Desktop default threshold to match the Android 2021 definition.
 constexpr uint64_t kLowMemoryDeviceThresholdMB = 2048;
 #endif
+
+absl::optional<uint64_t> g_amount_of_physical_memory_mb_for_testing;
 }  // namespace
 
 // static
@@ -38,17 +40,22 @@
 
 // static
 uint64_t SysInfo::AmountOfPhysicalMemory() {
+  constexpr uint64_t kMB = 1024 * 1024;
+
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableLowEndDeviceMode)) {
     // Keep using 512MB as the simulated RAM amount for when users or tests have
     // manually enabled low-end device mode. Note this value is different from
     // the threshold used for low end devices.
-    constexpr uint64_t kSimulatedMemoryForEnableLowEndDeviceMode =
-        512 * 1024 * 1024;
+    constexpr uint64_t kSimulatedMemoryForEnableLowEndDeviceMode = 512 * kMB;
     return std::min(kSimulatedMemoryForEnableLowEndDeviceMode,
                     AmountOfPhysicalMemoryImpl());
   }
 
+  if (g_amount_of_physical_memory_mb_for_testing) {
+    return g_amount_of_physical_memory_mb_for_testing.value() * kMB;
+  }
+
   return AmountOfPhysicalMemoryImpl();
 }
 
@@ -183,4 +190,17 @@
 #endif
 }
 
+// static
+absl::optional<uint64_t> SysInfo::SetAmountOfPhysicalMemoryMbForTesting(
+    const uint64_t amount_of_memory_mb) {
+  absl::optional<uint64_t> current = g_amount_of_physical_memory_mb_for_testing;
+  g_amount_of_physical_memory_mb_for_testing.emplace(amount_of_memory_mb);
+  return current;
+}
+
+// static
+void SysInfo::ClearAmountOfPhysicalMemoryMbForTesting() {
+  g_amount_of_physical_memory_mb_for_testing.reset();
+}
+
 }  // namespace base
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index 02f3006..e65b6936 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -16,6 +16,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "base/feature_list.h"
@@ -40,6 +41,10 @@
 FORWARD_DECLARE_TEST(SystemMetricsTest, ParseMeminfo);
 }
 
+namespace test {
+class ScopedAmountOfPhysicalMemoryOverride;
+}
+
 class FilePath;
 struct SystemMemoryInfoKB;
 
@@ -254,6 +259,7 @@
 #endif
 
  private:
+  friend class test::ScopedAmountOfPhysicalMemoryOverride;
   FRIEND_TEST_ALL_PREFIXES(SysInfoTest, AmountOfAvailablePhysicalMemory);
   FRIEND_TEST_ALL_PREFIXES(debug::SystemMetricsTest, ParseMeminfo);
 
@@ -268,6 +274,12 @@
   static uint64_t AmountOfAvailablePhysicalMemory(
       const SystemMemoryInfoKB& meminfo);
 #endif
+
+  // Sets the amount of physical memory in MB for testing, thus allowing tests
+  // to run irrespective of the host machine's configuration.
+  static absl::optional<uint64_t> SetAmountOfPhysicalMemoryMbForTesting(
+      uint64_t amount_of_memory_mb);
+  static void ClearAmountOfPhysicalMemoryMbForTesting();
 };
 
 }  // namespace base
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index 26c2f69..4c17a8fc 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -99,6 +99,8 @@
     "rectify_callback.h",
     "rectify_callback_internal.h",
     "repeating_test_future.h",
+    "scoped_amount_of_physical_memory_override.cc",
+    "scoped_amount_of_physical_memory_override.h",
     "scoped_command_line.cc",
     "scoped_command_line.h",
     "scoped_feature_list.cc",
diff --git a/base/test/scoped_amount_of_physical_memory_override.cc b/base/test/scoped_amount_of_physical_memory_override.cc
new file mode 100644
index 0000000..c33efc8
--- /dev/null
+++ b/base/test/scoped_amount_of_physical_memory_override.cc
@@ -0,0 +1,28 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_amount_of_physical_memory_override.h"
+
+#include "base/check_op.h"
+#include "base/system/sys_info.h"
+
+namespace base::test {
+
+ScopedAmountOfPhysicalMemoryOverride::ScopedAmountOfPhysicalMemoryOverride(
+    uint64_t amount_of_memory_mb) {
+  CHECK_GT(amount_of_memory_mb, 0u);
+  old_amount_of_physical_memory_mb_ =
+      base::SysInfo::SetAmountOfPhysicalMemoryMbForTesting(amount_of_memory_mb);
+}
+
+ScopedAmountOfPhysicalMemoryOverride::~ScopedAmountOfPhysicalMemoryOverride() {
+  if (old_amount_of_physical_memory_mb_) {
+    base::SysInfo::SetAmountOfPhysicalMemoryMbForTesting(
+        *old_amount_of_physical_memory_mb_);
+  } else {
+    base::SysInfo::ClearAmountOfPhysicalMemoryMbForTesting();
+  }
+}
+
+}  // namespace base::test
diff --git a/base/test/scoped_amount_of_physical_memory_override.h b/base/test/scoped_amount_of_physical_memory_override.h
new file mode 100644
index 0000000..93a2697
--- /dev/null
+++ b/base/test/scoped_amount_of_physical_memory_override.h
@@ -0,0 +1,37 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_SCOPED_AMOUNT_OF_PHYSICAL_MEMORY_OVERRIDE_H_
+#define BASE_TEST_SCOPED_AMOUNT_OF_PHYSICAL_MEMORY_OVERRIDE_H_
+
+#include <stdint.h>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base::test {
+
+// Sets the amount of physical memory in MB override on construction, and
+// removes it when the object goes out of scope. This class is intended to be
+// used by tests that need to override the amount of physical memory on the
+// system to validate different system conditions.
+class ScopedAmountOfPhysicalMemoryOverride {
+ public:
+  // Constructor that initializes the amount of memory override. Memory is
+  // specified in MB.
+  explicit ScopedAmountOfPhysicalMemoryOverride(uint64_t amount_of_memory_mb);
+
+  ScopedAmountOfPhysicalMemoryOverride(
+      const ScopedAmountOfPhysicalMemoryOverride&) = delete;
+  ScopedAmountOfPhysicalMemoryOverride& operator=(
+      const ScopedAmountOfPhysicalMemoryOverride&) = delete;
+
+  ~ScopedAmountOfPhysicalMemoryOverride();
+
+ private:
+  absl::optional<uint64_t> old_amount_of_physical_memory_mb_;
+};
+
+}  // namespace base::test
+
+#endif  // BASE_TEST_SCOPED_AMOUNT_OF_PHYSICAL_MEMORY_OVERRIDE_H_
diff --git a/base/values.cc b/base/values.cc
index b4625137..7bf83e1 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1184,30 +1184,6 @@
   return GetDict().FindByDottedPath(path);
 }
 
-absl::optional<int> Value::FindIntPath(StringPiece path) const {
-  return GetDict().FindIntByDottedPath(path);
-}
-
-const std::string* Value::FindStringPath(StringPiece path) const {
-  return GetDict().FindStringByDottedPath(path);
-}
-
-std::string* Value::FindStringPath(StringPiece path) {
-  return GetDict().FindStringByDottedPath(path);
-}
-
-const Value* Value::FindListPath(StringPiece path) const {
-  const Value* cur = GetDict().FindByDottedPath(path);
-  if (!cur || cur->type() != Type::LIST) {
-    return nullptr;
-  }
-  return cur;
-}
-
-Value* Value::FindListPath(StringPiece path) {
-  return const_cast<Value*>(std::as_const(*this).FindListPath(path));
-}
-
 bool operator==(const Value& lhs, const Value& rhs) {
   return lhs.data_ == rhs.data_;
 }
diff --git a/base/values.h b/base/values.h
index dd3e0347..bbc59a0 100644
--- a/base/values.h
+++ b/base/values.h
@@ -799,24 +799,6 @@
   Value* FindPath(StringPiece path);
   const Value* FindPath(StringPiece path) const;
 
-  // Convenience accessors used when the expected type of a value is known.
-  // Similar to Find<Type>Key() but accepts paths instead of keys.
-  //
-  // DEPRECATED: Use `Value::Dict::FindIntByDottedPath()`, or
-  // `Value::Dict::FindInt()` if the path only has one component, i.e. has no
-  // dots.
-  absl::optional<int> FindIntPath(StringPiece path) const;
-  // DEPRECATED: Use `Value::Dict::FindStringByDottedPath()`, or
-  // `Value::Dict::FindString()` if the path only has one component, i.e. has no
-  // dots.
-  const std::string* FindStringPath(StringPiece path) const;
-  std::string* FindStringPath(StringPiece path);
-  // DEPRECATED: Use `Value::Dict::FindListByDottedPath()`, or
-  // `Value::Dict::FindList()` if the path only has one component, i.e. has no
-  // dots.
-  Value* FindListPath(StringPiece path);
-  const Value* FindListPath(StringPiece path) const;
-
   // Note: Do not add more types. See the file-level comment above for why.
 
   // Comparison operators so that Values can easily be used with standard
diff --git a/build/config/chromecast/OWNERS b/build/config/chromecast/OWNERS
index 253037d7..8dd6352b 100644
--- a/build/config/chromecast/OWNERS
+++ b/build/config/chromecast/OWNERS
@@ -1,3 +1,2 @@
 mfoltz@chromium.org
-rwkeane@google.com
 seantopping@chromium.org
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index d93bd13f..cbf7372 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -946,7 +946,6 @@
     # sequences, ignoring the platform, when stderr is not a terminal.
     rustflags += [ "--color=always" ]
   }
-
   if (rust_abi_target != "") {
     rustflags += [ "--target=$rust_abi_target" ]
   }
@@ -957,6 +956,16 @@
   if (is_official_build) {
     rustflags += [ "-Ccodegen-units=1" ]
   }
+  if (!rust_prebuilt_stdlib) {
+    # When building against the Chromium Rust stdlib (which we compile) always
+    # abort instead of unwinding when panic occurs. In official builds, panics
+    # abort immediately (this is configured in the stdlib) to keep binary size
+    # down. So we unconditionally match behaviour in unofficial too.
+    rustflags += [
+      "-Cpanic=abort",
+      "-Zpanic_abort_tests",
+    ]
+  }
 }
 
 # The BUILDCONFIG file sets this config on targets by default, which means when
diff --git a/build/config/rust.gni b/build/config/rust.gni
index 9c56e6a1..72f7c0124 100644
--- a/build/config/rust.gni
+++ b/build/config/rust.gni
@@ -64,11 +64,6 @@
   # more work is done.
   use_goma_rust = false
 
-  # The host toolchain to use when you don't want sanitizers enabled. By default
-  # it is the regular toolchain, but when that toolchain has sanitizers, then
-  # this variable is changed to avoid them.
-  host_toolchain_no_sanitizers = host_toolchain
-
   # Force-enable `--color=always` for rustc, even when it would be disabled for
   # a platform. Mostly applicable to Windows, where new versions can handle ANSI
   # escape sequences but it's not reliable in general.
@@ -263,13 +258,20 @@
 rustc_common_args = "--crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}}"
 
 # Rust procedural macros are shared objects loaded into a prebuilt host rustc
-# binary. To build them, we obviously need to build for the host. Not only that,
-# but because the host rustc is prebuilt, it lacks the machinery to be able to
-# load shared objects built using sanitizers (ASAN etc.) For that reason, we need
-# to use a host toolchain that lacks sanitizers. This is only strictly necessary
-# for procedural macros, but we may also choose to build standalone Rust host
-# executable tools using the same toolchain, as they're likely to depend upon
-# similar dependencies (syn, quote etc.) and it saves a little build time.
-if (using_sanitizer || toolchain_disables_sanitizers) {
-  host_toolchain_no_sanitizers = "${host_toolchain}_no_sanitizers"
+# binary. To build them, we obviously need to build for the host. Not only
+# that, but because the host rustc is prebuilt, it lacks the machinery to be
+# able to load shared objects built using sanitizers (ASAN etc.). For that
+# reason, we need to use a host toolchain that lacks sanitizers. Additionally,
+# proc macros should use panic=unwind, which means they need a stdlib that is
+# compiled the same way, as is the stdlib that we ship with the compiler.
+if (toolchain_for_rust_host_build_tools) {
+  rust_macro_toolchain = current_toolchain
+} else {
+  rust_macro_toolchain = "${host_toolchain}_for_rust_host_build_tools"
 }
+
+# When this is true, a prebuilt Rust stdlib will be used. This has implications
+# such as that the panic strategy (unwind, abort) must match how the stdlib is
+# compiled, which is typically as unwind.
+rust_prebuilt_stdlib =
+    !use_chromium_rust_toolchain || toolchain_for_rust_host_build_tools
diff --git a/build/config/sanitizers/sanitizers.gni b/build/config/sanitizers/sanitizers.gni
index 446736e7..e9ac5ff4 100644
--- a/build/config/sanitizers/sanitizers.gni
+++ b/build/config/sanitizers/sanitizers.gni
@@ -122,8 +122,9 @@
 assert(!is_hwasan || (target_os == "android" && target_cpu == "arm64"),
        "HWASan only supported on Android ARM64 builds.")
 
-# Disable sanitizers for non-target toolchains.
-if (!is_a_target_toolchain || toolchain_disables_sanitizers) {
+# Disable sanitizers for non-target toolchains, and for the toolchain using
+# the prebuilt Rust stdlib which has no sanitizer support with it.
+if (!is_a_target_toolchain || toolchain_for_rust_host_build_tools) {
   is_asan = false
   is_cfi = false
   is_hwasan = false
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index ff7d345..e7a194de 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -6,6 +6,7 @@
 import("//build/config/chrome_build.gni")
 import("//build/config/clang/clang.gni")
 import("//build/config/compiler/compiler.gni")
+import("//build/config/rust.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/win/control_flow_guard.gni")
 import("//build/config/win/visual_studio_version.gni")
@@ -484,6 +485,16 @@
   if (is_component_build) {
     cflags = [ "/MD" ]
 
+    if (rust_prebuilt_stdlib) {
+      rustflags = [ "-Ctarget-feature=-crt-static" ]
+    } else {
+      # /MD specifies msvcrt.lib as the CRT library. Rust needs to agree, so
+      # we specify it explicitly. Once
+      # https://github.com/rust-lang/rust/issues/39016 is resolved we should
+      # instead tell rustc which CRT to use (static/dynamic + release/debug).
+      rustflags = [ "-Clink-arg=msvcrt.lib" ]
+    }
+
     if (use_custom_libcxx) {
       # On Windows, including libcpmt[d]/msvcprt[d] explicitly links the C++
       # standard library, which libc++ needs for exception_ptr internals.
@@ -491,6 +502,17 @@
     }
   } else {
     cflags = [ "/MT" ]
+
+    if (rust_prebuilt_stdlib) {
+      rustflags = [ "-Ctarget-feature=+crt-static" ]
+    } else {
+      # /MT specifies libcmt.lib as the CRT library. Rust needs to agree, so
+      # we specify it explicitly. Once
+      # https://github.com/rust-lang/rust/issues/39016 is resolved we should
+      # instead tell rustc which CRT to use (static/dynamic + release/debug).
+      rustflags = [ "-Clink-arg=libcmt.lib" ]
+    }
+
     if (use_custom_libcxx) {
       ldflags = [ "/DEFAULTLIB:libcpmt.lib" ]
     }
@@ -505,7 +527,8 @@
     # /MDd specifies msvcrtd.lib as the CRT library. Rust needs to agree, so
     # we specify it explicitly.
     # Once https://github.com/rust-lang/rust/issues/39016 is resolved we should
-    # instead tell rustc which CRT to use (static/dynamic + release/debug).
+    # instead tell rustc which CRT to use (static/dynamic + release/debug). We
+    # can't support prebuilt stdlib in this path until then.
     rustflags = [ "-Clink-arg=msvcrtd.lib" ]
 
     if (use_custom_libcxx) {
@@ -514,11 +537,16 @@
   } else {
     cflags = [ "/MD" ]
 
-    # /MD specifies msvcrt.lib as the CRT library. Rust needs to agree, so
-    # we specify it explicitly.
-    # Once https://github.com/rust-lang/rust/issues/39016 is resolved we should
-    # instead tell rustc which CRT to use (static/dynamic + release/debug).
-    rustflags = [ "-Clink-arg=msvcrt.lib" ]
+    if (rust_prebuilt_stdlib) {
+      rustflags = [ "-Ctarget-feature=-crt-static" ]
+    } else {
+      # /MD specifies msvcrt.lib as the CRT library. Rust needs to agree, so
+      # we specify it explicitly.
+      # Once https://github.com/rust-lang/rust/issues/39016 is resolved we
+      # should instead tell rustc which CRT to use (static/dynamic +
+      # release/debug).
+      rustflags = [ "-Clink-arg=msvcrt.lib" ]
+    }
 
     if (use_custom_libcxx) {
       ldflags = [ "/DEFAULTLIB:msvcprt.lib" ]
@@ -534,7 +562,8 @@
     # /MTd specifies libcmtd.lib as the CRT library. Rust needs to agree, so
     # we specify it explicitly.
     # Once https://github.com/rust-lang/rust/issues/39016 is resolved we should
-    # instead tell rustc which CRT to use (static/dynamic + release/debug).
+    # instead tell rustc which CRT to use (static/dynamic + release/debug). We
+    # can't support prebuilt stdlib in this path until then.
     rustflags = [ "-Clink-arg=libcmtd.lib" ]
 
     if (use_custom_libcxx) {
@@ -543,11 +572,16 @@
   } else {
     cflags = [ "/MT" ]
 
-    # /MT specifies libcmt.lib as the CRT library. Rust needs to agree, so
-    # we specify it explicitly.
-    # Once https://github.com/rust-lang/rust/issues/39016 is resolved we should
-    # instead tell rustc which CRT to use (static/dynamic + release/debug).
-    rustflags = [ "-Clink-arg=libcmt.lib" ]
+    if (rust_prebuilt_stdlib) {
+      rustflags = [ "-Ctarget-feature=+crt-static" ]
+    } else {
+      # /MT specifies libcmt.lib as the CRT library. Rust needs to agree, so
+      # we specify it explicitly.
+      # Once https://github.com/rust-lang/rust/issues/39016 is resolved we
+      # should instead tell rustc which CRT to use (static/dynamic +
+      # release/debug).
+      rustflags = [ "-Clink-arg=libcmt.lib" ]
+    }
 
     if (use_custom_libcxx) {
       ldflags = [ "/DEFAULTLIB:libcpmt.lib" ]
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 12191722..e4319691 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-12.20230515.1.1
+12.20230515.3.1
diff --git a/build/rust/BUILD.gn b/build/rust/BUILD.gn
index 4995459..3c33c0d6 100644
--- a/build/rust/BUILD.gn
+++ b/build/rust/BUILD.gn
@@ -70,3 +70,13 @@
 config("forbid_unsafe") {
   rustflags = [ "-Funsafe_code" ]
 }
+
+config("panic_immediate_abort") {
+  visibility = [ "//build/rust/std/rules:*" ]
+  if (is_official_build) {
+    rustflags = [
+      "--cfg",
+      "panic_immediate_abort",
+    ]
+  }
+}
diff --git a/build/rust/cargo_crate.gni b/build/rust/cargo_crate.gni
index 5a25ab4..6f85f6b 100644
--- a/build/rust/cargo_crate.gni
+++ b/build/rust/cargo_crate.gni
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/rust.gni")
 import("//build/rust/rust_executable.gni")
 import("//build/rust/rust_macro.gni")
 import("//build/rust/rust_static_library.gni")
@@ -296,8 +295,7 @@
     # Extra targets required to make build script work
     action("${_build_script_name}_output") {
       script = rebase_path("//build/rust/run_build_script.py")
-      build_script_target =
-          ":${_build_script_name}($host_toolchain_no_sanitizers)"
+      build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
       deps = [ build_script_target ]
 
       # The build script may be built with a different toolchain when
@@ -352,7 +350,10 @@
       }
     }
 
-    if (current_toolchain == host_toolchain_no_sanitizers) {
+    if (toolchain_for_rust_host_build_tools) {
+      # The build script is only available to be built on the host, and we use
+      # the rust_macro_toolchain for it to unblock building them while the
+      # Chromium stdlib is still being compiled.
       rust_executable(_build_script_name) {
         sources = invoker.build_sources
         crate_root = invoker.build_root
diff --git a/build/rust/rust_target.gni b/build/rust/rust_target.gni
index ba9f43a..1c85001 100644
--- a/build/rust/rust_target.gni
+++ b/build/rust/rust_target.gni
@@ -132,11 +132,7 @@
     _configs += invoker.library_configs
   }
 
-  _forward_to_host_toolchain = false
   if (invoker.target_type == "rust_proc_macro") {
-    if (current_toolchain != host_toolchain_no_sanitizers) {
-      _forward_to_host_toolchain = true
-    }
     _main_target_suffix = "${target_name}__proc_macro"
   } else {
     _main_target_suffix = "__rlib"
@@ -188,16 +184,17 @@
   # TODO(crbug.com/1256930) - verify this is correct
   assert(defined(invoker.sources), "sources must be listed")
 
-  if (_forward_to_host_toolchain) {
-    # Redirect to the host toolchain.
+  if (invoker.target_type == "rust_proc_macro" &&
+      !toolchain_for_rust_host_build_tools) {
+    # Redirect to the proc macro toolchain, which uses prebuilt stdlib libraries
+    # that are not built with panic=abort.
     group(_target_name) {
       testonly = _testonly
       if (defined(_visibility)) {
         visibility = _visibility
       }
-      public_deps = [
-        ":${_target_name}${_main_target_suffix}($host_toolchain_no_sanitizers)",
-      ]
+      public_deps =
+          [ ":${_target_name}${_main_target_suffix}($rust_macro_toolchain)" ]
     }
 
     not_needed(invoker, "*")
diff --git a/build/rust/std/BUILD.gn b/build/rust/std/BUILD.gn
index 4482166..9f95c13 100644
--- a/build/rust/std/BUILD.gn
+++ b/build/rust/std/BUILD.gn
@@ -71,12 +71,10 @@
     "unwind",
   ]
 
-  # proc_macro is special: we only run proc macros on the host, so we only want
-  # it for our host toolchain.
-  if (current_toolchain == host_toolchain_no_sanitizers) {
-    # Directs the local stdlib_for_rustc target to depend on proc_macro, and
-    # includes proc_macro in the prebuilts copied in find_stdlib. Otherwise it
-    # is not built or copied.
+  if (toolchain_for_rust_host_build_tools) {
+    # When building proc macros, include the proc_macro crate in what should be
+    # copied with find_stdlib. Otherwise it is not copied since it will be
+    # unused.
     stdlib_files += [ "proc_macro" ]
   }
 
@@ -110,8 +108,8 @@
       # //build/config/win:static_crt because Rustc does not allow us to specify
       # using the debug CRT: https://github.com/rust-lang/rust/issues/39016
       #
-      # As such, we have disabled all #[link] directives from the libc crate, and
-      # we need to add any non-CRT libs here.
+      # As such, we have disabled all #[link] directives from the libc crate,
+      # and we need to add any non-CRT libs here.
       ldflags = [ "legacy_stdio_definitions.lib" ]
     }
   }
@@ -125,9 +123,10 @@
     # the linking step of a C++ EXE that depends on Rust.
     if (is_win) {
       # These libs provide functions that are used by the stdlib. Rust crates
-      # will try to link them in with #[link] directives. However these don't get
-      # propagated to the linker if Rust isn't driving the linking (a C++ target
-      # that depends on a Rust rlib). So these need to be specified explicitly.
+      # will try to link them in with #[link] directives. However these don't
+      # get propagated to the linker if Rust isn't driving the linking (a C++
+      # target that depends on a Rust rlib). So these need to be specified
+      # explicitly.
       ldflags = [
         "advapi32.lib",
         "bcrypt.lib",
@@ -146,7 +145,7 @@
 
   sysroot_lib_subdir = "lib/rustlib/$rust_abi_target/lib"
 
-  if (use_chromium_rust_toolchain) {
+  if (!rust_prebuilt_stdlib) {
     local_rustc_sysroot = "$root_out_dir/local_rustc_sysroot"
 
     # All std targets starting with core build with our sysroot. It starts empty
@@ -338,6 +337,9 @@
 
     prebuilt_rustc_sysroot = "$root_out_dir/prebuilt_rustc_sysroot"
     copy("prebuilt_rustc_copy_to_sysroot") {
+      assert(enable_rust,
+             "Some C++ target is including Rust code even though " +
+                 "enable_rust=false")
       deps = [ ":find_stdlib" ]
       sources = get_target_outputs(":find_stdlib")
       outputs =
@@ -373,24 +375,11 @@
         this_file = "$lib_dir/lib$lib.rlib"
         ldflags += [ this_file ]
       }
-      if (is_win) {
-        # TODO(crbug.com/1434092): This should really be `libs`, however that
-        # breaks. Normally, we specify lib files with the `.lib` suffix but
-        # then when rustc links an EXE, it invokes lld-link with `.lib.lib`
-        # instead.
-        #
-        # Omitting the `.lib` suffix breaks linking as well, when clang drives
-        # the linking step of a C++ EXE that depends on Rust.
-        ldflags += _windows_lib_deps
-      }
       visibility = [ ":*" ]
     }
 
     # Use the sysroot generated by :prebuilt_rustc_copy_to_sysroot.
     group("stdlib_for_rustc") {
-      assert(
-          enable_rust,
-          "Some C++ target is including Rust code even though enable_rust=false")
       all_dependent_configs = [ ":prebuilt_stdlib_sysroot" ]
       deps = [ ":prebuilt_rustc_copy_to_sysroot" ]
     }
@@ -398,17 +387,17 @@
     # Links the Rust stdlib. Used by targets for which linking is driven by
     # C++.
     group("stdlib_for_clang") {
-      assert(
-          enable_rust,
-          "Some C++ target is including Rust code even though enable_rust=false")
       all_dependent_configs = [
         ":prebuilt_stdlib_libs",
         ":stdlib_public_dependent_libs",
       ]
-      deps = [
-        ":prebuilt_rustc_copy_to_sysroot",
-        ":remap_alloc",
-      ]
+      deps = [ ":prebuilt_rustc_copy_to_sysroot" ]
+
+      # The host builds tools toolchain supports Rust only and does not use
+      # the allocator remapping to point it to PartitionAlloc.
+      if (!toolchain_for_rust_host_build_tools) {
+        deps += [ ":remap_alloc" ]
+      }
     }
   }
 }
diff --git a/build/rust/std/gnrt_config.toml b/build/rust/std/gnrt_config.toml
index 76f9968b..676cb0b 100644
--- a/build/rust/std/gnrt_config.toml
+++ b/build/rust/std/gnrt_config.toml
@@ -41,6 +41,7 @@
 # Dependencies of profiler_builtins must have instrumentation disabled
 remove_library_configs = ['//build/config/coverage:default_coverage']
 extra_gn_deps_to_ignore = ['//build/rust/std:profiler_builtins_group']
+add_library_configs = ['//build/rust:panic_immediate_abort']
 
 [crate.libc]
 # Many flags are set by libc's build.rs based on new enough rustc but we don't
@@ -90,6 +91,7 @@
 # See https://github.com/rust-lang/rust/blob/master/library/std/build.rs
 cfg = ['backtrace_in_libstd']
 env = ['STD_ENV_ARCH=$rust_target_arch']
+add_library_configs = ['//build/rust:panic_immediate_abort']
 
 # Remove this from std. It will be depended on directly when needed.
 exclude_deps_in_gn = ['profiler_builtins']
diff --git a/build/rust/std/rules/BUILD.gn b/build/rust/std/rules/BUILD.gn
index 379809f..d9c7c415 100644
--- a/build/rust/std/rules/BUILD.gn
+++ b/build/rust/std/rules/BUILD.gn
@@ -194,7 +194,10 @@
     "//build/config/compiler:chromium_code",
     "//build/config/coverage:default_coverage",
   ]
-  library_configs += [ "//build/config/compiler:no_chromium_code" ]
+  library_configs += [
+    "//build/config/compiler:no_chromium_code",
+    "//build/rust:panic_immediate_abort",
+  ]
   executable_configs -= [ "//build/config/compiler:chromium_code" ]
   executable_configs += [ "//build/config/compiler:no_chromium_code" ]
   deps = [ "//build/rust/std:std_build_deps" ]
@@ -729,7 +732,10 @@
   cargo_pkg_name = "std"
   cargo_pkg_description = "The Rust Standard Library"
   library_configs -= [ "//build/config/compiler:chromium_code" ]
-  library_configs += [ "//build/config/compiler:no_chromium_code" ]
+  library_configs += [
+    "//build/config/compiler:no_chromium_code",
+    "//build/rust:panic_immediate_abort",
+  ]
   executable_configs -= [ "//build/config/compiler:chromium_code" ]
   executable_configs += [ "//build/config/compiler:no_chromium_code" ]
   deps = [
diff --git a/build/toolchain/apple/toolchain.gni b/build/toolchain/apple/toolchain.gni
index 70d7c03..34e951b 100644
--- a/build/toolchain/apple/toolchain.gni
+++ b/build/toolchain/apple/toolchain.gni
@@ -87,8 +87,8 @@
 # Shared toolchain definition. Invocations should set current_os to set the
 # build args in this definition. This is titled "single_apple_toolchain"
 # because it makes exactly one toolchain. Callers will normally want to
-# invoke instead "apple_toolchain" which may make an additional toolchain
-# without sanitizers.
+# invoke instead "apple_toolchain" which makes an additional toolchain for
+# Rust targets that are build-time artificts such as proc macros.
 template("single_apple_toolchain") {
   toolchain(target_name) {
     # When invoking this toolchain not as the default one, these args will be
@@ -104,10 +104,6 @@
       # ensure that it's always the same, regardless of the values that may be
       # set on those toolchains.
       host_toolchain = host_toolchain
-
-      # Similarly for the host toolchain which can be used to make .dylibs
-      # that will successfully load into prebuilt tools.
-      host_toolchain_no_sanitizers = host_toolchain_no_sanitizers
     }
 
     # When the invoker has explicitly overridden use_goma or cc_wrapper in the
@@ -792,8 +788,9 @@
   }
 }
 
-# Makes a single Apple toolchain, or possibly two if we need a
-# sanitizer-free equivalent.
+# Makes an Apple toolchain for the target, and an equivalent toolchain with the
+# prebuilt Rust stdlib for building proc macros (and other for-build-use
+# artifacts).
 template("apple_toolchain") {
   single_apple_toolchain(target_name) {
     assert(defined(invoker.toolchain_args),
@@ -809,9 +806,10 @@
     # toolchains, but presubmit checks require that we explicitly exclude them
   }
 
-  if (using_sanitizer) {
-    # Make an additional toolchain with no sanitizers.
-    single_apple_toolchain("${target_name}_no_sanitizers") {
+  if (enable_rust && current_toolchain == default_toolchain) {
+    # Make an additional toolchain which uses the prebuilt stdlib shipped with
+    # rustc.
+    single_apple_toolchain("${target_name}_for_rust_host_build_tools") {
       assert(defined(invoker.toolchain_args),
              "Toolchains must declare toolchain_args")
       forward_variables_from(invoker,
@@ -824,7 +822,9 @@
       toolchain_args = {
         # Populate toolchain args from the invoker.
         forward_variables_from(invoker.toolchain_args, "*")
-        toolchain_disables_sanitizers = true
+        toolchain_for_rust_host_build_tools = true
+        is_debug = false
+        is_component_build = false
       }
     }
   }
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index ad99431..516f4ad 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -108,9 +108,9 @@
 #      all shared libraries and executables as they are built. The pre-stripped
 #      artifacts will be put in lib.unstripped/ and exe.unstripped/.
 #
-# Callers will normally want to invoke "gcc_toolchain" instead, which makes
-# a toolchain just like this but may additionally create an extra toolchain
-# without sanitizers for host-side tools.
+# Callers will normally want to invoke "gcc_toolchain" instead, which makes an
+# additional toolchain for Rust targets that are build-time artificts such as
+# proc macros.
 template("single_gcc_toolchain") {
   toolchain(target_name) {
     assert(defined(invoker.ar), "gcc_toolchain() must specify a \"ar\" value")
@@ -149,10 +149,6 @@
       # set on those toolchains.
       host_toolchain = host_toolchain
 
-      # The same applies to the toolchain we use to build Rust procedural
-      # macros, which is probably the same but might have sanitizers disabled.
-      host_toolchain_no_sanitizers = host_toolchain_no_sanitizers
-
       if (!defined(invoker_toolchain_args.v8_current_cpu)) {
         v8_current_cpu = invoker_toolchain_args.current_cpu
       }
@@ -812,8 +808,9 @@
   }
 }
 
-# Makes a single GCC toolchain, or possibly two if we need
-# an equivalent toolchain without sanitizers.
+# Makes a GCC toolchain for the target, and an equivalent toolchain with the
+# prebuilt Rust stdlib for building proc macros (and other for-build-use
+# artifacts).
 template("gcc_toolchain") {
   single_gcc_toolchain(target_name) {
     assert(defined(invoker.toolchain_args),
@@ -829,9 +826,10 @@
     # toolchains, but presubmit checks require that we explicitly exclude them
   }
 
-  if (using_sanitizer) {
-    # Make an additional toolchain with no sanitizers.
-    single_gcc_toolchain("${target_name}_no_sanitizers") {
+  if (enable_rust && current_toolchain == default_toolchain) {
+    # Make an additional toolchain which uses the prebuilt stdlib shipped with
+    # rustc.
+    single_gcc_toolchain("${target_name}_for_rust_host_build_tools") {
       assert(defined(invoker.toolchain_args),
              "Toolchains must declare toolchain_args")
       forward_variables_from(invoker,
@@ -844,7 +842,12 @@
       toolchain_args = {
         # Populate toolchain args from the invoker.
         forward_variables_from(invoker.toolchain_args, "*")
-        toolchain_disables_sanitizers = true
+        toolchain_for_rust_host_build_tools = true
+
+        # The host build tools are static release builds to make the Chromium
+        # build faster.
+        is_debug = false
+        is_component_build = false
       }
     }
   }
diff --git a/build/toolchain/toolchain.gni b/build/toolchain/toolchain.gni
index d32d7d0..42973fa8 100644
--- a/build/toolchain/toolchain.gni
+++ b/build/toolchain/toolchain.gni
@@ -20,10 +20,10 @@
   # Used for binary size analysis.
   generate_linker_map = is_android && is_official_build
 
-  # Whether this toolchain should avoid building any sanitizer support
-  # because it's a host toolchain where we aim to make shared objects that may
-  # be loaded by prebuilt binaries without sanitizer support.
-  toolchain_disables_sanitizers = false
+  # Whether this toolchain is to be used for building host tools that are
+  # consumed during the build process. That includes proc macros and Cargo build
+  # scripts.
+  toolchain_for_rust_host_build_tools = false
 }
 
 if (generate_linker_map) {
@@ -83,17 +83,12 @@
       rebase_path("//build/toolchain/win/tool_wrapper.py", root_build_dir)
 
   stamp_command = "cmd /c type nul > \"{{output}}\""
-  copy_command =
-      "\"$python_path\" $_tool_wrapper_path recursive-mirror {{source}} {{output}}"
+  copy_command = "\"$python_path\" $_tool_wrapper_path recursive-mirror {{source}} {{output}}"
 } else {
   stamp_command = "touch {{output}}"
   copy_command = "ln -f {{source}} {{output}} 2>/dev/null || (rm -rf {{output}} && cp -af {{source}} {{output}})"
 }
 
-if (!defined(toolchain_disables_sanitizers)) {
-  toolchain_disables_sanitizers = false
-}
-
 # This variable is true if the current toolchain is one of the target
 # toolchains, i.e. a toolchain which is being used to build the main Chrome
 # binary. This generally means "not the host toolchain", but in the case where
diff --git a/build/toolchain/win/toolchain.gni b/build/toolchain/win/toolchain.gni
index 968a4a2..9b4027bc 100644
--- a/build/toolchain/win/toolchain.gni
+++ b/build/toolchain/win/toolchain.gni
@@ -28,8 +28,8 @@
 _clang_bin_path = rebase_path("$clang_base_path/bin", root_build_dir)
 
 # Makes a single MSVC toolchain. Callers should normally instead invoke
-# "msvc_toolchain" which might make an additional toolchain available
-# without sanitizers if required.
+# "msvc_toolchain" which makes an additional toolchain for Rust targets that
+# are build-time artificts such as proc macros.
 #
 # Parameters:
 #   environment: File name of environment file.
@@ -47,9 +47,6 @@
 
       # This value needs to be passed through unchanged.
       host_toolchain = host_toolchain
-
-      # This value needs to be passed through unchanged.
-      host_toolchain_no_sanitizers = host_toolchain_no_sanitizers
     }
 
     if (defined(toolchain_args.is_clang)) {
@@ -566,8 +563,9 @@
   }
 }
 
-# Makes a single MSVC toolchain, or possibly two if we
-# need an additional toolchain without sanitizers enabled.
+# Makes an MSVC toolchain for the target, and an equivalent toolchain with the
+# prebuilt Rust stdlib for building proc macros (and other for-build-use
+# artifacts).
 template("msvc_toolchain") {
   single_msvc_toolchain(target_name) {
     assert(defined(invoker.toolchain_args),
@@ -583,9 +581,10 @@
     # toolchains, but presubmit checks require that we explicitly exclude them
   }
 
-  if (using_sanitizer) {
-    # Make an additional toolchain with no sanitizers.
-    single_msvc_toolchain("${target_name}_no_sanitizers") {
+  if (enable_rust && current_toolchain == default_toolchain) {
+    # Make an additional toolchain which uses the prebuilt stdlib shipped with
+    # rustc.
+    single_msvc_toolchain("${target_name}_for_rust_host_build_tools") {
       assert(defined(invoker.toolchain_args),
              "Toolchains must declare toolchain_args")
       forward_variables_from(invoker,
@@ -598,7 +597,12 @@
       toolchain_args = {
         # Populate toolchain args from the invoker.
         forward_variables_from(invoker.toolchain_args, "*")
-        toolchain_disables_sanitizers = true
+        toolchain_for_rust_host_build_tools = true
+
+        # The host build tools are static release builds to make the Chromium
+        # build faster.
+        is_debug = false
+        is_component_build = false
       }
     }
   }
diff --git a/cc/animation/scroll_timeline.h b/cc/animation/scroll_timeline.h
index 0a3c0de..a245b55 100644
--- a/cc/animation/scroll_timeline.h
+++ b/cc/animation/scroll_timeline.h
@@ -36,6 +36,7 @@
   };
 
   struct ScrollOffsets {
+    ScrollOffsets() = default;
     ScrollOffsets(double start_offset, double end_offset) {
       start = start_offset;
       end = end_offset;
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index d03fb1c..7d0f3fa 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1103,6 +1103,8 @@
       "//components/webapk/android/libs/client:java",
       "//components/webapk/android/libs/common:java",
       "//components/webapps/browser/android:java",
+      "//components/webauthn/android:java",
+      "//components/webauthn/android:test_support_java",
       "//content/public/android:content_java",
       "//content/public/common:common_java",
       "//content/public/test/android:content_java_test_support",
@@ -1110,6 +1112,7 @@
       "//mojo/public/java:system_java",
       "//mojo/public/mojom/base:base_java",
       "//net/android:net_java",
+      "//services/device/public/java:device_feature_list_java",
       "//services/device/public/mojom:mojom_java",
       "//services/media_session/public/cpp/android:media_session_java",
       "//services/media_session/public/mojom:mojom_java",
@@ -1149,6 +1152,7 @@
       "//url:gurl_junit_tests",
       "//url:origin_java",
       "//url/mojom:url_mojom_gurl_java",
+      "//url/mojom:url_mojom_origin_java",
     ]
 
     deps += chrome_junit_test_java_deps
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 8699e45..6b0f8f57 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -317,4 +317,13 @@
   "junit/src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateRequest.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateResponse.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManException.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetRequest.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetResponse.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredential.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialManager.java",
+  "junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialOption.java",
+  "junit/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestRobolectricTest.java",
 ]
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index e7ba6e3..d0396f3d 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -549,7 +549,6 @@
   "javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java",
   "javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java",
-  "javatests/src/org/chromium/chrome/browser/webauth/Fido2ApiTestHelper.java",
   "javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java",
   "javatests/src/org/chromium/chrome/browser/webid/MDocProviderTest.java",
   "javatests/src/org/chromium/chrome/browser/webshare/WebShareTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
index e1d4646..2062f46e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
@@ -15,6 +15,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
 import android.net.Uri;
 import android.os.Build;
@@ -252,6 +253,9 @@
             return null;
         }
 
+        // Difficult to necessarily get the right theme from the other app. Use null instead.
+        Theme theme = null;
+
         int nameId = res.getIdentifier(RESOURCE_NAME, RESOURCE_STRING_TYPE, webApkPackageName);
         int shortNameId =
                 res.getIdentifier(RESOURCE_SHORT_NAME, RESOURCE_STRING_TYPE, webApkPackageName);
@@ -284,7 +288,7 @@
                 IntentUtils.safeGetInt(bundle, WebApkMetaDataKeys.DEFAULT_BACKGROUND_COLOR_ID, 0);
         int defaultBackgroundColor = (defaultBackgroundColorId == 0)
                 ? SplashLayout.getDefaultBackgroundColor(appContext)
-                : ApiCompatibilityUtils.getColor(res, defaultBackgroundColorId);
+                : res.getColor(defaultBackgroundColorId, theme);
 
         int shellApkVersion =
                 IntentUtils.safeGetInt(bundle, WebApkMetaDataKeys.SHELL_APK_VERSION, 0);
diff --git a/chrome/android/javatests/DEPS b/chrome/android/javatests/DEPS
index 2f89b85..1ce93a6 100644
--- a/chrome/android/javatests/DEPS
+++ b/chrome/android/javatests/DEPS
@@ -75,9 +75,6 @@
   "AuthenticatorTest\.java": [
     "+content/public/android/java/src/org/chromium/content_public/common/ContentSwitches.java",
   ],
-  "Fido2ApiTestHelper.java": [
-    "+content/public/android/java/src/org/chromium/content/browser/ClientDataJsonImpl.java",
-  ],
   "Fido2CredentialRequestTest.java": [
     "+content/public/android/java/src/org/chromium/content/browser/ClientDataJsonImpl.java",
   ],
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
index e97de35..13dae927 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
@@ -65,6 +65,7 @@
 import org.chromium.components.webauthn.AuthenticatorImpl;
 import org.chromium.components.webauthn.Fido2Api;
 import org.chromium.components.webauthn.Fido2ApiCallHelper;
+import org.chromium.components.webauthn.Fido2ApiTestHelper;
 import org.chromium.components.webauthn.Fido2CredentialRequest;
 import org.chromium.components.webauthn.InternalAuthenticator;
 import org.chromium.components.webauthn.InternalAuthenticatorJni;
@@ -129,7 +130,7 @@
     private PublicKeyCredentialRequestOptions mRequestOptions;
     private static final String GOOGLE_PLAY_SERVICES_PACKAGE = "com.google.android.gms";
     private static final String FILLER_ERROR_MSG = "Error Error";
-    private AuthenticatorCallback mCallback;
+    private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
     private long mStartTimeMs;
     private MockWebContents mMockWebContents;
 
@@ -258,71 +259,10 @@
         }
     }
 
-    private static class AuthenticatorCallback {
-        private Integer mStatus;
-        private MakeCredentialAuthenticatorResponse mMakeCredentialResponse;
-        private GetAssertionAuthenticatorResponse mGetAssertionAuthenticatorResponse;
-        private List<byte[]> mGetMatchingCredentialIdsResponse;
-
-        // Signals when request is complete.
-        private final ConditionVariable mDone = new ConditionVariable();
-
-        AuthenticatorCallback() {}
-
-        public void onRegisterResponse(int status, MakeCredentialAuthenticatorResponse response) {
-            assert mStatus == null;
-            mStatus = status;
-            mMakeCredentialResponse = response;
-            unblock();
-        }
-
-        public void onSignResponse(int status, GetAssertionAuthenticatorResponse response) {
-            assert mStatus == null;
-            mStatus = status;
-            mGetAssertionAuthenticatorResponse = response;
-            unblock();
-        }
-
-        public void onGetMatchingCredentialIds(List<byte[]> matchingCredentialIds) {
-            mGetMatchingCredentialIdsResponse = matchingCredentialIds;
-            unblock();
-        }
-
-        public void onError(int status) {
-            assert mStatus == null;
-            mStatus = status;
-            unblock();
-        }
-
-        public Integer getStatus() {
-            return mStatus;
-        }
-
-        public MakeCredentialAuthenticatorResponse getMakeCredentialResponse() {
-            return mMakeCredentialResponse;
-        }
-
-        public GetAssertionAuthenticatorResponse getGetAssertionResponse() {
-            return mGetAssertionAuthenticatorResponse;
-        }
-
-        public List<byte[]> getGetMatchingCredentialIdsResponse() {
-            return mGetMatchingCredentialIdsResponse;
-        }
-
-        public void blockUntilCalled() {
-            mDone.block();
-        }
-
-        private void unblock() {
-            mDone.open();
-        }
-    }
-
     private static class TestAuthenticatorImplJni implements InternalAuthenticator.Natives {
-        private AuthenticatorCallback mCallback;
+        private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
 
-        TestAuthenticatorImplJni(AuthenticatorCallback callback) {
+        TestAuthenticatorImplJni(Fido2ApiTestHelper.AuthenticatorCallback callback) {
             mCallback = callback;
         }
 
@@ -460,7 +400,7 @@
         Assume.assumeTrue(gmsVersionSupported());
         mIntentSender = new MockIntentSender();
         mTestServer = sActivityTestRule.getTestServer();
-        mCallback = new AuthenticatorCallback();
+        mCallback = Fido2ApiTestHelper.getAuthenticatorCallback();
         String url = mTestServer.getURLWithHostName(
                 "subdomain.example.test", "/content/test/data/android/authenticator.html");
         GURL gurl = new GURL(url);
@@ -505,7 +445,7 @@
     @SmallTest
     public void testConvertOriginToString_defaultPortRemoved() {
         Origin origin = Origin.create(new GURL("https://www.example.com:443"));
-        String parsedOrigin = mRequest.convertOriginToString(origin);
+        String parsedOrigin = Fido2CredentialRequest.convertOriginToString(origin);
         Assert.assertEquals(parsedOrigin, "https://www.example.com/");
     }
 
@@ -1474,12 +1414,14 @@
         ArgumentCaptor<Origin> topOriginCaptor = ArgumentCaptor.forClass(Origin.class);
         Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
                 .buildClientDataJson(eq(ClientDataRequestType.PAYMENT_GET),
-                        eq(mRequest.convertOriginToString(mOrigin)), eq(mRequestOptions.challenge),
-                        eq(false), eq(payment.serialize()), eq(mRequestOptions.relyingPartyId),
-                        topOriginCaptor.capture());
+                        eq(Fido2CredentialRequest.convertOriginToString(mOrigin)),
+                        eq(mRequestOptions.challenge), eq(false), eq(payment.serialize()),
+                        eq(mRequestOptions.relyingPartyId), topOriginCaptor.capture());
 
-        String topOriginString = mRequest.convertOriginToString(topOriginCaptor.getValue());
-        String expectedTopOriginString = mRequest.convertOriginToString(Origin.create(topUrl));
+        String topOriginString =
+                Fido2CredentialRequest.convertOriginToString(topOriginCaptor.getValue());
+        String expectedTopOriginString =
+                Fido2CredentialRequest.convertOriginToString(Origin.create(topUrl));
         Assert.assertEquals(expectedTopOriginString, topOriginString);
     }
 
diff --git a/chrome/android/junit/DEPS b/chrome/android/junit/DEPS
index aafa7c9f..489a055 100644
--- a/chrome/android/junit/DEPS
+++ b/chrome/android/junit/DEPS
@@ -37,8 +37,17 @@
   "+components/sync/android/java/src/org/chromium/components/sync",
   "+components/sync/test",
   "+components/ukm/android",
+  "+components/webauthn/android",
 
   "-content/public/android/java",
   "+content/public/android/java/src/org/chromium/content_public",
   "+content/public/android/java/src/org/chromium/content/browser/RenderCoordinatesImpl.java",
 ]
+
+specific_include_rules = {
+  # Added to allow tests to mock JNI methods within the implementation. Tests
+  # use the public API classes where possible.
+  "Fido2CredentialRequestRobolectricTest.java": [
+    "+content/public/android/java/src/org/chromium/content/browser/ClientDataJsonImpl.java",
+  ]
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateRequest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateRequest.java
new file mode 100644
index 0000000..3efae684
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateRequest.java
@@ -0,0 +1,80 @@
+// Copyright 2023 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.webauth;
+
+import android.os.Bundle;
+
+/**
+ * Fake implementation of the Android Credential Manager CreateCredentialRequest object.
+ */
+public final class FakeAndroidCredManCreateRequest {
+    private final String mType;
+    private final Bundle mCredentialData;
+    private final Bundle mCandidateQueryData;
+    private boolean mAlwaysSendAppInfoToProvider;
+    private final String mOrigin;
+
+    public String getType() {
+        return mType;
+    }
+
+    public Bundle getCredentialData() {
+        return mCredentialData;
+    }
+
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
+    }
+
+    public boolean getAlwaysSendAppInfoToProvider() {
+        return mAlwaysSendAppInfoToProvider;
+    }
+
+    public String getOrigin() {
+        return mOrigin;
+    }
+
+    private FakeAndroidCredManCreateRequest(String type, Bundle credentialData,
+            Bundle candidateQueryData, boolean alwaysSendAppInfoToProvider, String origin) {
+        mType = type;
+        mCredentialData = credentialData;
+        mCandidateQueryData = candidateQueryData;
+        mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+        mOrigin = origin;
+    }
+
+    /**
+     * Builder for FakeAndroidCredManCreateRequest.
+     */
+    public static class Builder {
+        private String mType;
+        private final Bundle mCredentialData;
+        private final Bundle mCandidateQueryData;
+        private boolean mAlwaysSendAppInfoToProvider;
+        private String mOrigin;
+
+        public Builder(String type, Bundle credentialData, Bundle candidateQueryData) {
+            mType = type;
+            mCredentialData = credentialData;
+            mCandidateQueryData = candidateQueryData;
+        }
+
+        public FakeAndroidCredManCreateRequest.Builder setAlwaysSendAppInfoToProvider(
+                boolean value) {
+            mAlwaysSendAppInfoToProvider = value;
+            return this;
+        }
+
+        public FakeAndroidCredManCreateRequest.Builder setOrigin(String origin) {
+            mOrigin = origin;
+            return this;
+        }
+
+        public FakeAndroidCredManCreateRequest build() {
+            return new FakeAndroidCredManCreateRequest(mType, mCredentialData, mCandidateQueryData,
+                    mAlwaysSendAppInfoToProvider, mOrigin);
+        }
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateResponse.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateResponse.java
new file mode 100644
index 0000000..300926d7
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManCreateResponse.java
@@ -0,0 +1,18 @@
+// Copyright 2023 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.webauth;
+
+import android.os.Bundle;
+
+/**
+ * Fake implementation of the Android Credential Manager CreateCredentialResponse object.
+ */
+public final class FakeAndroidCredManCreateResponse {
+    public Bundle getData() {
+        Bundle data = new Bundle();
+        data.putString("androidx.credentials.BUNDLE_KEY_REGISTRATION_RESPONSE_JSON", "json");
+        return data;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManException.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManException.java
new file mode 100644
index 0000000..31414e2
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManException.java
@@ -0,0 +1,21 @@
+// Copyright 2023 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.webauth;
+
+/**
+ * Fake implementation of Credential Manager Exception.
+ */
+public final class FakeAndroidCredManException extends Exception {
+    private final String mType;
+
+    public FakeAndroidCredManException(String type, String message) {
+        super(message, null);
+        mType = type;
+    }
+
+    public String getType() {
+        return mType;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetRequest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetRequest.java
new file mode 100644
index 0000000..e9dc8b5
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetRequest.java
@@ -0,0 +1,78 @@
+// Copyright 2023 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.webauth;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fake implementation of the Android Credential Manager GetCredentialRequest object.
+ */
+public final class FakeAndroidCredManGetRequest {
+    private final List<FakeAndroidCredentialOption> mCredentialOptions;
+    private final Bundle mData;
+    private final boolean mAlwaysSendAppInfoToProvider;
+    private String mOrigin;
+
+    public List<FakeAndroidCredentialOption> getCredentialOptions() {
+        return mCredentialOptions;
+    }
+
+    public Bundle getData() {
+        return mData;
+    }
+
+    public String getOrigin() {
+        return mOrigin;
+    }
+
+    public boolean getAlwaysSendAppInfoToProvider() {
+        return mAlwaysSendAppInfoToProvider;
+    }
+
+    private FakeAndroidCredManGetRequest(List<FakeAndroidCredentialOption> credentialOptions,
+            Bundle data, boolean alwaysSendAppInfoToProvider, String origin) {
+        mCredentialOptions = credentialOptions;
+        mData = data;
+        mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+        mOrigin = origin;
+    }
+
+    /**
+     * Builder for FakeAndroidCredManGetRequest.
+     */
+    public static final class Builder {
+        private List<FakeAndroidCredentialOption> mCredentialOptions = new ArrayList<>();
+        private final Bundle mData;
+        private String mOrigin;
+        private boolean mAlwaysSendAppInfoToProvider = true;
+
+        public Builder(Bundle data) {
+            mData = data;
+        }
+
+        public Builder addCredentialOption(FakeAndroidCredentialOption credentialOption) {
+            mCredentialOptions.add(credentialOption);
+            return this;
+        }
+
+        public Builder setOrigin(String origin) {
+            mOrigin = origin;
+            return this;
+        }
+
+        public Builder setAlwaysSendAppInfoToProvider(boolean value) {
+            mAlwaysSendAppInfoToProvider = value;
+            return this;
+        }
+
+        public FakeAndroidCredManGetRequest build() {
+            return new FakeAndroidCredManGetRequest(
+                    mCredentialOptions, mData, mAlwaysSendAppInfoToProvider, mOrigin);
+        }
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetResponse.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetResponse.java
new file mode 100644
index 0000000..3cc4793
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredManGetResponse.java
@@ -0,0 +1,14 @@
+// Copyright 2023 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.webauth;
+
+/**
+ * Fake implementation of the Android Credential Manager GetCredentialResponse object.
+ */
+public final class FakeAndroidCredManGetResponse {
+    public FakeAndroidCredential getCredential() {
+        return new FakeAndroidCredential();
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredential.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredential.java
new file mode 100644
index 0000000..c356bb5
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredential.java
@@ -0,0 +1,18 @@
+// Copyright 2023 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.webauth;
+
+import android.os.Bundle;
+
+/**
+ * Fake implementation of the Android Credential Manager Credential object.
+ */
+public final class FakeAndroidCredential {
+    public Bundle getData() {
+        Bundle data = new Bundle();
+        data.putString("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON", "json");
+        return data;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialManager.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialManager.java
new file mode 100644
index 0000000..df9e886
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialManager.java
@@ -0,0 +1,73 @@
+// Copyright 2023 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.webauth;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Fake implementation of the Android Credential Manager Service.
+ */
+public final class FakeAndroidCredentialManager {
+    FakeAndroidCredManCreateRequest mCreateRequest;
+    FakeAndroidCredManGetRequest mGetRequest;
+    FakeAndroidCredManException mErrorResponse;
+
+    public FakeAndroidCredentialManager() {}
+
+    /**
+     * Fake implementation of CredentialManager.createCredential().
+     */
+    public void createCredential(Context context, FakeAndroidCredManCreateRequest request,
+            CancellationSignal cancellationSignal, Executor executor,
+            OutcomeReceiver<FakeAndroidCredManCreateResponse, Throwable> callback) {
+        mCreateRequest = request;
+
+        if (mErrorResponse != null) {
+            callback.onError(mErrorResponse);
+            return;
+        }
+        callback.onResult(new FakeAndroidCredManCreateResponse());
+    }
+
+    /**
+     * Fake implementation of CredentialManager.getCredential().
+     */
+    public void getCredential(Context context, FakeAndroidCredManGetRequest request,
+            CancellationSignal cancellationSignal, Executor executor,
+            OutcomeReceiver<FakeAndroidCredManGetResponse, FakeAndroidCredManException> callback) {
+        mGetRequest = request;
+
+        if (mErrorResponse != null) {
+            callback.onError(mErrorResponse);
+            return;
+        }
+        callback.onResult(new FakeAndroidCredManGetResponse());
+    }
+
+    /**
+     * Returns the received Create Request for inspection by tests.
+     */
+    public FakeAndroidCredManCreateRequest getCreateRequest() {
+        return mCreateRequest;
+    }
+
+    /**
+     * Returns the received Get Request for inspection by tests.
+     */
+    public FakeAndroidCredManGetRequest getGetRequest() {
+        return mGetRequest;
+    }
+
+    /**
+     * Sets an error response to be provided to the module under test.
+     */
+    public void setErrorResponse(FakeAndroidCredManException error) {
+        mErrorResponse = error;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialOption.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialOption.java
new file mode 100644
index 0000000..2ae8a78
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/FakeAndroidCredentialOption.java
@@ -0,0 +1,89 @@
+// Copyright 2023 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.webauth;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Fake implementation of the Android Credential Manager CredentialOption object.
+ */
+public final class FakeAndroidCredentialOption {
+    private final String mType;
+    private final Bundle mCredentialRetrievalData;
+    private final Bundle mCandidateQueryData;
+    private final boolean mIsSystemProviderRequired;
+    private final ArraySet<ComponentName> mAllowedProviders;
+
+    public String getType() {
+        return mType;
+    }
+
+    public Bundle getCredentialRetrievalData() {
+        return mCredentialRetrievalData;
+    }
+
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
+    }
+
+    public boolean isSystemProviderRequired() {
+        return mIsSystemProviderRequired;
+    }
+
+    public Set<ComponentName> getAllowedProviders() {
+        return mAllowedProviders;
+    }
+
+    private FakeAndroidCredentialOption(String type, Bundle credentialRetrievalData,
+            Bundle candidateQueryData, boolean isSystemProviderRequired,
+            ArraySet<ComponentName> allowedProviders) {
+        mType = type;
+        mCredentialRetrievalData = credentialRetrievalData;
+        mCandidateQueryData = candidateQueryData;
+        mIsSystemProviderRequired = isSystemProviderRequired;
+        mAllowedProviders = allowedProviders;
+    }
+
+    /**
+     * Builder for FakeAndroidCredentialOption.
+     */
+    public static final class Builder {
+        private String mType;
+        private Bundle mCredentialRetrievalData;
+        private Bundle mCandidateQueryData;
+        private boolean mIsSystemProviderRequired;
+        private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>();
+
+        public Builder(String type, Bundle credentialRetrievalData, Bundle candidateQueryData) {
+            mType = type;
+            mCredentialRetrievalData = credentialRetrievalData;
+            mCandidateQueryData = candidateQueryData;
+        }
+
+        public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) {
+            mIsSystemProviderRequired = isSystemProviderRequired;
+            return this;
+        }
+
+        public Builder addAllowedProvider(ComponentName allowedProvider) {
+            mAllowedProviders.add(allowedProvider);
+            return this;
+        }
+
+        public Builder setAllowedProviders(Set<ComponentName> allowedProviders) {
+            mAllowedProviders = new ArraySet<>(allowedProviders);
+            return this;
+        }
+
+        public FakeAndroidCredentialOption build() {
+            return new FakeAndroidCredentialOption(mType, mCredentialRetrievalData,
+                    mCandidateQueryData, mIsSystemProviderRequired, mAllowedProviders);
+        }
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestRobolectricTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestRobolectricTest.java
new file mode 100644
index 0000000..f747651
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestRobolectricTest.java
@@ -0,0 +1,227 @@
+// Copyright 2023 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.webauth;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.FeatureList;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
+import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
+import org.chromium.components.webauthn.AuthenticatorImpl;
+import org.chromium.components.webauthn.Fido2ApiTestHelper;
+import org.chromium.components.webauthn.Fido2CredentialRequest;
+import org.chromium.content.browser.ClientDataJsonImpl;
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.RenderFrameHost.WebAuthSecurityChecksResults;
+import org.chromium.content_public.browser.WebAuthenticationDelegate;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.device.DeviceFeatureList;
+import org.chromium.net.GURLUtils;
+import org.chromium.net.GURLUtilsJni;
+import org.chromium.url.GURL;
+import org.chromium.url.Origin;
+
+@RunWith(BaseRobolectricTestRunner.class)
+public class Fido2CredentialRequestRobolectricTest {
+    private FakeAndroidCredentialManager mCredentialManager;
+    private Fido2CredentialRequest mRequest;
+    private PublicKeyCredentialCreationOptions mCreationOptions;
+    private PublicKeyCredentialRequestOptions mRequestOptions;
+    private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
+    private Origin mOrigin;
+
+    @Mock
+    private RenderFrameHost mFrameHost;
+    @Mock
+    private WebContents mWebContents;
+    @Mock
+    GURLUtils.Natives mGURLUtilsJniMock;
+    @Mock
+    ClientDataJsonImpl.Natives mClientDataJsonImplMock;
+
+    @Rule
+    public JniMocker mMocker = new JniMocker();
+
+    @Before
+    public void setUp() throws Exception {
+        FeatureList.TestValues testValues = new FeatureList.TestValues();
+        testValues.addFeatureFlagOverride(DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN, true);
+        FeatureList.setTestValues(testValues);
+
+        MockitoAnnotations.initMocks(this);
+
+        org.chromium.url.internal.mojom.Origin mojomOrigin =
+                new org.chromium.url.internal.mojom.Origin();
+        mojomOrigin.scheme = "https";
+        mojomOrigin.host = "subdomain.example.test";
+        mojomOrigin.port = 443;
+        GURL gurl = new GURL(
+                "https://subdomain.example.test:443/content/test/data/android/authenticator.html");
+        mOrigin = new Origin(mojomOrigin);
+
+        mMocker.mock(GURLUtilsJni.TEST_HOOKS, mGURLUtilsJniMock);
+        Mockito.when(mGURLUtilsJniMock.getOrigin(any(String.class)))
+                .thenReturn("https://subdomain.example.test:443");
+
+        mCreationOptions = Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
+        mRequestOptions = Fido2ApiTestHelper.createDefaultGetAssertionOptions();
+
+        mRequest = new Fido2CredentialRequest(
+                /*intentSender=*/null, WebAuthenticationDelegate.Support.BROWSER);
+        AuthenticatorImpl.overrideFido2CredentialRequestForTesting(mRequest);
+
+        Fido2ApiTestHelper.mockFido2CredentialRequestJni(mMocker);
+        Fido2ApiTestHelper.mockClientDataJson(mMocker, "{}");
+
+        mCallback = Fido2ApiTestHelper.getAuthenticatorCallback();
+
+        mRequest.setWebContentsForTesting(mWebContents);
+
+        Mockito.when(mFrameHost.getLastCommittedURL()).thenReturn(gurl);
+        Mockito.when(mFrameHost.getLastCommittedOrigin()).thenReturn(mOrigin);
+        Mockito.when(mFrameHost.performMakeCredentialWebAuthSecurityChecks(
+                             any(String.class), any(Origin.class), anyBoolean()))
+                .thenReturn(0);
+        Mockito.when(mFrameHost.performGetAssertionWebAuthSecurityChecks(
+                             any(String.class), any(Origin.class), anyBoolean()))
+                .thenReturn(new WebAuthSecurityChecksResults(AuthenticatorStatus.SUCCESS, false));
+
+        mCredentialManager = new FakeAndroidCredentialManager();
+        mRequest.setOverrideVersionCheckForTesting(true);
+        mRequest.setCredManClassesForTesting(mCredentialManager,
+                FakeAndroidCredManCreateRequest.Builder.class,
+                FakeAndroidCredManGetRequest.Builder.class,
+                FakeAndroidCredentialOption.Builder.class);
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeCredential_credManEnabled_success() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mRequest.handleMakeCredentialRequest(mCreationOptions, mFrameHost, mOrigin,
+                (responseStatus, response)
+                        -> mCallback.onRegisterResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        FakeAndroidCredManCreateRequest credManRequest = mCredentialManager.getCreateRequest();
+        Assert.assertNotNull(credManRequest);
+        Assert.assertEquals(
+                credManRequest.getOrigin(), Fido2CredentialRequest.convertOriginToString(mOrigin));
+        Assert.assertEquals(
+                credManRequest.getType(), "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL");
+        Assert.assertEquals(credManRequest.getCredentialData().getString(
+                                    "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"),
+                "{serialized_make_request}");
+        Assert.assertTrue(credManRequest.getAlwaysSendAppInfoToProvider());
+        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeCredential_credManEnabledUserCancel_notAllowedError() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mCredentialManager.setErrorResponse(new FakeAndroidCredManException(
+                "android.credentials.CreateCredentialException.TYPE_USER_CANCELED", "Message"));
+        mRequest.handleMakeCredentialRequest(mCreationOptions, mFrameHost, mOrigin,
+                (responseStatus, response)
+                        -> mCallback.onRegisterResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        Assert.assertEquals(
+                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeCredential_credManEnabledUnknownError_unknownError() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mCredentialManager.setErrorResponse(new FakeAndroidCredManException(
+                "android.credentials.CreateCredentialException.TYPE_UNKNOWN", "Message"));
+        mRequest.handleMakeCredentialRequest(mCreationOptions, mFrameHost, mOrigin,
+                (responseStatus, response)
+                        -> mCallback.onRegisterResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        Assert.assertEquals(
+                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAssertion_credManEnabled_success() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
+                (responseStatus, response)
+                        -> mCallback.onSignResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        FakeAndroidCredManGetRequest credManRequest = mCredentialManager.getGetRequest();
+        Assert.assertNotNull(credManRequest);
+        Assert.assertEquals(
+                credManRequest.getOrigin(), Fido2CredentialRequest.convertOriginToString(mOrigin));
+        FakeAndroidCredentialOption option = credManRequest.getCredentialOptions().get(0);
+        Assert.assertNotNull(option);
+        Assert.assertEquals(option.getType(), "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL");
+        Assert.assertEquals(option.getCredentialRetrievalData().getString(
+                                    "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"),
+                "{serialized_get_request}");
+        Assert.assertFalse(option.isSystemProviderRequired());
+        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAssertion_credManEnabledUserCancel_notAllowedError() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mCredentialManager.setErrorResponse(new FakeAndroidCredManException(
+                "android.credentials.CreateCredentialException.TYPE_USER_CANCELED", "Message"));
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
+                (responseStatus, response)
+                        -> mCallback.onSignResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        Assert.assertEquals(
+                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAssertion_credManEnabledUnknownError_unknownError() {
+        // Calls to `context.getMainExecutor()` require API level 28 or higher.
+        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        mCredentialManager.setErrorResponse(new FakeAndroidCredManException(
+                "android.credentials.CreateCredentialException.TYPE_UNKNOWN", "Message"));
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
+                (responseStatus, response)
+                        -> mCallback.onSignResponse(responseStatus, response),
+                errorStatus -> mCallback.onError(errorStatus));
+        Assert.assertEquals(
+                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
+    }
+}
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index f6700d100..58b52f8 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-115.0.5771.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-115.0.5772.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e25b8b4..f12f3f7 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -10942,6 +10942,9 @@
         <message name="IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC" desc="Body of the web signin interception bubble when the new account is personal and the existing account is managed">
           This will separate your browsing from <ph name="EXISTING_USER">$1<ex>bob@example.com</ex></ph>
         </message>
+        <message name="IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_BY_TOKEN" desc="Body of the web signin interception bubble when the new account is managed via a token">
+          You just signed in a managed account, creating a new managed profile will allow you to access some resources linked to that account.
+        </message>
         <message name="IDS_SIGNIN_DICE_WEB_INTERCEPT_SWITCH_BUBBLE_DESC_V2" desc="Description for the profile switch interception bubble">
           <ph name="NAME">$2<ex>Bob</ex></ph>'s profile is linked to <ph name="EMAIL">$1<ex>bob@gmail.com</ex></ph>
         </message>
@@ -14464,23 +14467,15 @@
     <message name = "IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME" desc="Label for combobox option for an enabled variation that will have a description describing what variation is enabled in the placeholder.">
       Enabled – <ph name="VARIATION_NAME">$1<ex>tabs shrink to pinned tab width</ex></ph>
     </message>
-
-    <!-- ChromeLabs Tab Search Media Tabs -->
-    <message name='IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_NAME' desc="Name for the Tab Search Media Tabs experiment">
-      Media Tabs Section in Tab Search
+    <!-- ChromeLabs ChromeRefresh2023-->
+    <message name="IDS_CHROMEREFRESH2023_EXPERIMENT_NAME" desc="The name of the project for Chrome's desktop UI redesign.">
+      Chrome Refresh 2023
     </message>
-    <message name='IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_DESCRIPTION' desc='Description for the Tab Search Media Tabs experiment'>
-       Adds a new section in 'Search Tabs' to easily find your tabs playing audio or video. Access through the button on the top corner of your browser.
+    <message name="IDS_CHROMEREFRESH2023_DESCRIPTION" desc="The description of the project for Chrome's desktop UI redesign.">
+      Enables the new desktop design.
     </message>
-    <message name='IDS_MEDIA_TABS_ALSO_SHOWN_IN_OPEN_TABS_SECTION' desc='Label describing behavior that media tabs will also show up in the open tabs section'>
-      Media tabs also shown in Open Tabs section
-    </message>
-    <!-- ChromeLabs Side Panel-->
-    <message name="IDS_SIDE_PANEL_EXPERIMENT_NAME" desc="Name for Side Panel experiment">
-      Side Panel
-    </message>
-    <message name="IDS_SIDE_PANEL_EXPERIMENT_DESCRIPTION" desc="Description for Side Panel experiment">
-      Enables a browser-level side panel for a useful and persistent way to access your Reading List and Bookmarks.
+     <message name="IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX" desc="Indicates no omnibox design changes in Chrome's desktop UI redesign.">
+      without Omnibox
     </message>
     <!-- ChromeLabs Tab Scrolling -->
     <message name="IDS_TAB_SCROLLING_EXPERIMENT_NAME" desc="Name for Tab Scrolling experiment">
@@ -14501,20 +14496,6 @@
     <message name="IDS_TABS_DO_NOT_SHRINK" desc="Label describing tab behavior will not shrink">
       Tabs don't shrink
     </message>
-    <!-- ChromeLabs Tab Search -->
-    <message name="IDS_TAB_SEARCH_EXPERIMENT_NAME" desc="Name for Tab Search experiment">
-      Tab Search
-    </message>
-    <message name="IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION" desc="Description for Tab Search experiment">
-      Enable a popup bubble in Top Chrome UI to search over currently open tabs.
-    </message>
-    <!-- ChromeLabs Lens Region Search -->
-    <message name="IDS_LENS_REGION_SEARCH_EXPERIMENT_NAME" desc="Name for Lens Region Search experiment">
-      Search your screen with Google Lens
-    </message>
-    <message name="IDS_LENS_REGION_SEARCH_EXPERIMENT_DESCRIPTION" desc="Description for Lens Region Search experiment">
-      Right click and select “Search images with Google Lens” to search any region of the site to learn more about the visual content you see while you browse and shop on the web.
-    </message>
     <!-- Lens Region Search bubble dialog -->
     <message name="IDS_LENS_REGION_SEARCH_BUBBLE_TEXT" desc="Text that is shown in the Lens Region Search education bubble when starting the feature. Informs the user to drag over the screen to select a region to search with Google Lens.">
       Select image area to search
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..df399e1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+4c989535f0adc2d85681a1099c8a673e0e81cd7c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_EXPERIMENT_NAME.png.sha1
new file mode 100644
index 0000000..df399e1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_EXPERIMENT_NAME.png.sha1
@@ -0,0 +1 @@
+4c989535f0adc2d85681a1099c8a673e0e81cd7c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX.png.sha1
new file mode 100644
index 0000000..df399e1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX.png.sha1
@@ -0,0 +1 @@
+4c989535f0adc2d85681a1099c8a673e0e81cd7c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
deleted file mode 100644
index 4947113..0000000
--- a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-49a231b486ea8b2096fac940af80b4722cd62723
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_NAME.png.sha1
deleted file mode 100644
index f8aea72..0000000
--- a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_EXPERIMENT_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a39b19402b988bd1812585a51a71b046d6777f4b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_MEDIA_TABS_ALSO_SHOWN_IN_OPEN_TABS_SECTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_MEDIA_TABS_ALSO_SHOWN_IN_OPEN_TABS_SECTION.png.sha1
deleted file mode 100644
index b3240f6f..0000000
--- a/chrome/app/generated_resources_grd/IDS_MEDIA_TABS_ALSO_SHOWN_IN_OPEN_TABS_SECTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7a07ca6e0559e4190333e6f7927412a2f120d36e
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_DESCRIPTION.png.sha1
deleted file mode 100644
index 8d89aa71..0000000
--- a/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f3bf7d631ab665453b4b1a0ab1847e7c3b586a8a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_NAME.png.sha1
deleted file mode 100644
index 8d89aa71..0000000
--- a/chrome/app/generated_resources_grd/IDS_SIDE_PANEL_EXPERIMENT_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f3bf7d631ab665453b4b1a0ab1847e7c3b586a8a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_BY_TOKEN.png.sha1 b/chrome/app/generated_resources_grd/IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_BY_TOKEN.png.sha1
new file mode 100644
index 0000000..195d1bde
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_BY_TOKEN.png.sha1
@@ -0,0 +1 @@
+30351d03f081f27eafd89d826a7a818e0a9a0b50
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
deleted file mode 100644
index e68acc5..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-71b4ef1b62d61ec7fe64d09c600e069450c6fefe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1
deleted file mode 100644
index e68acc5..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-71b4ef1b62d61ec7fe64d09c600e069450c6fefe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_DESCRIPTION.png.sha1
deleted file mode 100644
index 67ee4169..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-cbe280d1df55cc1b5d548106fcc3bb80834a2a11
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_NAME.png.sha1
deleted file mode 100644
index f888125..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_MEDIA_TABS_EXPERIMENT_NAME.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-adc0e841d57b79a617f686f1af365f0604c73e81
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index f673aabf..a42c5ad 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -6798,6 +6798,8 @@
       "signin/process_dice_header_delegate_impl.h",
       "signin/signin_ui_delegate_impl_dice.cc",
       "signin/signin_ui_delegate_impl_dice.h",
+      "signin/web_signin_interceptor.cc",
+      "signin/web_signin_interceptor.h",
     ]
     if (is_win) {
       sources += [
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index be6ba488..eaf7da1 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -283,6 +283,7 @@
   "+components/safe_browsing/core/browser",
   "+components/safe_browsing/core/common",
   "+components/safe_search_api",
+  "+components/saved_tab_groups",
   "+components/schema_org",
   "+components/shared_highlighting/core/common",
   "+components/search",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8475455..8d383708 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6909,11 +6909,6 @@
      FEATURE_VALUE_TYPE(media::kCrOSEnforceSystemAecNs)},
 #endif
 
-    {"reduce-horizontal-fling-velocity",
-     flag_descriptions::kReduceHorizontalFlingVelocityName,
-     flag_descriptions::kReduceHorizontalFlingVelocityDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kReduceHorizontalFlingVelocity)},
-
     {"enable-css-selector-fragment-anchor",
      flag_descriptions::kEnableCssSelectorFragmentAnchorName,
      flag_descriptions::kEnableCssSelectorFragmentAnchorDescription, kOsAll,
@@ -8283,7 +8278,8 @@
      flag_descriptions::kChromeLabsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kChromeLabs)},
 
-    {"chrome-refresh-2023", flag_descriptions::kChromeRefresh2023Name,
+    {flag_descriptions::kChromeRefresh2023Id,
+     flag_descriptions::kChromeRefresh2023Name,
      flag_descriptions::kChromeRefresh2023Description, kOsDesktop,
      FEATURE_WITH_PARAMS_VALUE_TYPE(features::kChromeRefresh2023,
                                     kChromeRefresh2023Variations,
@@ -9128,10 +9124,6 @@
          autofill::features::kAutofillEnableUpdateVirtualCardEnrollment)},
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"enable-desktop-capture-lacros-v2",
-     flag_descriptions::kDesktopCaptureLacrosV2Name,
-     flag_descriptions::kDesktopCaptureLacrosV2Description, kOsCrOS | kOsLacros,
-     FEATURE_VALUE_TYPE(features::kDesktopCaptureLacrosV2)},
 
     {"enable-lacros-aura-capture", flag_descriptions::kLacrosAuraCaptureName,
      flag_descriptions::kLacrosAuraCaptureDescription, kOsCrOS | kOsLacros,
@@ -9988,6 +9980,11 @@
     {"slim-compositor", flag_descriptions::kSlimCompositorName,
      flag_descriptions::kSlimCompositorDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(features::kSlimCompositor)},
+
+    {"surface-control-magnifier",
+     flag_descriptions::kSurfaceControlMagnifierName,
+     flag_descriptions::kSurfaceControlMagnifierDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kAndroidSurfaceControlMagnifier)},
 #endif  // BUILDFLAG(IS_ANDROID)
 
     {"autofill-enable-iban-client-side-url-filtering",
diff --git a/chrome/browser/apps/app_preload_service/web_app_preload_installer.cc b/chrome/browser/apps/app_preload_service/web_app_preload_installer.cc
index e1ee4bd..55f0e92 100644
--- a/chrome/browser/apps/app_preload_service/web_app_preload_installer.cc
+++ b/chrome/browser/apps/app_preload_service/web_app_preload_installer.cc
@@ -99,7 +99,7 @@
 std::string WebAppPreloadInstaller::GetAppId(
     const PreloadAppDefinition& app) const {
   // The app's "Web app manifest ID" is the equivalent of the unhashed app ID.
-  return web_app::GenerateAppIdFromUnhashed(app.GetWebAppManifestId().spec());
+  return web_app::GenerateAppIdFromManifestId(app.GetWebAppManifestId());
 }
 
 void WebAppPreloadInstaller::InstallAppImpl(
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 80d53b3..aaa4484 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -15,8 +15,8 @@
 #include "base/memory/raw_ptr.h"
 #include "base/process/process.h"
 #include "base/run_loop.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
@@ -716,14 +716,12 @@
     // must use the ExecuteScriptAsync.
     content::ExecuteScriptAsync(
         embedder_web_contents,
-        base::StringPrintf("try { "
-                           "  runTest('%s'); "
-                           "} catch (e) { "
-                           "  console.log('UNABLE TO START TEST.'); "
-                           "  console.log(e); "
-                           "  chrome.test.sendMessage('TEST_FAILED'); "
-                           "}",
-                           test_name.c_str()));
+        base::StrCat({"try { runTest('", test_name,
+                      "'); } catch (e) { "
+                      "  console.log('UNABLE TO START TEST.'); "
+                      "  console.log(e); "
+                      "  chrome.test.sendMessage('TEST_FAILED'); "
+                      "}"}));
     ASSERT_TRUE(done_listener.WaitUntilSatisfied());
   }
 
@@ -742,9 +740,9 @@
 
     ExtensionTestMessageListener test_run_listener("PASSED");
     test_run_listener.set_failure_message("FAILED");
-    EXPECT_TRUE(content::ExecJs(
-        embedder_web_contents,
-        base::StringPrintf("startDenyTest('%s')", test_name.c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(embedder_web_contents,
+                        base::StrCat({"startDenyTest('", test_name, "')"})));
     ASSERT_TRUE(test_run_listener.WaitUntilSatisfied());
   }
 
@@ -758,9 +756,9 @@
   }
 
   void SendMessageToEmbedder(const std::string& message) {
-    EXPECT_TRUE(content::ExecJs(
-        GetEmbedderWebContents(),
-        base::StringPrintf("onAppCommand('%s');", message.c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(GetEmbedderWebContents(),
+                        base::StrCat({"onAppCommand('", message, "');"})));
   }
 
   void SendMessageToGuestAndWait(const std::string& message,
@@ -772,7 +770,7 @@
 
     EXPECT_TRUE(content::ExecJs(
         GetGuestViewManager()->WaitForSingleGuestRenderFrameHostCreated(),
-        base::StringPrintf("onAppCommand('%s');", message.c_str())));
+        base::StrCat({"onAppCommand('", message, "');"})));
 
     if (listener) {
       ASSERT_TRUE(listener->WaitUntilSatisfied());
@@ -915,8 +913,7 @@
 
   static std::string DescribeParams(
       const testing::TestParamInfo<ParamType>& info) {
-    return base::StringPrintf("NewWindow%s",
-                              info.param ? "Restricted" : "Legacy");
+    return info.param ? "NewWindowRestricted" : "NewWindowLegacy";
   }
 
   bool IsNewWindowRestricted() { return GetParam(); }
@@ -935,7 +932,7 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     WebViewTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor,
-                                    base::StringPrintf("%f", scale()));
+                                    base::NumberToString(scale()));
   }
 
   static float scale() { return 2.0f; }
@@ -1017,12 +1014,9 @@
   // Inject JS to start audio.
   GURL audio_url = embedded_test_server()->GetURL(
       "/extensions/platform_apps/web_view/simple/ping.mp3");
-  std::string setup_audio_script = base::StringPrintf(
-      "ae = document.createElement('audio');"
-      "ae.src='%s';"
-      "document.body.appendChild(ae);"
-      "ae.play();",
-      audio_url.spec().c_str());
+  std::string setup_audio_script = base::StrCat(
+      {"ae = document.createElement('audio'); ae.src='", audio_url.spec(),
+       "'; document.body.appendChild(ae); ae.play();"});
   EXPECT_TRUE(content::ExecJs(guest, setup_audio_script,
                               content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
 
@@ -2130,9 +2124,7 @@
 
   static std::string DescribeParams(
       const testing::TestParamInfo<ParamType>& info) {
-    bool use_interstitials = info.param;
-    return base::StringPrintf("Use%s",
-                              use_interstitials ? "Interstitial" : "ErrorPage");
+    return info.param ? "UseInterstitial" : "UseErrorPage";
   }
 
   bool UseInterstitials() { return GetParam(); }
@@ -2660,9 +2652,9 @@
 
   ExtensionTestMessageListener done_listener("TEST_PASSED");
   done_listener.set_failure_message("TEST_FAILED");
-  EXPECT_TRUE(content::ExecJs(
-      embedder_web_contents,
-      base::StringPrintf("startAllowTest('%s')", test_name.c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(embedder_web_contents,
+                      base::StrCat({"startAllowTest('", test_name, "')"})));
   ASSERT_TRUE(done_listener.WaitUntilSatisfied());
 
   mock->WaitForRequestMediaPermission();
@@ -2961,8 +2953,7 @@
 
   ExtensionTestMessageListener done_listener("TEST_PASSED");
   done_listener.set_failure_message("TEST_FAILED");
-  EXPECT_TRUE(content::ExecJs(embedder_web_contents,
-                              base::StringPrintf("startCheckTest('')")));
+  EXPECT_TRUE(content::ExecJs(embedder_web_contents, "startCheckTest('')"));
   ASSERT_TRUE(done_listener.WaitUntilSatisfied());
 
   mock->WaitForCheckMediaPermission();
@@ -3469,10 +3460,10 @@
         content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
     EXPECT_TRUE(content::ExecJs(
         web_contents,
-        base::StringPrintf(
-            "startDownload('%s', '%s?cookie=%s')", cookie.c_str(),
-            embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str(),
-            cookie.c_str())));
+        base::StrCat(
+            {"startDownload('", cookie, "', '",
+             embedded_test_server()->GetURL(kDownloadPathPrefix).spec(),
+             "?cookie=", cookie, "')"})));
 
     // This maps to DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED.
     interrupted_observer.WaitForFinished();
@@ -3559,10 +3550,10 @@
 
     EXPECT_TRUE(content::ExecJs(
         web_contents,
-        base::StringPrintf(
-            "startDownload('%s', '%s?cookie=%s')", cookie.c_str(),
-            embedded_test_server()->GetURL(kDownloadPathPrefix).spec().c_str(),
-            cookie.c_str())));
+        base::StrCat(
+            {"startDownload('", cookie, "', '",
+             embedded_test_server()->GetURL(kDownloadPathPrefix).spec(),
+             "?cookie=", cookie, "')"})));
 
     // This maps to DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED.
     interrupted_observer.WaitForFinished();
@@ -4143,9 +4134,8 @@
     WebViewChannelTest,
     MAYBE_Shim_TestRulesRegistryIDAreRemovedAfterWebViewIsGone) {
   ASSERT_EQ(extensions::GetCurrentChannel(), GetChannelParam());
-  SCOPED_TRACE(base::StringPrintf(
-      "Testing Channel %s",
-      version_info::GetChannelString(GetChannelParam()).c_str()));
+  SCOPED_TRACE(base::StrCat(
+      {"Testing Channel ", version_info::GetChannelString(GetChannelParam())}));
 
   LoadAppWithGuest("web_view/rules_registry");
 
@@ -4192,9 +4182,8 @@
 IN_PROC_BROWSER_TEST_P(WebViewChannelTest,
                        Shim_WebViewWebRequestRegistryHasNoPersistentCache) {
   ASSERT_EQ(extensions::GetCurrentChannel(), GetChannelParam());
-  SCOPED_TRACE(base::StringPrintf(
-      "Testing Channel %s",
-      version_info::GetChannelString(GetChannelParam()).c_str()));
+  SCOPED_TRACE(base::StrCat(
+      {"Testing Channel ", version_info::GetChannelString(GetChannelParam())}));
 
   LoadAppWithGuest("web_view/rules_registry");
 
@@ -4821,9 +4810,7 @@
 
   static std::string DescribeParams(
       const testing::TestParamInfo<ParamType>& info) {
-    bool is_scroll_disabled = info.param;
-    return base::StringPrintf("Scroll%s",
-                              is_scroll_disabled ? "Disabled" : "Enabled");
+    return info.param ? "ScrollDisabled" : "ScrollEnabled";
   }
 };
 
@@ -6358,10 +6345,8 @@
   // Attach a second <webview>.
   ASSERT_TRUE(content::ExecJs(
       GetEmbedderWebContents(),
-      base::StringPrintf("const w = document.createElement('webview');"
-                         "w.src = '%s';"
-                         "document.body.appendChild(w);",
-                         start_url.spec().c_str())));
+      base::StrCat({"const w = document.createElement('webview'); w.src = '",
+                    start_url.spec(), "'; document.body.appendChild(w);"})));
   GetGuestViewManager()->WaitForNumGuestsCreated(2u);
   auto* guest2 = GetGuestViewManager()->GetLastGuestViewCreated();
   ASSERT_NE(guest, guest2);
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 93e5cc0..40b73fb 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -656,8 +656,6 @@
     "attestation/tpm_challenge_key_with_timeout.h",
     "audio/audio_survey_handler.cc",
     "audio/audio_survey_handler.h",
-    "authpolicy/authpolicy_credentials_manager.cc",
-    "authpolicy/authpolicy_credentials_manager.h",
     "authpolicy/data_pipe_utils.cc",
     "authpolicy/data_pipe_utils.h",
     "authpolicy/kerberos_files_handler.cc",
@@ -3439,8 +3437,6 @@
     "//chromeos/ash/components/dbus/anomaly_detector:proto",
     "//chromeos/ash/components/dbus/attestation",
     "//chromeos/ash/components/dbus/attestation:attestation_proto",
-    "//chromeos/ash/components/dbus/authpolicy",
-    "//chromeos/ash/components/dbus/authpolicy:authpolicy_proto",
     "//chromeos/ash/components/dbus/chunneld",
     "//chromeos/ash/components/dbus/cicerone",
     "//chromeos/ash/components/dbus/cicerone:cicerone_proto",
@@ -5004,7 +5000,6 @@
     "attestation/tpm_challenge_key_result_unittest.cc",
     "attestation/tpm_challenge_key_subtle_unittest.cc",
     "attestation/tpm_challenge_key_unittest.cc",
-    "authpolicy/authpolicy_credentials_manager_unittest.cc",
     "bluetooth/debug_logs_manager_unittest.cc",
     "borealis/borealis_app_launcher_unittest.cc",
     "borealis/borealis_app_uninstaller_unittest.cc",
@@ -5831,7 +5826,6 @@
     "//chromeos/ash/components/dbus/attestation",
     "//chromeos/ash/components/dbus/attestation:attestation_proto",
     "//chromeos/ash/components/dbus/audio",
-    "//chromeos/ash/components/dbus/authpolicy",
     "//chromeos/ash/components/dbus/biod",
     "//chromeos/ash/components/dbus/chunneld",
     "//chromeos/ash/components/dbus/cicerone",
@@ -6261,8 +6255,12 @@
   }
 
   if (use_cups) {
-    sources += [ "printing/cups_print_job_manager_utils_unittest.cc" ]
+    sources += [
+      "printing/cups_print_job_manager_utils_unittest.cc",
+      "printing/cups_proxy_service_manager_unittest.cc",
+    ]
     deps += [
+      "//chromeos/ash/components/dbus/cups_proxy:cups_proxy",
       "//printing:printing_base",
       "//printing/backend",
     ]
diff --git a/chrome/browser/ash/app_list/search/omnibox/OWNERS b/chrome/browser/ash/app_list/search/omnibox/OWNERS
new file mode 100644
index 0000000..5535cd2
--- /dev/null
+++ b/chrome/browser/ash/app_list/search/omnibox/OWNERS
@@ -0,0 +1 @@
+file://components/omnibox/OWNERS
diff --git a/chrome/browser/ash/arc/accessibility/accessibility_info_data_wrapper_unittest.cc b/chrome/browser/ash/arc/accessibility/accessibility_info_data_wrapper_unittest.cc
index f09d685..cf9781e 100644
--- a/chrome/browser/ash/arc/accessibility/accessibility_info_data_wrapper_unittest.cc
+++ b/chrome/browser/ash/arc/accessibility/accessibility_info_data_wrapper_unittest.cc
@@ -89,7 +89,7 @@
 }
 
 TEST_F(AccessibilityInfoDataWrapperTest, RootNodeBounds) {
-  UpdateDisplay("400x400");
+  UpdateDisplay("400x300");
 
   auto shell_surface = exo::test::ShellSurfaceBuilder({200, 200})
                            .SetGeometry(gfx::Rect(10, 10, 200, 200))
@@ -107,7 +107,7 @@
 }
 
 TEST_F(AccessibilityInfoDataWrapperTest, RootNodeBoundsOnExternalDisplay) {
-  UpdateDisplay("400x400,500x500");
+  UpdateDisplay("400x300,600x500");
 
   auto shell_surface = exo::test::ShellSurfaceBuilder({200, 200})
                            .SetGeometry(gfx::Rect(410, 10, 200, 200))
@@ -125,7 +125,7 @@
 }
 
 TEST_F(AccessibilityInfoDataWrapperTest, BoundsScalingPiArc) {
-  UpdateDisplay("400x400*2");  // 2x device scale factor.
+  UpdateDisplay("400x300*2");  // 2x device scale factor.
 
   // With default_scale_cancellation, Android has default (1x) scale factor.
   wm_helper->SetDefaultScaleCancellation(true);
@@ -146,7 +146,7 @@
 }
 
 TEST_F(AccessibilityInfoDataWrapperTest, BoundsScalingFromRvcArcAndLater) {
-  UpdateDisplay("400x400*2");  // 2x device scale factor.
+  UpdateDisplay("400x300*2");  // 2x device scale factor.
 
   // Without default_scale_cancellation, Android use the same (2x) scale factor.
   wm_helper->SetDefaultScaleCancellation(false);
diff --git a/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.cc b/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.cc
index 657eb29c..9be4fe5a 100644
--- a/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.cc
+++ b/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h"
 
 #include "base/containers/span.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/timer/timer.h"
@@ -138,8 +139,10 @@
 
 void SoftBindAttestationFlowImpl::Session::ReportFailure(
     const std::string& error_message) {
+  LOG(WARNING) << "Attestation session failure: " << error_message;
   if (!callback_) {
-    LOG(ERROR) << "Attestation session failure callback in null.";
+    LOG(WARNING) << "Callback is null";
+    base::debug::DumpWithoutCrashing();
     return;
   }
   std::move(callback_).Run(std::vector<std::string>{"INVALID:" + error_message},
@@ -148,6 +151,11 @@
 
 void SoftBindAttestationFlowImpl::Session::ReportSuccess(
     const std::vector<std::string>& certificate_chain) {
+  if (!callback_) {
+    LOG(WARNING) << "Attestation session success but callback is null";
+    base::debug::DumpWithoutCrashing();
+    return;
+  }
   std::move(callback_).Run(certificate_chain, /*valid=*/true);
 }
 
diff --git a/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h b/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h
index 317e385..a10597ff 100644
--- a/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h
+++ b/chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h
@@ -74,7 +74,7 @@
     void OnTimeout();
 
     Callback callback_;
-    base::RepeatingTimer timer_;
+    base::RetainingOneShotTimer timer_;
     const AccountId account_id_;
     std::string user_key_;
     int max_retries_ = 3;
diff --git a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.cc b/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.cc
deleted file mode 100644
index 7e3bb5e..0000000
--- a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.cc
+++ /dev/null
@@ -1,364 +0,0 @@
-// Copyright 2017 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/ash/authpolicy/authpolicy_credentials_manager.h"
-
-#include <memory>
-#include <utility>
-
-#include "ash/constants/notifier_catalogs.h"
-#include "ash/public/cpp/notification_utils.h"
-#include "base/functional/bind.h"
-#include "base/location.h"
-#include "base/memory/singleton.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/task/single_thread_task_runner.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part.h"
-#include "chrome/browser/lifetime/application_lifetime.h"
-#include "chrome/browser/notifications/notification_common.h"
-#include "chrome/browser/notifications/notification_display_service.h"
-#include "chrome/browser/notifications/notification_display_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/grit/chromium_strings.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/grit/theme_resources.h"
-#include "chromeos/ash/components/account_manager/account_manager_factory.h"
-#include "chromeos/ash/components/dbus/authpolicy/authpolicy_client.h"
-#include "chromeos/ash/components/network/network_handler.h"
-#include "chromeos/ash/components/network/network_state.h"
-#include "chromeos/ash/components/network/network_state_handler.h"
-#include "components/account_manager_core/account.h"
-#include "components/account_manager_core/chromeos/account_manager.h"
-#include "components/user_manager/user.h"
-#include "components/user_manager/user_manager.h"
-#include "components/vector_icons/vector_icons.h"
-#include "dbus/message.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/message_center/public/cpp/notification.h"
-#include "ui/message_center/public/cpp/notification_delegate.h"
-
-namespace ash {
-
-namespace {
-
-constexpr base::TimeDelta kGetUserStatusCallsInterval = base::Hours(1);
-constexpr char kProfileSigninNotificationId[] = "chrome://settings/signin/";
-
-// Sets up Chrome OS Account Manager.
-// |profile| is a non-owning pointer to |Profile|.
-// |account_id| is the |AccountId| for the Device Account.
-void SetupAccountManager(Profile* profile, const AccountId& account_id) {
-  auto* factory =
-      g_browser_process->platform_part()->GetAccountManagerFactory();
-  DCHECK(factory);
-  auto* account_manager =
-      factory->GetAccountManager(profile->GetPath().value());
-  DCHECK(account_manager);
-  // |account_manager::AccountManager::UpsertAccount| is idempotent and safe to
-  // call multiple times.
-  account_manager->UpsertAccount(
-      ::account_manager::AccountKey{
-          account_id.GetObjGuid(),
-          account_manager::AccountType::kActiveDirectory},
-      account_id.GetUserEmail(),
-      account_manager::AccountManager::kActiveDirectoryDummyToken);
-}
-
-}  // namespace
-
-AuthPolicyCredentialsManager::AuthPolicyCredentialsManager(Profile* profile)
-    : profile_(profile),
-      kerberos_files_handler_(base::BindRepeating(
-          &AuthPolicyCredentialsManager::GetUserKerberosFiles,
-          base::Unretained(this))) {
-  const user_manager::User* user =
-      ProfileHelper::Get()->GetUserByProfile(profile);
-  CHECK(user && user->IsActiveDirectoryUser());
-  StartObserveNetwork();
-  account_id_ = user->GetAccountId();
-  GetUserStatus();
-  GetUserKerberosFiles();
-
-  // Connecting to the signal sent by authpolicyd notifying that Kerberos files
-  // have changed.
-  AuthPolicyClient::Get()->ConnectToSignal(
-      authpolicy::kUserKerberosFilesChangedSignal,
-      base::BindRepeating(
-          &AuthPolicyCredentialsManager::OnUserKerberosFilesChangedCallback,
-          weak_factory_.GetWeakPtr()),
-      base::BindOnce(&AuthPolicyCredentialsManager::OnSignalConnectedCallback,
-                     weak_factory_.GetWeakPtr()));
-
-  SetupAccountManager(profile, user->GetAccountId());
-}
-
-AuthPolicyCredentialsManager::~AuthPolicyCredentialsManager() {}
-
-void AuthPolicyCredentialsManager::Shutdown() {
-  StopObserveNetwork();
-}
-
-void AuthPolicyCredentialsManager::DefaultNetworkChanged(
-    const NetworkState* network) {
-  GetUserStatusIfConnected(network);
-}
-
-void AuthPolicyCredentialsManager::NetworkConnectionStateChanged(
-    const NetworkState* network) {
-  GetUserStatusIfConnected(network);
-}
-
-void AuthPolicyCredentialsManager::OnShuttingDown() {
-  StopObserveNetwork();
-}
-
-KerberosFilesHandler*
-AuthPolicyCredentialsManager::GetKerberosFilesHandlerForTesting() {
-  return &kerberos_files_handler_;
-}
-
-void AuthPolicyCredentialsManager::GetUserStatus() {
-  DCHECK(!is_get_status_in_progress_);
-  is_get_status_in_progress_ = true;
-  rerun_get_status_on_error_ = false;
-  scheduled_get_user_status_call_.Cancel();
-  authpolicy::GetUserStatusRequest request;
-  request.set_user_principal_name(account_id_.GetUserEmail());
-  request.set_account_id(account_id_.GetObjGuid());
-  AuthPolicyClient::Get()->GetUserStatus(
-      request,
-      base::BindOnce(&AuthPolicyCredentialsManager::OnGetUserStatusCallback,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void AuthPolicyCredentialsManager::OnGetUserStatusCallback(
-    authpolicy::ErrorType error,
-    const authpolicy::ActiveDirectoryUserStatus& user_status) {
-  DCHECK(is_get_status_in_progress_);
-  is_get_status_in_progress_ = false;
-  ScheduleGetUserStatus();
-  last_error_ = error;
-  if (error != authpolicy::ERROR_NONE) {
-    DLOG(ERROR) << "GetUserStatus failed with " << error;
-    if (rerun_get_status_on_error_) {
-      rerun_get_status_on_error_ = false;
-      GetUserStatus();
-    }
-    return;
-  }
-  rerun_get_status_on_error_ = false;
-
-  // user_status.account_info() is missing if the TGT is invalid.
-  if (user_status.has_account_info()) {
-    CHECK(user_status.account_info().account_id() == account_id_.GetObjGuid());
-    UpdateDisplayAndGivenName(user_status.account_info());
-  }
-
-  // user_status.password_status() is missing if the TGT is invalid or device is
-  // offline.
-  bool force_online_signin = false;
-  if (user_status.has_password_status()) {
-    switch (user_status.password_status()) {
-      case authpolicy::ActiveDirectoryUserStatus::PASSWORD_VALID:
-        break;
-      case authpolicy::ActiveDirectoryUserStatus::PASSWORD_EXPIRED:
-        ShowNotification(IDS_ACTIVE_DIRECTORY_PASSWORD_EXPIRED);
-        force_online_signin = true;
-        break;
-      case authpolicy::ActiveDirectoryUserStatus::PASSWORD_CHANGED:
-        ShowNotification(IDS_ACTIVE_DIRECTORY_PASSWORD_CHANGED);
-        force_online_signin = true;
-        break;
-    }
-  }
-
-  // user_status.tgt_status() is always present.
-  DCHECK(user_status.has_tgt_status());
-  switch (user_status.tgt_status()) {
-    case authpolicy::ActiveDirectoryUserStatus::TGT_VALID:
-      break;
-    case authpolicy::ActiveDirectoryUserStatus::TGT_EXPIRED:
-    case authpolicy::ActiveDirectoryUserStatus::TGT_NOT_FOUND:
-      ShowNotification(IDS_ACTIVE_DIRECTORY_REFRESH_AUTH_TOKEN);
-      break;
-  }
-
-  user_manager::UserManager::Get()->SaveForceOnlineSignin(account_id_,
-                                                          force_online_signin);
-}
-
-void AuthPolicyCredentialsManager::GetUserKerberosFiles() {
-  AuthPolicyClient::Get()->GetUserKerberosFiles(
-      account_id_.GetObjGuid(),
-      base::BindOnce(
-          &AuthPolicyCredentialsManager::OnGetUserKerberosFilesCallback,
-          weak_factory_.GetWeakPtr()));
-}
-
-void AuthPolicyCredentialsManager::OnGetUserKerberosFilesCallback(
-    authpolicy::ErrorType error,
-    const authpolicy::KerberosFiles& kerberos_files) {
-  auto nullstr = absl::optional<std::string>();
-  kerberos_files_handler_.SetFiles(
-      kerberos_files.has_krb5cc() ? kerberos_files.krb5cc() : nullstr,
-      kerberos_files.has_krb5conf() ? kerberos_files.krb5conf() : nullstr);
-}
-
-void AuthPolicyCredentialsManager::ScheduleGetUserStatus() {
-  // Unretained is safe here because it is a CancelableOnceClosure and owned by
-  // this object.
-  scheduled_get_user_status_call_.Reset(base::BindOnce(
-      &AuthPolicyCredentialsManager::GetUserStatus, base::Unretained(this)));
-  // TODO(rsorokin): This does not re-schedule after wake from sleep
-  // (and thus the maximal interval between two calls can be (sleep time +
-  // kGetUserStatusCallsInterval)) (see crbug.com/726672).
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE, scheduled_get_user_status_call_.callback(),
-      kGetUserStatusCallsInterval);
-}
-
-void AuthPolicyCredentialsManager::StartObserveNetwork() {
-  DCHECK(NetworkHandler::IsInitialized());
-  if (is_observing_network_)
-    return;
-  is_observing_network_ = true;
-  network_state_handler_observer_.Observe(
-      NetworkHandler::Get()->network_state_handler());
-}
-
-void AuthPolicyCredentialsManager::StopObserveNetwork() {
-  if (!is_observing_network_)
-    return;
-  DCHECK(NetworkHandler::IsInitialized());
-  is_observing_network_ = false;
-  network_state_handler_observer_.Reset();
-}
-
-void AuthPolicyCredentialsManager::UpdateDisplayAndGivenName(
-    const authpolicy::ActiveDirectoryAccountInfo& account_info) {
-  if (display_name_ == account_info.display_name() &&
-      given_name_ == account_info.given_name()) {
-    return;
-  }
-  display_name_ = account_info.display_name();
-  given_name_ = account_info.given_name();
-  user_manager::UserManager::Get()->UpdateUserAccountData(
-      account_id_,
-      user_manager::UserManager::UserAccountData(
-          base::UTF8ToUTF16(display_name_), base::UTF8ToUTF16(given_name_),
-          std::string() /* locale */));
-}
-
-void AuthPolicyCredentialsManager::ShowNotification(int message_id) {
-  if (shown_notifications_.count(message_id) > 0)
-    return;
-
-  message_center::RichNotificationData data;
-  data.buttons.push_back(message_center::ButtonInfo(
-      l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_BUTTON)));
-
-  const std::string notification_id = kProfileSigninNotificationId +
-                                      profile_->GetProfileUserName() +
-                                      base::NumberToString(message_id);
-  message_center::NotifierId notifier_id(
-      message_center::NotifierType::SYSTEM_COMPONENT,
-      kProfileSigninNotificationId,
-      NotificationCatalogName::kAuthpolicyCredentialsError);
-
-  // Set |profile_id| for multi-user notification blocker.
-  notifier_id.profile_id = profile_->GetProfileUserName();
-
-  auto delegate =
-      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
-          base::BindRepeating([](absl::optional<int> button_index) {
-            chrome::AttemptUserExit();
-          }));
-
-  message_center::Notification notification = CreateSystemNotification(
-      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
-      l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_BUBBLE_VIEW_TITLE),
-      l10n_util::GetStringUTF16(message_id),
-      l10n_util::GetStringUTF16(IDS_SIGNIN_ERROR_DISPLAY_SOURCE),
-      GURL(notification_id), notifier_id, data, std::move(delegate),
-      vector_icons::kNotificationWarningIcon,
-      message_center::SystemNotificationWarningLevel::WARNING);
-  notification.SetSystemPriority();
-
-  // Add the notification.
-  NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
-      NotificationHandler::Type::TRANSIENT, notification,
-      /*metadata=*/nullptr);
-  shown_notifications_.insert(message_id);
-}
-
-void AuthPolicyCredentialsManager::GetUserStatusIfConnected(
-    const NetworkState* network) {
-  if (!network || !network->IsConnectedState())
-    return;
-  if (is_get_status_in_progress_) {
-    rerun_get_status_on_error_ = true;
-    return;
-  }
-  if (last_error_ != authpolicy::ERROR_NONE)
-    GetUserStatus();
-}
-
-void AuthPolicyCredentialsManager::OnUserKerberosFilesChangedCallback(
-    dbus::Signal* signal) {
-  DCHECK_EQ(signal->GetInterface(), authpolicy::kAuthPolicyInterface);
-  DCHECK_EQ(signal->GetMember(), authpolicy::kUserKerberosFilesChangedSignal);
-  GetUserKerberosFiles();
-}
-
-void AuthPolicyCredentialsManager::OnSignalConnectedCallback(
-    const std::string& interface_name,
-    const std::string& signal_name,
-    bool success) {
-  DCHECK_EQ(interface_name, authpolicy::kAuthPolicyInterface);
-  DCHECK_EQ(signal_name, authpolicy::kUserKerberosFilesChangedSignal);
-  DCHECK(success);
-}
-
-// static
-AuthPolicyCredentialsManagerFactory*
-AuthPolicyCredentialsManagerFactory::GetInstance() {
-  return base::Singleton<AuthPolicyCredentialsManagerFactory>::get();
-}
-
-AuthPolicyCredentialsManagerFactory::AuthPolicyCredentialsManagerFactory()
-    : ProfileKeyedServiceFactory(
-          "AuthPolicyCredentialsManager",
-          ProfileSelections::Builder()
-              .WithRegular(ProfileSelection::kOriginalOnly)
-              // TODO(crbug.com/1418376): Check if this service is needed in
-              // Guest mode.
-              .WithGuest(ProfileSelection::kOriginalOnly)
-              .Build()) {}
-
-AuthPolicyCredentialsManagerFactory::~AuthPolicyCredentialsManagerFactory() {}
-
-bool AuthPolicyCredentialsManagerFactory::ServiceIsCreatedWithBrowserContext()
-    const {
-  return true;
-}
-
-KeyedService* AuthPolicyCredentialsManagerFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-  // UserManager is usually not initialized in tests.
-  if (!user_manager::UserManager::IsInitialized())
-    return nullptr;
-  Profile* profile = Profile::FromBrowserContext(context);
-  const user_manager::User* user =
-      ProfileHelper::Get()->GetUserByProfile(profile);
-  if (!user || !user->IsActiveDirectoryUser())
-    return nullptr;
-  return new AuthPolicyCredentialsManager(profile);
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h b/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h
deleted file mode 100644
index 9d377f0..0000000
--- a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2017 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_ASH_AUTHPOLICY_AUTHPOLICY_CREDENTIALS_MANAGER_H_
-#define CHROME_BROWSER_ASH_AUTHPOLICY_AUTHPOLICY_CREDENTIALS_MANAGER_H_
-
-#include <set>
-#include <string>
-
-#include "base/cancelable_callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/scoped_observation.h"
-#include "chrome/browser/ash/authpolicy/kerberos_files_handler.h"
-#include "chrome/browser/profiles/profile_keyed_service_factory.h"
-#include "chromeos/ash/components/dbus/authpolicy/active_directory_info.pb.h"
-#include "chromeos/ash/components/network/network_state_handler_observer.h"
-#include "components/account_id/account_id.h"
-#include "components/keyed_service/core/keyed_service.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
-
-class Profile;
-
-namespace authpolicy {
-class ActiveDirectoryUserStatus;
-}  // namespace authpolicy
-
-namespace base {
-template <typename T>
-struct DefaultSingletonTraits;
-}  // namespace base
-
-namespace dbus {
-class Signal;
-}  // namespace dbus
-
-namespace ash {
-
-class NetworkStateHandler;
-
-// A service responsible for tracking user credential status. Created for each
-// Active Directory user profile.
-class AuthPolicyCredentialsManager : public KeyedService,
-                                     public NetworkStateHandlerObserver {
- public:
-  explicit AuthPolicyCredentialsManager(Profile* profile);
-
-  AuthPolicyCredentialsManager(const AuthPolicyCredentialsManager&) = delete;
-  AuthPolicyCredentialsManager& operator=(const AuthPolicyCredentialsManager&) =
-      delete;
-
-  ~AuthPolicyCredentialsManager() override;
-
-  // KeyedService overrides.
-  void Shutdown() override;
-
-  // NetworkStateHandlerObserver overrides.
-  void DefaultNetworkChanged(const NetworkState* network) override;
-  void NetworkConnectionStateChanged(const NetworkState* network) override;
-  void OnShuttingDown() override;
-
-  KerberosFilesHandler* GetKerberosFilesHandlerForTesting();
-
- private:
-  friend class AuthPolicyCredentialsManagerTest;
-  // Calls AuthPolicyClient::GetUserStatus method.
-  void GetUserStatus();
-
-  // See AuthPolicyClient::GetUserStatusCallback.
-  void OnGetUserStatusCallback(
-      authpolicy::ErrorType error,
-      const authpolicy::ActiveDirectoryUserStatus& user_status);
-
-  // Calls AuthPolicyClient::GetUserKerberosFiles.
-  void GetUserKerberosFiles();
-
-  // See AuthPolicyClient::GetUserKerberosFilesCallback.
-  void OnGetUserKerberosFilesCallback(
-      authpolicy::ErrorType error,
-      const authpolicy::KerberosFiles& kerberos_files);
-
-  // Post delayed task to call GetUserStatus in the future.
-  void ScheduleGetUserStatus();
-
-  // Add itself as network observer.
-  void StartObserveNetwork();
-  // Remove itself as network observer.
-  void StopObserveNetwork();
-
-  // Update display and given name in case it has changed.
-  void UpdateDisplayAndGivenName(
-      const authpolicy::ActiveDirectoryAccountInfo& account_info);
-
-  // Shows user notification to sign out/sign in.
-  void ShowNotification(int message_id);
-
-  // Call GetUserStatus if |network_state| is connected and the previous call
-  // failed.
-  void GetUserStatusIfConnected(const NetworkState* network_state);
-
-  // Callback for 'UserKerberosFilesChanged' D-Bus signal sent by authpolicyd.
-  void OnUserKerberosFilesChangedCallback(dbus::Signal* signal);
-
-  // Called after connected to 'UserKerberosFilesChanged' signal.
-  void OnSignalConnectedCallback(const std::string& interface_name,
-                                 const std::string& signal_name,
-                                 bool success);
-
-  const raw_ptr<Profile, ExperimentalAsh> profile_;
-  AccountId account_id_;
-  std::string display_name_;
-  std::string given_name_;
-  bool is_get_status_in_progress_ = false;
-  bool rerun_get_status_on_error_ = false;
-  bool is_observing_network_ = false;
-  KerberosFilesHandler kerberos_files_handler_;
-
-  base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
-      network_state_handler_observer_{this};
-
-  // Stores message ids of shown notifications. Each notification is shown at
-  // most once.
-  std::set<int> shown_notifications_;
-  authpolicy::ErrorType last_error_ = authpolicy::ERROR_NONE;
-  base::CancelableOnceClosure scheduled_get_user_status_call_;
-
-  base::WeakPtrFactory<AuthPolicyCredentialsManager> weak_factory_{this};
-};
-
-// Singleton that owns all AuthPolicyCredentialsManagers and associates them
-// with BrowserContexts.
-class AuthPolicyCredentialsManagerFactory : public ProfileKeyedServiceFactory {
- public:
-  static AuthPolicyCredentialsManagerFactory* GetInstance();
-
-  AuthPolicyCredentialsManagerFactory(
-      const AuthPolicyCredentialsManagerFactory&) = delete;
-  AuthPolicyCredentialsManagerFactory& operator=(
-      const AuthPolicyCredentialsManagerFactory&) = delete;
-
- private:
-  friend struct base::DefaultSingletonTraits<
-      AuthPolicyCredentialsManagerFactory>;
-  friend class AuthPolicyCredentialsManagerTest;
-  friend class ExistingUserControllerActiveDirectoryTest;
-
-  AuthPolicyCredentialsManagerFactory();
-  ~AuthPolicyCredentialsManagerFactory() override;
-
-  bool ServiceIsCreatedWithBrowserContext() const override;
-
-  // Returns nullptr in case profile is not Active Directory. Otherwise returns
-  // valid AuthPolicyCredentialsManager.
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override;
-};
-
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_AUTHPOLICY_AUTHPOLICY_CREDENTIALS_MANAGER_H_
diff --git a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager_unittest.cc b/chrome/browser/ash/authpolicy/authpolicy_credentials_manager_unittest.cc
deleted file mode 100644
index e24b9ee4..0000000
--- a/chrome/browser/ash/authpolicy/authpolicy_credentials_manager_unittest.cc
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2017 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/ash/authpolicy/authpolicy_credentials_manager.h"
-
-#include <memory>
-
-#include "base/memory/raw_ptr.h"
-#include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/notifications/notification_display_service_tester.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/test/base/scoped_testing_local_state.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
-#include "chromeos/ash/components/network/network_handler_test_helper.h"
-#include "components/user_manager/scoped_user_manager.h"
-#include "components/user_manager/user_manager.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace ash {
-
-namespace {
-
-constexpr char kProfileSigninNotificationId[] = "chrome://settings/signin/";
-constexpr char kProfileEmail[] = "user@example.com";
-constexpr char kDisplayName[] = "DisplayName";
-constexpr char16_t kDisplayName16[] = u"DisplayName";
-constexpr char kGivenName[] = "Given Name";
-constexpr char16_t kGivenName16[] = u"Given Name";
-
-}  // namespace
-
-class AuthPolicyCredentialsManagerTest : public testing::Test {
- public:
-  AuthPolicyCredentialsManagerTest()
-      : local_state_(TestingBrowserProcess::GetGlobal()),
-        user_manager_enabler_(std::make_unique<FakeChromeUserManager>()) {}
-
-  AuthPolicyCredentialsManagerTest(const AuthPolicyCredentialsManagerTest&) =
-      delete;
-  AuthPolicyCredentialsManagerTest& operator=(
-      const AuthPolicyCredentialsManagerTest&) = delete;
-
-  ~AuthPolicyCredentialsManagerTest() override = default;
-
-  void SetUp() override {
-    AuthPolicyClient::InitializeFake();
-    fake_authpolicy_client()->DisableOperationDelayForTesting();
-
-    TestingProfile::Builder profile_builder;
-    profile_builder.SetProfileName(kProfileEmail);
-    account_id_ =
-        AccountId::AdFromUserEmailObjGuid(kProfileEmail, "1234567890");
-    auto* user = fake_user_manager()->AddUser(account_id_);
-
-    base::RunLoop run_loop;
-    fake_authpolicy_client()->set_on_get_status_closure(run_loop.QuitClosure());
-
-    profile_ = profile_builder.Build();
-    display_service_ =
-        std::make_unique<NotificationDisplayServiceTester>(profile());
-
-    authpolicy_credentials_manager_ =
-        static_cast<AuthPolicyCredentialsManager*>(
-            AuthPolicyCredentialsManagerFactory::GetInstance()
-                ->GetServiceForBrowserContext(profile(), false /* create */));
-    EXPECT_TRUE(authpolicy_credentials_manager_);
-
-    run_loop.Run();
-    EXPECT_FALSE(user->force_online_signin());
-  }
-
-  void TearDown() override {
-    profile_.reset();
-    AuthPolicyClient::Shutdown();
-  }
-
- protected:
-  AccountId& account_id() { return account_id_; }
-  TestingProfile* profile() { return profile_.get(); }
-  AuthPolicyCredentialsManager* authpolicy_credentials_manager() {
-    return authpolicy_credentials_manager_;
-  }
-  FakeAuthPolicyClient* fake_authpolicy_client() const {
-    return FakeAuthPolicyClient::Get();
-  }
-
-  FakeChromeUserManager* fake_user_manager() {
-    return static_cast<FakeChromeUserManager*>(
-        user_manager::UserManager::Get());
-  }
-
-  int GetNumberOfNotifications() {
-    return display_service_
-        ->GetDisplayedNotificationsForType(NotificationHandler::Type::TRANSIENT)
-        .size();
-  }
-
-  void CancelNotificationById(int message_id) {
-    const std::string notification_id = kProfileSigninNotificationId +
-                                        profile()->GetProfileUserName() +
-                                        base::NumberToString(message_id);
-    EXPECT_TRUE(display_service_->GetNotification(notification_id));
-    display_service_->RemoveNotification(NotificationHandler::Type::TRANSIENT,
-                                         notification_id, false);
-  }
-
-  void CallGetUserStatusAndWait() {
-    base::RunLoop run_loop;
-    fake_authpolicy_client()->set_on_get_status_closure(run_loop.QuitClosure());
-    authpolicy_credentials_manager()->GetUserStatus();
-    run_loop.Run();
-  }
-
-  content::BrowserTaskEnvironment task_environment_;
-  ScopedTestingLocalState local_state_;
-
-  NetworkHandlerTestHelper network_handler_test_helper_;
-  AccountId account_id_;
-  std::unique_ptr<TestingProfile> profile_;
-
-  // Owned by AuthPolicyCredentialsManagerFactory.
-  raw_ptr<AuthPolicyCredentialsManager, ExperimentalAsh>
-      authpolicy_credentials_manager_;
-  user_manager::ScopedUserManager user_manager_enabler_;
-
-  std::unique_ptr<NotificationDisplayServiceTester> display_service_;
-};
-
-// Tests saving display and given name into user manager. No error means no
-// notifications are shown.
-TEST_F(AuthPolicyCredentialsManagerTest, SaveNames) {
-  fake_authpolicy_client()->set_display_name(kDisplayName);
-  fake_authpolicy_client()->set_given_name(kGivenName);
-
-  CallGetUserStatusAndWait();
-  EXPECT_EQ(0, GetNumberOfNotifications());
-  const auto* user = fake_user_manager()->FindUser(account_id());
-  ASSERT_TRUE(user);
-  EXPECT_EQ(kDisplayName16, user->display_name());
-  EXPECT_EQ(kGivenName16, user->GetGivenName());
-}
-
-// Tests notification is shown at most once for the same error.
-TEST_F(AuthPolicyCredentialsManagerTest, ShowSameNotificationOnce) {
-  // In case of expired password save to force online signin and show
-  // notification.
-  fake_authpolicy_client()->set_password_status(
-      authpolicy::ActiveDirectoryUserStatus::PASSWORD_EXPIRED);
-  CallGetUserStatusAndWait();
-  EXPECT_EQ(1, GetNumberOfNotifications());
-  EXPECT_TRUE(
-      fake_user_manager()->FindUser(account_id())->force_online_signin());
-  CancelNotificationById(IDS_ACTIVE_DIRECTORY_PASSWORD_EXPIRED);
-
-  // Do not show the same notification twice.
-  CallGetUserStatusAndWait();
-  EXPECT_EQ(0, GetNumberOfNotifications());
-  EXPECT_TRUE(
-      fake_user_manager()->FindUser(account_id())->force_online_signin());
-}
-
-// Tests both notifications are shown if different errors occurs.
-TEST_F(AuthPolicyCredentialsManagerTest, ShowDifferentNotifications) {
-  // In case of expired password save to force online signin and show
-  // notification.
-  fake_authpolicy_client()->set_password_status(
-      authpolicy::ActiveDirectoryUserStatus::PASSWORD_CHANGED);
-  fake_authpolicy_client()->set_tgt_status(
-      authpolicy::ActiveDirectoryUserStatus::TGT_EXPIRED);
-  CallGetUserStatusAndWait();
-  EXPECT_EQ(2, GetNumberOfNotifications());
-  EXPECT_TRUE(
-      fake_user_manager()->FindUser(account_id())->force_online_signin());
-  CancelNotificationById(IDS_ACTIVE_DIRECTORY_PASSWORD_CHANGED);
-  CancelNotificationById(IDS_ACTIVE_DIRECTORY_REFRESH_AUTH_TOKEN);
-  EXPECT_EQ(0, GetNumberOfNotifications());
-}
-
-// Tests invalid TGT status does not force online signin but still shows
-// a notification.
-TEST_F(AuthPolicyCredentialsManagerTest, InvalidTGTDoesntForceOnlineSignin) {
-  fake_authpolicy_client()->set_tgt_status(
-      authpolicy::ActiveDirectoryUserStatus::TGT_EXPIRED);
-  CallGetUserStatusAndWait();
-  EXPECT_FALSE(
-      fake_user_manager()->FindUser(account_id())->force_online_signin());
-  EXPECT_EQ(1, GetNumberOfNotifications());
-  CancelNotificationById(IDS_ACTIVE_DIRECTORY_REFRESH_AUTH_TOKEN);
-  EXPECT_EQ(0, GetNumberOfNotifications());
-}
-
-// Tests successfull case does not show any notification and does not force
-// online signin.
-TEST_F(AuthPolicyCredentialsManagerTest, Success_NoNotifications) {
-  CallGetUserStatusAndWait();
-  EXPECT_EQ(0, GetNumberOfNotifications());
-  EXPECT_FALSE(
-      fake_user_manager()->FindUser(account_id())->force_online_signin());
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/browser_context_keyed_service_factories.cc b/chrome/browser/ash/browser_context_keyed_service_factories.cc
index ae3bc84..653d3ff 100644
--- a/chrome/browser/ash/browser_context_keyed_service_factories.cc
+++ b/chrome/browser/ash/browser_context_keyed_service_factories.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/ash/app_restore/full_restore_service_factory.h"
 #include "chrome/browser/ash/apps/apk_web_app_service_factory.h"
 #include "chrome/browser/ash/arc/session/arc_service_launcher.h"
-#include "chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h"
 #include "chrome/browser/ash/bluetooth/debug_logs_manager_factory.h"
 #include "chrome/browser/ash/borealis/borealis_service_factory.h"
 #include "chrome/browser/ash/bruschetta/bruschetta_service_factory.h"
@@ -157,7 +156,6 @@
   ash::quick_unlock::QuickUnlockFactory::GetInstance();
   ash::settings::OsSettingsHatsManagerFactory::GetInstance();
   ash::settings::OsSettingsManagerFactory::GetInstance();
-  AuthPolicyCredentialsManagerFactory::GetInstance();
   ax::AccessibilityServiceRouterFactory::EnsureFactoryBuilt();
   bluetooth::DebugLogsManagerFactory::GetInstance();
   borealis::BorealisServiceFactory::GetInstance();
diff --git a/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc b/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
index 209c3b9..1df7d00a 100644
--- a/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
+++ b/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
@@ -330,7 +330,7 @@
 TEST_F(VmCameraMicManagerPrivacyIndicatorsTest, PrivacyIndicatorsView) {
   // Make sure privacy indicators work on multiple displays.
   display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
-      .UpdateDisplay("800x800,801+0-800x800");
+      .UpdateDisplay("800x700,801+0-800x700");
 
   SetCameraAccessing(kPluginVm, false);
   SetCameraPrivacyIsOn(false);
diff --git a/chrome/browser/ash/dbus/ash_dbus_helper.cc b/chrome/browser/ash/dbus/ash_dbus_helper.cc
index a14029d7..0359028 100644
--- a/chrome/browser/ash/dbus/ash_dbus_helper.cc
+++ b/chrome/browser/ash/dbus/ash_dbus_helper.cc
@@ -27,7 +27,6 @@
 #include "chromeos/ash/components/dbus/attestation/attestation_client.h"
 #include "chromeos/ash/components/dbus/audio/cras_audio_client.h"
 #include "chromeos/ash/components/dbus/audio/floss_media_client.h"
-#include "chromeos/ash/components/dbus/authpolicy/authpolicy_client.h"
 #include "chromeos/ash/components/dbus/biod/biod_client.h"
 #include "chromeos/ash/components/dbus/cdm_factory_daemon/cdm_factory_daemon_client.h"
 #include "chromeos/ash/components/dbus/cec_service/cec_service_client.h"
@@ -144,7 +143,6 @@
   InitializeDBusClient<ArcQuotaClient>(bus);
   InitializeDBusClient<ArcVmDataMigratorClient>(bus);
   InitializeDBusClient<AttestationClient>(bus);
-  InitializeDBusClient<AuthPolicyClient>(bus);
   InitializeDBusClient<BiodClient>(bus);  // For device::Fingerprint.
   InitializeDBusClient<CdmFactoryDaemonClient>(bus);
   InitializeDBusClient<CecServiceClient>(bus);
@@ -329,7 +327,6 @@
   CecServiceClient::Shutdown();
   CdmFactoryDaemonClient::Shutdown();
   BiodClient::Shutdown();
-  AuthPolicyClient::Shutdown();
   AttestationClient::Shutdown();
   ArcVmDataMigratorClient::Shutdown();
   ArcQuotaClient::Shutdown();
diff --git a/chrome/browser/ash/guest_os/guest_id.cc b/chrome/browser/ash/guest_os/guest_id.cc
index eab0f64d..f21bc80 100644
--- a/chrome/browser/ash/guest_os/guest_id.cc
+++ b/chrome/browser/ash/guest_os/guest_id.cc
@@ -117,16 +117,16 @@
                          const GuestId& container_id,
                          base::Value::Dict properties) {
   ScopedListPrefUpdate updater(profile->GetPrefs(), prefs::kGuestOsContainers);
-  if (base::ranges::any_of(*updater, [&](const auto& dict) {
+  if (base::ranges::any_of(*updater, [&container_id](const auto& dict) {
         return MatchContainerDict(dict, container_id);
       })) {
     return;
   }
 
-  base::Value new_container{container_id.ToDictValue()};
-  for (const auto item : properties) {
-    if (base::Contains(*kPropertiesAllowList, item.first)) {
-      new_container.SetKey(std::move(item.first), std::move(item.second));
+  base::Value::Dict new_container = container_id.ToDictValue();
+  for (auto [key, value] : properties) {
+    if (base::Contains(*kPropertiesAllowList, key)) {
+      new_container.Set(key, std::move(value));
     }
   }
   updater->Append(std::move(new_container));
@@ -174,7 +174,7 @@
   });
   if (it != updater->end()) {
     if (base::Contains(*kPropertiesAllowList, key)) {
-      it->SetKey(key, std::move(value));
+      it->GetDict().Set(key, std::move(value));
     } else {
       LOG(ERROR) << "Ignoring disallowed property: " << key;
     }
diff --git a/chrome/browser/ash/login/users/user_manager_unittest.cc b/chrome/browser/ash/login/users/user_manager_unittest.cc
index 9b70e5d..21a3d70d 100644
--- a/chrome/browser/ash/login/users/user_manager_unittest.cc
+++ b/chrome/browser/ash/login/users/user_manager_unittest.cc
@@ -52,6 +52,13 @@
 
 namespace {
 
+const AccountId kOwnerAccountId =
+    AccountId::FromUserEmailGaiaId("owner@example.com", "1234567890");
+const AccountId kAccountId0 =
+    AccountId::FromUserEmailGaiaId("user0@example.com", "0123456789");
+const AccountId kAccountId1 =
+    AccountId::FromUserEmailGaiaId("user1@example.com", "9012345678");
+
 constexpr char kDeviceLocalAccountId[] = "device_local_account";
 
 AccountId CreateDeviceLocalKioskAppAccountId(const std::string& account_id) {
@@ -59,11 +66,6 @@
       kDeviceLocalAccountId, policy::DeviceLocalAccount::TYPE_KIOSK_APP));
 }
 
-constexpr char kOwnerEmail[] = "owner@example.com";
-
-const AccountId kOwnerAccountId =
-    AccountId::FromUserEmailGaiaId(kOwnerEmail, "1234567890");
-
 }  // namespace
 
 class UserManagerObserverTest : public user_manager::UserManager::Observer {
@@ -233,13 +235,6 @@
     GetChromeUserManager()->RetrieveTrustedDevicePolicies();
   }
 
-  const AccountId owner_account_id_at_invalid_domain_ =
-      AccountId::FromUserEmailGaiaId("owner@invalid.domain", "1234567890");
-  const AccountId account_id0_at_invalid_domain_ =
-      AccountId::FromUserEmailGaiaId("user0@invalid.domain", "0123456789");
-  const AccountId account_id1_at_invalid_domain_ =
-      AccountId::FromUserEmailGaiaId("user1@invalid.domain", "9012345678");
-
  protected:
   // The call chain
   // - `ProfileRequiresPolicyUnknown`
@@ -276,12 +271,12 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ false,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   EXPECT_FALSE(IsEphemeralAccountId(EmptyAccountId()));
 
-  EXPECT_EQ(GetUserManagerOwnerId(), owner_account_id_at_invalid_domain_);
+  EXPECT_EQ(GetUserManagerOwnerId(), kOwnerAccountId);
 }
 
 // Tests that `IsEphemeralAccountId(account_id)` returns false when `account_id`
@@ -304,7 +299,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   EXPECT_TRUE(IsEphemeralAccountId(EmptyAccountId()));
@@ -322,7 +317,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kFollowDeviceWidePolicy);
@@ -331,7 +326,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ false,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 }
@@ -347,7 +342,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kUnset);
@@ -356,7 +351,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ false,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
   EXPECT_FALSE(IsEphemeralAccountId(account_id));
 }
@@ -372,7 +367,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kDisable);
@@ -393,7 +388,7 @@
 
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ false,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   SetDeviceLocalKioskAppAccount(
       kDeviceLocalAccountId, "",
       policy::DeviceLocalAccount::EphemeralMode::kEnable);
@@ -408,19 +403,17 @@
       CreateMockRemoveUserManager();
 
   // Create owner account and login in.
-  user_manager->UserLoggedIn(owner_account_id_at_invalid_domain_,
-                             owner_account_id_at_invalid_domain_.GetUserEmail(),
+  user_manager->UserLoggedIn(kOwnerAccountId, kOwnerAccountId.GetUserEmail(),
                              false /* browser_restart */, false /* is_child */);
 
   // Create non-owner account  and login in.
-  user_manager->UserLoggedIn(account_id0_at_invalid_domain_,
-                             account_id0_at_invalid_domain_.GetUserEmail(),
+  user_manager->UserLoggedIn(kAccountId0, kAccountId0.GetUserEmail(),
                              false /* browser_restart */, false /* is_child */);
 
   ASSERT_EQ(2U, user_manager->GetUsers().size());
 
   // Removing logged-in account is unacceptable.
-  user_manager->RemoveUser(account_id0_at_invalid_domain_,
+  user_manager->RemoveUser(kAccountId0,
                            user_manager::UserRemovalReason::UNKNOWN);
   EXPECT_EQ(2U, user_manager->GetUsers().size());
 
@@ -434,22 +427,20 @@
   // Get a pointer to the user that will be removed.
   user_manager::User* user_to_remove = nullptr;
   for (user_manager::User* user : user_manager->GetUsers()) {
-    if (user->GetAccountId() == account_id0_at_invalid_domain_) {
+    if (user->GetAccountId() == kAccountId0) {
       user_to_remove = user;
       break;
     }
   }
   ASSERT_TRUE(user_to_remove);
-  ASSERT_EQ(account_id0_at_invalid_domain_, user_to_remove->GetAccountId());
+  ASSERT_EQ(kAccountId0, user_to_remove->GetAccountId());
 
   // Removing non-owner account is acceptable.
-  EXPECT_CALL(*user_manager,
-              AsyncRemoveCryptohome(account_id0_at_invalid_domain_))
-      .Times(1);
+  EXPECT_CALL(*user_manager, AsyncRemoveCryptohome(kAccountId0)).Times(1);
 
   // Pass the account id of the user to be removed from the user list to verify
   // that a reference to the account id will not be used after user removal.
-  user_manager->RemoveUser(account_id0_at_invalid_domain_,
+  user_manager->RemoveUser(kAccountId0,
                            user_manager::UserRemovalReason::UNKNOWN);
   testing::Mock::VerifyAndClearExpectations(user_manager.get());
   EXPECT_EQ(1, observer_test.OnUserToBeRemovedCallCount());
@@ -457,11 +448,9 @@
   EXPECT_EQ(1U, user_manager->GetUsers().size());
 
   // Removing owner account is unacceptable.
-  EXPECT_CALL(*user_manager,
-              AsyncRemoveCryptohome(owner_account_id_at_invalid_domain_))
-      .Times(0);
+  EXPECT_CALL(*user_manager, AsyncRemoveCryptohome(kOwnerAccountId)).Times(0);
   observer_test.ResetCallCounts();
-  user_manager->RemoveUser(owner_account_id_at_invalid_domain_,
+  user_manager->RemoveUser(kOwnerAccountId,
                            user_manager::UserRemovalReason::UNKNOWN);
   testing::Mock::VerifyAndClearExpectations(user_manager.get());
   EXPECT_EQ(0, observer_test.OnUserToBeRemovedCallCount());
@@ -476,37 +465,34 @@
       SystemSaltGetter::RawSalt({1, 2, 3, 4, 5, 6, 7, 8}));
 
   user_manager::UserManager::Get()->UserLoggedIn(
-      owner_account_id_at_invalid_domain_,
-      owner_account_id_at_invalid_domain_.GetUserEmail(),
+      kOwnerAccountId, kOwnerAccountId.GetUserEmail(),
       false /* browser_restart */, false /* is_child */);
   ResetUserManager();
   user_manager::UserManager::Get()->UserLoggedIn(
-      account_id0_at_invalid_domain_,
-      owner_account_id_at_invalid_domain_.GetUserEmail(),
-      false /* browser_restart */, false /* is_child */);
+      kAccountId0, kAccountId0.GetUserEmail(), false /* browser_restart */,
+      false /* is_child */);
   ResetUserManager();
   user_manager::UserManager::Get()->UserLoggedIn(
-      account_id1_at_invalid_domain_,
-      owner_account_id_at_invalid_domain_.GetUserEmail(),
-      false /* browser_restart */, false /* is_child */);
+      kAccountId1, kAccountId1.GetUserEmail(), false /* browser_restart */,
+      false /* is_child */);
   ResetUserManager();
 
   const user_manager::UserList* users =
       &user_manager::UserManager::Get()->GetUsers();
   ASSERT_EQ(3U, users->size());
-  EXPECT_EQ((*users)[0]->GetAccountId(), account_id1_at_invalid_domain_);
-  EXPECT_EQ((*users)[1]->GetAccountId(), account_id0_at_invalid_domain_);
-  EXPECT_EQ((*users)[2]->GetAccountId(), owner_account_id_at_invalid_domain_);
+  EXPECT_EQ((*users)[0]->GetAccountId(), kAccountId1);
+  EXPECT_EQ((*users)[1]->GetAccountId(), kAccountId0);
+  EXPECT_EQ((*users)[2]->GetAccountId(), kOwnerAccountId);
 
   test_wallpaper_controller_.ClearCounts();
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   users = &user_manager::UserManager::Get()->GetUsers();
   EXPECT_EQ(1U, users->size());
-  EXPECT_EQ((*users)[0]->GetAccountId(), owner_account_id_at_invalid_domain_);
+  EXPECT_EQ((*users)[0]->GetAccountId(), kOwnerAccountId);
   // Verify that the wallpaper is removed when user is removed.
   EXPECT_EQ(2, test_wallpaper_controller_.remove_user_wallpaper_count());
 }
@@ -514,31 +500,28 @@
 TEST_F(UserManagerTest, RegularUserLoggedInAsEphemeral) {
   SetDeviceSettings(
       /* ephemeral_users_enabled= */ true,
-      /* owner= */ owner_account_id_at_invalid_domain_.GetUserEmail());
+      /* owner= */ kOwnerAccountId.GetUserEmail());
   RetrieveTrustedDevicePolicies();
 
   user_manager::UserManager::Get()->UserLoggedIn(
-      owner_account_id_at_invalid_domain_,
-      account_id0_at_invalid_domain_.GetUserEmail(),
+      kOwnerAccountId, kOwnerAccountId.GetUserEmail(),
       false /* browser_restart */, false /* is_child */);
   ResetUserManager();
   user_manager::UserManager::Get()->UserLoggedIn(
-      account_id0_at_invalid_domain_,
-      account_id0_at_invalid_domain_.GetUserEmail(),
-      false /* browser_restart */, false /* is_child */);
+      kAccountId0, kAccountId0.GetUserEmail(), false /* browser_restart */,
+      false /* is_child */);
   ResetUserManager();
 
   const user_manager::UserList* users =
       &user_manager::UserManager::Get()->GetUsers();
   EXPECT_EQ(1U, users->size());
-  EXPECT_EQ((*users)[0]->GetAccountId(), owner_account_id_at_invalid_domain_);
+  EXPECT_EQ((*users)[0]->GetAccountId(), kOwnerAccountId);
 }
 
 TEST_F(UserManagerTest, ScreenLockAvailability) {
   // Log in the user and create the profile.
   user_manager::UserManager::Get()->UserLoggedIn(
-      owner_account_id_at_invalid_domain_,
-      owner_account_id_at_invalid_domain_.GetUserEmail(),
+      kOwnerAccountId, kOwnerAccountId.GetUserEmail(),
       false /* browser_restart */, false /* is_child */);
   user_manager::User* const user =
       user_manager::UserManager::Get()->GetActiveUser();
@@ -560,12 +543,10 @@
 
 TEST_F(UserManagerTest, ProfileRequiresPolicyUnknown) {
   user_manager::UserManager::Get()->UserLoggedIn(
-      owner_account_id_at_invalid_domain_,
-      owner_account_id_at_invalid_domain_.GetUserEmail(), false, false);
+      kOwnerAccountId, kOwnerAccountId.GetUserEmail(), false, false);
   user_manager::KnownUser known_user(local_state_->Get());
-  EXPECT_EQ(
-      user_manager::ProfileRequiresPolicy::kUnknown,
-      known_user.GetProfileRequiresPolicy(owner_account_id_at_invalid_domain_));
+  EXPECT_EQ(user_manager::ProfileRequiresPolicy::kUnknown,
+            known_user.GetProfileRequiresPolicy(kOwnerAccountId));
   ResetUserManager();
 }
 
@@ -579,12 +560,12 @@
 
   // Save a user as an owner.
   user_manager::UserManager::Get()->RecordOwner(
-      AccountId::FromUserEmail(kOwnerEmail));
+      AccountId::FromUserEmail(kOwnerAccountId.GetUserEmail()));
 
   // Now `GetOwnerEmail` should return the email of the user above.
   owner = user_manager::UserManager::Get()->GetOwnerEmail();
   ASSERT_TRUE(owner.has_value());
-  EXPECT_EQ(owner.value(), kOwnerEmail);
+  EXPECT_EQ(owner.value(), kOwnerAccountId.GetUserEmail());
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.cc b/chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.cc
index 3f51083d..88c30ef 100644
--- a/chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.cc
+++ b/chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.cc
@@ -12,7 +12,6 @@
 #include "chrome/browser/ash/android_sms/android_sms_app_manager.h"
 #include "chrome/browser/ash/android_sms/android_sms_pairing_state_tracker_impl.h"
 #include "chrome/browser/ash/android_sms/android_sms_service_factory.h"
-#include "chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h"
 #include "chrome/browser/ash/cryptauth/gcm_device_info_provider_impl.h"
 #include "chrome/browser/ash/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/ash/multidevice_setup/auth_token_validator_factory.h"
diff --git a/chrome/browser/ash/printing/cups_proxy_service_manager.cc b/chrome/browser/ash/printing/cups_proxy_service_manager.cc
index 6fe43cf..3074394 100644
--- a/chrome/browser/ash/printing/cups_proxy_service_manager.cc
+++ b/chrome/browser/ash/printing/cups_proxy_service_manager.cc
@@ -6,24 +6,63 @@
 
 #include <memory>
 
+#include "base/check.h"
 #include "base/feature_list.h"
 #include "chrome/browser/ash/printing/cups_proxy_service_delegate_impl.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/services/cups_proxy/cups_proxy_service.h"
+#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "chromeos/ash/components/dbus/cups_proxy/cups_proxy_client.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
 #include "content/public/browser/browser_context.h"
 
 namespace ash {
 
-CupsProxyServiceManager::CupsProxyServiceManager() {
-  // Don't wait for the daemon if the feature is turned off anyway.
-  if (base::FeatureList::IsEnabled(features::kPluginVm)) {
-    CupsProxyClient::Get()->WaitForServiceToBeAvailable(
-        base::BindOnce(&CupsProxyServiceManager::OnDaemonAvailable,
-                       weak_factory_.GetWeakPtr()));
+namespace {
+
+// Returns true iff the primary profile has been created.
+bool IsPrimaryProfileCreated() {
+  if (!user_manager::UserManager::IsInitialized()) {
+    return false;
   }
+
+  const user_manager::User* primary_user =
+      user_manager::UserManager::Get()->GetPrimaryUser();
+  return primary_user && primary_user->is_profile_created();
 }
 
-CupsProxyServiceManager::~CupsProxyServiceManager() = default;
+}  // namespace
+
+CupsProxyServiceManager::CupsProxyServiceManager()
+    : profile_manager_(g_browser_process->profile_manager()) {
+  // Don't wait for the daemon or subscribe to ProfileManager if the feature is
+  // turned off anyway.
+  if (!base::FeatureList::IsEnabled(features::kPluginVm)) {
+    return;
+  }
+
+  // The primary profile might have been created already. If so, there's no
+  // need to subscribe to ProfileManager.
+  primary_profile_available_ = IsPrimaryProfileCreated();
+
+  if (!primary_profile_available_ && profile_manager_) {
+    profile_manager_->AddObserver(this);
+  }
+
+  CupsProxyClient::Get()->WaitForServiceToBeAvailable(base::BindOnce(
+      &CupsProxyServiceManager::OnDaemonAvailable, weak_factory_.GetWeakPtr()));
+}
+
+CupsProxyServiceManager::~CupsProxyServiceManager() {
+  if (profile_manager_) {
+    profile_manager_->RemoveObserver(this);
+  }
+
+  if (cups_proxy::CupsProxyService::GetInstance() != nullptr) {
+    cups_proxy::CupsProxyService::Shutdown();
+  }
+}
 
 void CupsProxyServiceManager::OnDaemonAvailable(bool daemon_available) {
   if (!daemon_available) {
@@ -31,6 +70,54 @@
     return;
   }
 
+  daemon_available_ = true;
+
+  MaybeSpawnCupsProxyService();
+}
+
+void CupsProxyServiceManager::OnProfileAdded(Profile* profile) {
+  if (!profile) {
+    return;
+  }
+
+  BrowserContextHelper* browser_context_helper = BrowserContextHelper::Get();
+  if (!browser_context_helper) {
+    return;
+  }
+
+  const user_manager::User* user =
+      browser_context_helper->GetUserByBrowserContext(profile);
+  if (!user) {
+    return;
+  }
+
+  DCHECK(user_manager::UserManager::IsInitialized());
+  if (!user_manager::UserManager::Get()->IsPrimaryUser(user)) {
+    return;
+  }
+
+  // Now that we've seen the primary profile, there's no need to keep our
+  // subscription to ProfileManager.
+  profile_manager_->RemoveObserver(this);
+  profile_manager_ = nullptr;
+
+  primary_profile_available_ = true;
+
+  MaybeSpawnCupsProxyService();
+}
+
+void CupsProxyServiceManager::OnProfileManagerDestroying() {
+  if (profile_manager_) {
+    profile_manager_->RemoveObserver(this);
+    profile_manager_ = nullptr;
+  }
+}
+
+void CupsProxyServiceManager::MaybeSpawnCupsProxyService() {
+  if (!primary_profile_available_ || !daemon_available_) {
+    return;
+  }
+
   // Attempt to start the service, which will then bootstrap a connection
   // with the daemon.
   cups_proxy::CupsProxyService::Spawn(
diff --git a/chrome/browser/ash/printing/cups_proxy_service_manager.h b/chrome/browser/ash/printing/cups_proxy_service_manager.h
index be74069..1e56861 100644
--- a/chrome/browser/ash/printing/cups_proxy_service_manager.h
+++ b/chrome/browser/ash/printing/cups_proxy_service_manager.h
@@ -6,6 +6,9 @@
 #define CHROME_BROWSER_ASH_PRINTING_CUPS_PROXY_SERVICE_MANAGER_H_
 
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
 #include "chrome/services/cups_proxy/cups_proxy_service.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -19,7 +22,8 @@
 //
 // Note: This manager is not fault-tolerant, i.e. should the service/daemon
 // fail, we do not try to restart.
-class CupsProxyServiceManager : public KeyedService {
+class CupsProxyServiceManager : public KeyedService,
+                                public ProfileManagerObserver {
  public:
   CupsProxyServiceManager();
 
@@ -28,9 +32,24 @@
 
   ~CupsProxyServiceManager() override;
 
+  // ProfileManagerObserver overrides:
+  void OnProfileAdded(Profile* profile) override;
+  void OnProfileManagerDestroying() override;
+
  private:
   void OnDaemonAvailable(bool daemon_available);
 
+  // Spawns CupsProxyService iff the primary profile is available and the
+  // CupsProxyDaemon is available.
+  void MaybeSpawnCupsProxyService();
+
+  // Whether or not the CupsProxyDaemon is available.
+  bool daemon_available_ = false;
+  // Whether or not the primary profile is available.
+  bool primary_profile_available_ = false;
+
+  ProfileManager* profile_manager_ = nullptr;
+
   base::WeakPtrFactory<CupsProxyServiceManager> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ash/printing/cups_proxy_service_manager_unittest.cc b/chrome/browser/ash/printing/cups_proxy_service_manager_unittest.cc
new file mode 100644
index 0000000..9653cbe5
--- /dev/null
+++ b/chrome/browser/ash/printing/cups_proxy_service_manager_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2023 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/ash/printing/cups_proxy_service_manager.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/services/cups_proxy/cups_proxy_service.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/ash/components/dbus/cups_proxy/cups_proxy_client.h"
+#include "components/account_id/account_id.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+constexpr char kProfileName[] = "user@example.com";
+
+}  // namespace
+
+class CupsProxyServiceManagerTest : public testing::Test {
+ protected:
+  CupsProxyServiceManagerTest()
+      : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+  CupsProxyServiceManagerTest(const CupsProxyServiceManagerTest&) = delete;
+  CupsProxyServiceManagerTest& operator=(const CupsProxyServiceManagerTest&) =
+      delete;
+  ~CupsProxyServiceManagerTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(testing_profile_manager_.SetUp());
+
+    auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
+    fake_user_manager_ = fake_user_manager.get();
+    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
+        std::move(fake_user_manager));
+
+    CupsProxyClient::InitializeFake();
+  }
+
+  void TearDown() override { CupsProxyClient::Shutdown(); }
+
+  void CreatePrimaryProfile() {
+    AccountId account_id = AccountId::FromUserEmail(kProfileName);
+    fake_user_manager_->AddUser(account_id);
+    user_manager::UserManager::Get()->UserLoggedIn(
+        account_id,
+        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
+        /*browser_restart=*/false,
+        /*is_child=*/false);
+    testing_profile_manager_.CreateTestingProfile(kProfileName,
+                                                  /*is_main_profile=*/true);
+  }
+
+  content::BrowserTaskEnvironment* task_environment() {
+    return &task_environment_;
+  }
+
+  base::test::ScopedFeatureList* scoped_feature_list() {
+    return &scoped_feature_list_;
+  }
+
+  user_manager::FakeUserManager* fake_user_manager() {
+    return fake_user_manager_;
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestingProfileManager testing_profile_manager_;
+  // Owned by `scoped_user_manager_`.
+  user_manager::FakeUserManager* fake_user_manager_ = nullptr;
+  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+};
+
+TEST_F(CupsProxyServiceManagerTest, FeatureNotEnabled) {
+  scoped_feature_list()->InitAndDisableFeature(features::kPluginVm);
+
+  CupsProxyServiceManager manager;
+
+  EXPECT_EQ(nullptr, cups_proxy::CupsProxyService::GetInstance());
+}
+
+TEST_F(CupsProxyServiceManagerTest, PrimaryProfileAlreadyCreated) {
+  scoped_feature_list()->InitAndEnableFeature(features::kPluginVm);
+  CreatePrimaryProfile();
+
+  CupsProxyServiceManager manager;
+
+  task_environment()->RunUntilIdle();
+
+  EXPECT_NE(nullptr, cups_proxy::CupsProxyService::GetInstance());
+}
+
+TEST_F(CupsProxyServiceManagerTest, PrimaryProfileCreatedLater) {
+  scoped_feature_list()->InitAndEnableFeature(features::kPluginVm);
+
+  // Before the primary profile has been created, we don't expect
+  // CupsProxyService to have been spawned.
+  CupsProxyServiceManager manager;
+
+  task_environment()->RunUntilIdle();
+
+  EXPECT_EQ(nullptr, cups_proxy::CupsProxyService::GetInstance());
+
+  CreatePrimaryProfile();
+
+  task_environment()->RunUntilIdle();
+
+  EXPECT_NE(nullptr, cups_proxy::CupsProxyService::GetInstance());
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/smb_client/smb_service_factory.cc b/chrome/browser/ash/smb_client/smb_service_factory.cc
index 644707c..a53d8a5 100644
--- a/chrome/browser/ash/smb_client/smb_service_factory.cc
+++ b/chrome/browser/ash/smb_client/smb_service_factory.cc
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "base/time/default_tick_clock.h"
-#include "chrome/browser/ash/authpolicy/authpolicy_credentials_manager.h"
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/file_system_provider/service_factory.h"
 #include "chrome/browser/ash/kerberos/kerberos_credentials_manager_factory.h"
@@ -55,7 +54,6 @@
               .WithGuest(ProfileSelection::kRedirectedToOriginal)
               .Build()) {
   DependsOn(file_system_provider::ServiceFactory::GetInstance());
-  DependsOn(AuthPolicyCredentialsManagerFactory::GetInstance());
   DependsOn(KerberosCredentialsManagerFactory::GetInstance());
   DependsOn(file_manager::VolumeManagerFactory::GetInstance());
 }
diff --git a/chrome/browser/banners/app_banner_manager_desktop.cc b/chrome/browser/banners/app_banner_manager_desktop.cc
index 4cc238f..be63674 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop.cc
@@ -149,11 +149,6 @@
       .has_value();
 }
 
-std::string AppBannerManagerDesktop::GetAppIdentifier() {
-  DCHECK(!blink::IsEmptyManifest(manifest()));
-  return web_app::GenerateAppIdUnhashedFromManifest(manifest());
-}
-
 web_app::WebAppRegistrar& AppBannerManagerDesktop::registrar() {
   auto* provider = web_app::WebAppProvider::GetForWebApps(
       Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
diff --git a/chrome/browser/banners/app_banner_manager_desktop.h b/chrome/browser/banners/app_banner_manager_desktop.h
index 548ef06..111067e 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.h
+++ b/chrome/browser/banners/app_banner_manager_desktop.h
@@ -59,7 +59,6 @@
   bool IsRelatedNonWebAppInstalled(
       const blink::Manifest::RelatedApplication& related_app) const override;
   bool IsWebAppConsideredInstalled() const override;
-  std::string GetAppIdentifier() override;
 
   // Called when the web app install initiated by a banner has completed.
   virtual void DidFinishCreatingWebApp(const web_app::AppId& app_id,
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index ecda86d..3fe1b8a 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -998,7 +998,7 @@
       ash::cloud_upload::CloudUploadUI, ash::office_fallback::OfficeFallbackUI,
 #endif
       NewTabPageUI, OmniboxPopupUI, BookmarksSidePanelUI, CustomizeChromeUI,
-      InternalsUI>(map);
+      InternalsUI, ReadingListUI>(map);
 
   RegisterWebUIControllerInterfaceBinder<
       new_tab_page::mojom::PageHandlerFactory, NewTabPageUI>(map);
diff --git a/chrome/browser/chrome_navigation_browsertest.cc b/chrome/browser/chrome_navigation_browsertest.cc
index 25475263..8b11d80 100644
--- a/chrome/browser/chrome_navigation_browsertest.cc
+++ b/chrome/browser/chrome_navigation_browsertest.cc
@@ -538,13 +538,15 @@
   // of CSP, use a javascript: URL which does go through the CSP checks.
   content::RenderFrameHost* error_host =
       ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
-  std::string location;
-  EXPECT_EQ(
-      EvalJs(
-          error_host,
-          "location='javascript:domAutomationController.send(location.href)';",
-          content::EXECUTE_SCRIPT_USE_MANUAL_REPLY),
-      content::kUnreachableWebDataURL);
+  EXPECT_EQ(EvalJs(error_host,
+                   R"(
+                    var resolve;
+                    new Promise((res) => {
+                      resolve = res;
+                      location = 'javascript:resolve(location.href)';
+                    });
+        )"),
+            content::kUnreachableWebDataURL);
 
   // The error page should have a unique origin.
   EXPECT_EQ("null", EvalJs(error_host, "self.origin;"));
diff --git a/chrome/browser/devtools/protocol/page_handler.cc b/chrome/browser/devtools/protocol/page_handler.cc
index 8392bb22..86e06c61 100644
--- a/chrome/browser/devtools/protocol/page_handler.cc
+++ b/chrome/browser/devtools/protocol/page_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/devtools/protocol/page_handler.h"
 
+#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
@@ -317,13 +318,19 @@
                           protocol::Maybe<protocol::String>());
     return;
   }
-  absl::optional<std::string> id;
-  if (data.manifest->id.has_value()) {
-    id = base::UTF16ToUTF8(data.manifest->id.value());
+  // Either both the id and start_url are present, or they are both empty.
+  std::string current_app_id_str;
+  std::string recommended_manifest_id_path_only;
+  if (data.manifest->id.is_valid()) {
+    CHECK(data.manifest->start_url.is_valid());
+    current_app_id_str = data.manifest->id.spec();
+    recommended_manifest_id_path_only =
+        web_app::GenerateManifestIdFromStartUrlOnly(data.manifest->start_url)
+            .PathForRequest();
+  } else {
+    CHECK(!data.manifest->start_url.is_valid());
   }
-  callback->sendSuccess(
-      web_app::GenerateAppIdUnhashed(id, data.manifest->start_url),
-      web_app::GenerateRecommendedId(data.manifest->start_url));
+  callback->sendSuccess(current_app_id_str, recommended_manifest_id_path_only);
 }
 
 #if BUILDFLAG(ENABLE_PRINTING)
diff --git a/chrome/browser/enterprise/profile_management/saml_response_parser.cc b/chrome/browser/enterprise/profile_management/saml_response_parser.cc
index adb3d01..ce202ac 100644
--- a/chrome/browser/enterprise/profile_management/saml_response_parser.cc
+++ b/chrome/browser/enterprise/profile_management/saml_response_parser.cc
@@ -61,20 +61,21 @@
       return nullptr;
     }
     for (const auto& attribute_child : *attribute_children) {
-      const std::string* tag = attribute_child.FindStringPath("tag");
+      const auto& attribute_child_dict = attribute_child.GetDict();
+      const std::string* tag = attribute_child_dict.FindString("tag");
       if (!tag || *tag != "AttributeValue") {
         continue;
       }
 
-      const base::Value* attribute_value_children =
-          attribute_child.FindListPath(kChildrenKey);
+      const base::Value::List* attribute_value_children =
+          attribute_child_dict.FindList(kChildrenKey);
       if (!attribute_value_children) {
         continue;
       }
-      for (const auto& attribute_value_item :
-           attribute_value_children->GetList()) {
-        if (attribute_value_item.is_dict()) {
-          return attribute_value_item.FindStringPath("text");
+      for (const auto& attribute_value_item : *attribute_value_children) {
+        auto* attribute_value_dict = attribute_value_item.GetIfDict();
+        if (attribute_value_dict) {
+          return attribute_value_dict->FindString("text");
         }
       }
     }
diff --git a/chrome/browser/enterprise/profile_token_management/profile_token_navigation_throttle.cc b/chrome/browser/enterprise/profile_token_management/profile_token_navigation_throttle.cc
index 621a2be..0949084 100644
--- a/chrome/browser/enterprise/profile_token_management/profile_token_navigation_throttle.cc
+++ b/chrome/browser/enterprise/profile_token_management/profile_token_navigation_throttle.cc
@@ -7,6 +7,7 @@
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/enterprise/profile_token_management/token_management_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profiles_state.h"
@@ -60,6 +61,7 @@
 ProfileTokenNavigationThrottle::MaybeCreateThrottleFor(
     content::NavigationHandle* navigation_handle) {
   if (!base::FeatureList::IsEnabled(features::kEnableProfileTokenManagement) ||
+      !g_browser_process->local_state() ||
       !profiles::IsProfileCreationAllowed()) {
     return nullptr;
   }
diff --git a/chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator_unittest.cc
index be2d8d1..51d6ef74 100644
--- a/chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator_unittest.cc
@@ -109,16 +109,15 @@
                        const std::string& pref_name,
                        const std::string& timestamp_name,
                        TestingProfile* profile) {
-    std::unique_ptr<base::Value> id_values =
-        std::make_unique<base::Value>(base::Value::Type::DICT);
+    base::Value::Dict id_values;
     for (const auto& id : ids) {
-      base::Value request_data(base::Value::Type::DICT);
-      request_data.SetKey(
-          timestamp_name,
-          ::base::TimeToValue(base::Time::FromJavaTime(kTimeStamp)));
-      request_data.SetKey(extension_misc::kExtensionWorkflowJustification,
-                          base::Value(kJustification));
-      id_values->SetKey(id, std::move(request_data));
+      id_values.Set(
+          id,
+          base::Value::Dict()
+              .Set(timestamp_name,
+                   ::base::TimeToValue(base::Time::FromJavaTime(kTimeStamp)))
+              .Set(extension_misc::kExtensionWorkflowJustification,
+                   base::Value(kJustification)));
     }
 
     profile->GetTestingPrefService()->SetUserPref(pref_name,
diff --git a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
index 129aaf5f..8351e72f 100644
--- a/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/profile_report_generator_unittest.cc
@@ -137,16 +137,14 @@
 
 #if !BUILDFLAG(IS_ANDROID)
   void SetExtensionToPendingList(const std::vector<std::string>& ids) {
-    std::unique_ptr<base::Value> id_values =
-        std::make_unique<base::Value>(base::Value::Type::DICT);
+    base::Value::Dict id_values;
     for (const auto& id : ids) {
-      base::Value request_data(base::Value::Type::DICT);
-      request_data.SetKey(
-          extension_misc::kExtensionRequestTimestamp,
-          ::base::TimeToValue(base::Time::FromJavaTime(kFakeTime)));
-      request_data.SetKey(extension_misc::kExtensionWorkflowJustification,
-                          base::Value(kJustification));
-      id_values->SetKey(id, std::move(request_data));
+      id_values.Set(
+          id, base::Value::Dict()
+                  .Set(extension_misc::kExtensionRequestTimestamp,
+                       ::base::TimeToValue(base::Time::FromJavaTime(kFakeTime)))
+                  .Set(extension_misc::kExtensionWorkflowJustification,
+                       base::Value(kJustification)));
     }
     profile()->GetTestingPrefService()->SetUserPref(
         prefs::kCloudExtensionRequestIds, std::move(id_values));
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
index 2bc74d3..acf7492b 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
+#include "base/strings/strcat.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -728,7 +729,8 @@
   CreateScheduler();
   g_browser_process->GetBuildState()->SetUpdate(
       BuildState::UpdateType::kNormalUpdate,
-      base::Version("1" + version_info::GetVersionNumber()), absl::nullopt);
+      base::Version(base::StrCat({"1", version_info::GetVersionNumber()})),
+      absl::nullopt);
   task_environment_.RunUntilIdle();
 
   // The timestamp should not have been updated, since a periodic report was not
@@ -752,7 +754,8 @@
   CreateScheduler();
   g_browser_process->GetBuildState()->SetUpdate(
       BuildState::UpdateType::kNormalUpdate,
-      base::Version("1" + version_info::GetVersionNumber()), absl::nullopt);
+      base::Version(base::StrCat({"1", version_info::GetVersionNumber()})),
+      absl::nullopt);
   task_environment_.RunUntilIdle();
 
   // The timestamp should not have been updated, since a periodic report was not
@@ -764,7 +767,8 @@
   // The report should be stopped in case of persistent error.
   g_browser_process->GetBuildState()->SetUpdate(
       BuildState::UpdateType::kNormalUpdate,
-      base::Version("2" + version_info::GetVersionNumber()), absl::nullopt);
+      base::Version(base::StrCat({"2", version_info::GetVersionNumber()})),
+      absl::nullopt);
   histogram_tester_.ExpectUniqueSample(kUploadTriggerMetricName, 2, 1);
 }
 
@@ -790,7 +794,8 @@
 
   g_browser_process->GetBuildState()->SetUpdate(
       BuildState::UpdateType::kNormalUpdate,
-      base::Version("1" + version_info::GetVersionNumber()), absl::nullopt);
+      base::Version(base::StrCat({"1", version_info::GetVersionNumber()})),
+      absl::nullopt);
   task_environment_.RunUntilIdle();
   ::testing::Mock::VerifyAndClearExpectations(generator_);
   ::testing::Mock::VerifyAndClearExpectations(uploader_);
diff --git a/chrome/browser/extensions/api/declarative/rules_registry_service_unittest.cc b/chrome/browser/extensions/api/declarative/rules_registry_service_unittest.cc
index 3e4ca58..7d8446a6 100644
--- a/chrome/browser/extensions/api/declarative/rules_registry_service_unittest.cc
+++ b/chrome/browser/extensions/api/declarative/rules_registry_service_unittest.cc
@@ -11,7 +11,7 @@
 
 #include "base/functional/bind.h"
 #include "base/run_loop.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/strcat.h"
 #include "base/values.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/version_info/channel.h"
@@ -143,9 +143,9 @@
   };
 
   for (const auto& test_case : test_cases) {
-    SCOPED_TRACE(base::StringPrintf(
-        "Testing Channel %s",
-        version_info::GetChannelString(test_case.channel).c_str()));
+    SCOPED_TRACE(
+        base::StrCat({"Testing Channel ",
+                      version_info::GetChannelString(test_case.channel)}));
     ScopedCurrentChannel scoped_channel(test_case.channel);
 
     ASSERT_EQ(test_case.expect_api_enabled,
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.cc b/chrome/browser/extensions/api/printing/printing_api_utils.cc
index 023cf8ea..326699bf 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.cc
@@ -238,8 +238,7 @@
   }
   cloud_devices::printer::Media media_value = media.value();
   printing::PrintSettings::RequestedMedia requested_media;
-  if (media_value.size_um.width() <= 0 || media_value.size_um.height() <= 0 ||
-      media_value.vendor_id.empty()) {
+  if (media_value.size_um.width() <= 0 || media_value.size_um.height() <= 0) {
     LOG(ERROR) << "Loaded invalid media from print ticket.";
     return nullptr;
   }
@@ -306,8 +305,7 @@
       capabilities.papers,
       [&requested_media](
           const printing::PrinterSemanticCapsAndDefaults::Paper& paper) {
-        return paper.size_um == requested_media.size_microns &&
-               paper.vendor_id == requested_media.vendor_id;
+        return paper.size_um == requested_media.size_microns;
       });
 }
 
diff --git a/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc b/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
index b8f81ca..827b7969 100644
--- a/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
+++ b/chrome/browser/extensions/system_display/display_info_provider_chromeos_unittest.cc
@@ -592,7 +592,7 @@
 }
 
 TEST_F(DisplayInfoProviderChromeosTest, GetMirroring) {
-  UpdateDisplay("600x600, 400x520/o");
+  UpdateDisplay("600x500, 400x520/o");
   DisplayUnitInfoList result;
   result = GetAllDisplaysInfo();
 
@@ -638,7 +638,7 @@
 }
 
 TEST_F(DisplayInfoProviderChromeosTest, GetBounds) {
-  UpdateDisplay("600x600, 400x520");
+  UpdateDisplay("600x500, 400x520");
   GetDisplayManager()->SetLayoutForCurrentDisplays(
       display::test::CreateDisplayLayout(display_manager(),
                                          display::DisplayPlacement::LEFT, -40));
@@ -646,7 +646,7 @@
   DisplayUnitInfoList result = GetAllDisplaysInfo();
 
   ASSERT_EQ(2u, result.size());
-  EXPECT_EQ("0,0 600x600", SystemInfoDisplayBoundsToString(result[0].bounds));
+  EXPECT_EQ("0,0 600x500", SystemInfoDisplayBoundsToString(result[0].bounds));
   EXPECT_EQ("-400,-40 400x520",
             SystemInfoDisplayBoundsToString(result[1].bounds));
 
@@ -657,7 +657,7 @@
   result = GetAllDisplaysInfo();
 
   ASSERT_EQ(2u, result.size());
-  EXPECT_EQ("0,0 600x600", SystemInfoDisplayBoundsToString(result[0].bounds));
+  EXPECT_EQ("0,0 600x500", SystemInfoDisplayBoundsToString(result[0].bounds));
   EXPECT_EQ("40,-520 400x520",
             SystemInfoDisplayBoundsToString(result[1].bounds));
 
@@ -667,8 +667,8 @@
 
   result = GetAllDisplaysInfo();
   ASSERT_EQ(2u, result.size());
-  EXPECT_EQ("0,0 600x600", SystemInfoDisplayBoundsToString(result[0].bounds));
-  EXPECT_EQ("80,600 400x520",
+  EXPECT_EQ("0,0 600x500", SystemInfoDisplayBoundsToString(result[0].bounds));
+  EXPECT_EQ("80,500 400x520",
             SystemInfoDisplayBoundsToString(result[1].bounds));
 }
 
@@ -1071,7 +1071,7 @@
 }
 
 TEST_F(DisplayInfoProviderChromeosTest, SetBoundsOriginPrimaryHiDPI) {
-  UpdateDisplay("1200x600*2,500x500");
+  UpdateDisplay("1200x600*2,500x400");
 
   const display::Display& secondary =
       display::test::DisplayManagerTestApi(display_manager())
@@ -1083,7 +1083,7 @@
   EXPECT_TRUE(
       CallSetDisplayUnitInfo(base::NumberToString(secondary.id()), info));
 
-  EXPECT_EQ("600,-100 500x500", secondary.bounds().ToString());
+  EXPECT_EQ("600,-100 500x400", secondary.bounds().ToString());
 }
 
 TEST_F(DisplayInfoProviderChromeosTest, SetBoundsOriginSecondaryHiDPI) {
@@ -1551,7 +1551,7 @@
 
   // Initialize displays that have bounds outside the valid width range of 640px
   // to 4096px.
-  UpdateDisplay("400x400, 4500x1000#4500x1000");
+  UpdateDisplay("400x350, 4500x1000#4500x1000");
 
   display_id_list = display_manager()->GetConnectedDisplayIdList();
 
@@ -1677,7 +1677,7 @@
   }
 
   // Add more displays.
-  UpdateDisplay("200x200,600x600,700x700");
+  UpdateDisplay("200x150,600x550,700x650");
   display::DisplayIdList id_list =
       display_manager()->GetConnectedDisplayIdList();
   EXPECT_EQ(3U, id_list.size());
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 18b45f8..4b523677 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2244,11 +2244,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "enable-desktop-capture-lacros-v2",
-    "owners": [ "alcooper", "mfoltz" ],
-    "expiry_milestone": 115
-  },
-  {
     "name": "enable-desktop-pwas-additional-windowing-controls",
     "owners": [ "laurila@google.com", "isandrk@chromium.org", "desktop-pwas-team@google.com" ],
     "expiry_milestone": 122
@@ -5893,6 +5888,11 @@
     "expiry_milestone": 114
   },
   {
+    "name": "overflow-menu-customization",
+    "owners": [ "rkgibson@google.com", "bling-flags@google.com" ],
+    "expiry_milestone": 115
+  },
+  {
     "name": "overlay-scrollbars",
     "owners": [ "chaopeng", "bokan", "input-dev" ],
     "expiry_milestone": 114
@@ -6474,11 +6474,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "reduce-horizontal-fling-velocity",
-    "owners": [ "flackr", "input-dev" ],
-    "expiry_milestone": 95
-  },
-  {
     "name": "reduce-user-agent-android-version-device-model",
     "owners": [ "miketaylr", "victortan"],
     "expiry_milestone": 116
@@ -7080,6 +7075,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "surface-control-magnifier",
+    "owners": [ "boliu" ],
+    "expiry_milestone": 122
+  },
+  {
     "name": "sync-autofill-wallet-usage-data",
     "owners": [ "alexandertekle@google.com", "treib" ],
     "expiry_milestone": 120
@@ -7122,7 +7122,7 @@
   {
     "name": "system-color-chooser",
     "owners": [ "bur", "chrome-mac-dev@google.com" ],
-    "expiry_milestone": 115
+    "expiry_milestone": 123
   },
   {
     "name": "system-extensions",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index cb0871c..c9ebecc 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -719,8 +719,9 @@
     "Access Chrome Labs through the toolbar menu to see featured user-facing "
     "experimental features.";
 
+const char kChromeRefresh2023Id[] = "chrome-refresh-2023";
 const char kChromeRefresh2023Name[] = "Chrome Refresh 2023";
-const char kChromeRefresh2023Description[] = "Refresh of Chrome Desktop UI.";
+const char kChromeRefresh2023Description[] = "Enables the new desktop design.";
 
 const char kChromeWebuiRefresh2023Name[] = "Chrome WebUI Refresh 2023";
 const char kChromeWebuiRefresh2023Description[] =
@@ -1351,12 +1352,6 @@
     "with the pixel and improves text rendering. This should be enabled when a "
     "device is using fractional scale factor.";
 
-const char kReduceHorizontalFlingVelocityName[] =
-    "Reduce horizontal fling velocity";
-const char kReduceHorizontalFlingVelocityDescription[] =
-    "Reduces the velocity of horizontal flings to 20% of their original"
-    "velocity.";
-
 extern const char kDropInputEventsBeforeFirstPaintName[] =
     "Drop Input Events Before First Paint";
 extern const char kDropInputEventsBeforeFirstPaintDescription[] =
@@ -2484,7 +2479,8 @@
 
 const char kOverlayStrategiesName[] = "Select HW overlay strategies";
 const char kOverlayStrategiesDescription[] =
-    "Select strategies used to promote quads to HW overlays.";
+    "Select strategies used to promote quads to HW overlays. Note that "
+    "strategies other than Default may break playback of protected content.";
 const char kOverlayStrategiesDefault[] = "Default";
 const char kOverlayStrategiesNone[] = "None";
 const char kOverlayStrategiesUnoccludedFullscreen[] =
@@ -4219,6 +4215,12 @@
     "Enable rich gestures for stylus which can be used to modify text in "
     "editable web content.";
 
+const char kSurfaceControlMagnifierName[] = "Surface control magnifier";
+const char kSurfaceControlMagnifierDescription[] =
+    "Use magnifier built using SurfaceControl. Depends on SurfaceControl, "
+    "Slim compositor, and Android OS support. No effect if enabled on "
+    "unsupported environment.";
+
 const char kTabGroupsForTabletsName[] = "Tab groups on tablets";
 const char kTabGroupsForTabletsDescription[] = "Enable tab groups on tablets.";
 
@@ -6590,11 +6592,6 @@
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-const char kDesktopCaptureLacrosV2Name[] = "Enable Lacros Desktop Capture V2";
-const char kDesktopCaptureLacrosV2Description[] =
-    "Enables the improved desktop/window capturer for doing screen/window "
-    "sharing on Lacros";
-
 const char kExperimentalWebAppProfileIsolationName[] =
     "Enable experimental web app profile isolation";
 const char kExperimentalWebAppProfileIsolationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index aa2aa20e..9c03fe28 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -393,6 +393,7 @@
 extern const char kChromeLabsName[];
 extern const char kChromeLabsDescription[];
 
+extern const char kChromeRefresh2023Id[];
 extern const char kChromeRefresh2023Name[];
 extern const char kChromeRefresh2023Description[];
 
@@ -753,9 +754,6 @@
 extern const char kExperimentalRgbKeyboardPatternsName[];
 extern const char kExperimentalRgbKeyboardPatternsDescription[];
 
-extern const char kReduceHorizontalFlingVelocityName[];
-extern const char kReduceHorizontalFlingVelocityDescription[];
-
 extern const char kRetailCouponsName[];
 extern const char kRetailCouponsDescription[];
 
@@ -2455,6 +2453,9 @@
 extern const char kStylusRichGesturesName[];
 extern const char kStylusRichGesturesDescription[];
 
+extern const char kSurfaceControlMagnifierName[];
+extern const char kSurfaceControlMagnifierDescription[];
+
 extern const char kTabGroupsForTabletsName[];
 extern const char kTabGroupsForTabletsDescription[];
 
@@ -3771,9 +3772,6 @@
 #endif  // #if BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-extern const char kDesktopCaptureLacrosV2Name[];
-extern const char kDesktopCaptureLacrosV2Description[];
-
 extern const char kExperimentalWebAppProfileIsolationName[];
 extern const char kExperimentalWebAppProfileIsolationDescription[];
 
diff --git a/chrome/browser/flags/BUILD.gn b/chrome/browser/flags/BUILD.gn
index af17742..c68bd9ef 100644
--- a/chrome/browser/flags/BUILD.gn
+++ b/chrome/browser/flags/BUILD.gn
@@ -13,6 +13,7 @@
     "android/java/src/org/chromium/chrome/browser/flags/CachedFlag.java",
     "android/java/src/org/chromium/chrome/browser/flags/CachedFlagsSafeMode.java",
     "android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java",
+    "android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureMap.java",
     "android/java/src/org/chromium/chrome/browser/flags/ChromeSessionState.java",
     "android/java/src/org/chromium/chrome/browser/flags/DoubleCachedFieldTrialParameter.java",
     "android/java/src/org/chromium/chrome/browser/flags/FeatureParamUtils.java",
@@ -43,7 +44,7 @@
 generate_jni("jni_headers") {
   sources = [
     "android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java",
-    "android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java",
+    "android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureMap.java",
     "android/java/src/org/chromium/chrome/browser/flags/ChromeSessionState.java",
   ]
 }
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 41b4d379..84f3ca07 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -9,17 +9,12 @@
 #include <string>
 
 #include "base/android/feature_map.h"
-#include "base/android/jni_array.h"
-#include "base/android/jni_string.h"
-#include "base/containers/flat_map.h"
 #include "base/feature_list.h"
-#include "base/metrics/field_trial_params.h"
 #include "base/no_destructor.h"
-#include "base/strings/string_piece_forward.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h"
 #include "chrome/browser/flags/android/chrome_session_state.h"
-#include "chrome/browser/flags/jni_headers/ChromeFeatureList_jni.h"
+#include "chrome/browser/flags/jni_headers/ChromeFeatureMap_jni.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/notifications/chime/android/features.h"
 #include "chrome/browser/push_messaging/push_messaging_features.h"
@@ -75,11 +70,6 @@
 #include "third_party/blink/public/common/features.h"
 #include "ui/base/ui_base_features.h"
 
-using base::android::ConvertJavaStringToUTF8;
-using base::android::ConvertUTF8ToJavaString;
-using base::android::JavaParamRef;
-using base::android::ScopedJavaLocalRef;
-
 namespace chrome {
 namespace android {
 
@@ -419,6 +409,10 @@
 
 }  // namespace
 
+static jlong JNI_ChromeFeatureMap_GetNativeMap(JNIEnv* env) {
+  return reinterpret_cast<jlong>(GetFeatureMap());
+}
+
 // Alphabetical:
 
 BASE_FEATURE(kAdaptiveButtonInTopToolbar,
@@ -1143,79 +1137,5 @@
              "WebApkTrampolineOnInitialIntent",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-static jboolean JNI_ChromeFeatureList_IsEnabled(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name) {
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  return base::FeatureList::IsEnabled(*feature);
-}
-
-static ScopedJavaLocalRef<jstring>
-JNI_ChromeFeatureList_GetFieldTrialParamByFeature(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name,
-    const JavaParamRef<jstring>& jparam_name) {
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
-  const std::string& param_value =
-      base::GetFieldTrialParamValueByFeature(*feature, param_name);
-  return ConvertUTF8ToJavaString(env, param_value);
-}
-
-static jint JNI_ChromeFeatureList_GetFieldTrialParamByFeatureAsInt(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name,
-    const JavaParamRef<jstring>& jparam_name,
-    const jint jdefault_value) {
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
-  return base::GetFieldTrialParamByFeatureAsInt(*feature, param_name,
-                                                jdefault_value);
-}
-
-static jdouble JNI_ChromeFeatureList_GetFieldTrialParamByFeatureAsDouble(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name,
-    const JavaParamRef<jstring>& jparam_name,
-    const jdouble jdefault_value) {
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
-  return base::GetFieldTrialParamByFeatureAsDouble(*feature, param_name,
-                                                   jdefault_value);
-}
-
-static jboolean JNI_ChromeFeatureList_GetFieldTrialParamByFeatureAsBoolean(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name,
-    const JavaParamRef<jstring>& jparam_name,
-    const jboolean jdefault_value) {
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  const std::string& param_name = ConvertJavaStringToUTF8(env, jparam_name);
-  return base::GetFieldTrialParamByFeatureAsBool(*feature, param_name,
-                                                 jdefault_value);
-}
-
-static ScopedJavaLocalRef<jobjectArray>
-JNI_ChromeFeatureList_GetFlattedFieldTrialParamsForFeature(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& jfeature_name) {
-  base::FieldTrialParams params;
-  std::vector<std::string> keys_and_values;
-  const base::Feature* feature = GetFeatureMap()->FindFeatureExposedToJava(
-      base::StringPiece(ConvertJavaStringToUTF8(env, jfeature_name)));
-  if (feature && base::GetFieldTrialParamsByFeature(*feature, &params)) {
-    for (const auto& param_pair : params) {
-      keys_and_values.push_back(param_pair.first);
-      keys_and_values.push_back(param_pair.second);
-    }
-  }
-  return base::android::ToJavaArrayOfStrings(env, keys_and_values);
-}
-
 }  // namespace android
 }  // namespace chrome
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 483433e1..af6baf8c 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
@@ -4,167 +4,97 @@
 
 package org.chromium.chrome.browser.flags;
 
-import org.chromium.base.FeatureList;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.FeatureMap;
 
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Java accessor for base/feature_list.h state.
+ * A list of feature flags exposed to Java.
  *
- * This class provides methods to access values of feature flags registered in
- * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc and as a constant
- * in this class.
+ * This class lists flags exposed to Java as String constants. They should match
+ * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc.
  *
- * This class also provides methods to access values of field trial parameters associated to those
- * flags.
+ * This class also provides convenience methods to access values of flags and their field trial
+ * parameters through {@link ChromeFeatureMap}.
+ *
+ * Chrome-layer {@link CachedFlag}s are instantiated here as well.
  */
-@JNINamespace("chrome::android")
 public abstract class ChromeFeatureList {
     /** Prevent instantiation. */
     private ChromeFeatureList() {}
 
     /**
-     * Returns whether the specified feature is enabled or not in native.
-     *
-     * @param featureName The name of the feature to query.
-     * @return Whether the feature is enabled or not.
-     */
-    private static boolean isEnabledInNative(String featureName) {
-        assert FeatureList.isNativeInitialized();
-        return ChromeFeatureListJni.get().isEnabled(featureName);
-    }
-
-    /**
-     * Returns whether the specified feature is enabled or not.
+     * Convenience method to check Chrome-layer feature flags, see
+     * {@link FeatureMap#isEnabled(String)}}.
      *
      * Note: Features queried through this API must be added to the array
      * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
-     *
-     * Calling this has the side effect of bucketing this client, which may cause an experiment to
-     * be marked as active.
-     *
-     * Should be called only after native is loaded. If {@link FeatureList#isInitialized()} returns
-     * true, this method is safe to call.  In tests, this will return any values set through
-     * {@link FeatureList#setTestFeatures(Map)}, even before native is loaded.
-     *
-     * @param featureName The name of the feature to query.
-     * @return Whether the feature is enabled or not.
      */
     public static boolean isEnabled(String featureName) {
-        // FeatureFlags set for testing override the native default value.
-        Boolean testValue = FeatureList.getTestValueForFeature(featureName);
-        if (testValue != null) return testValue;
-        return isEnabledInNative(featureName);
+        return ChromeFeatureMap.getInstance().isEnabled(featureName);
     }
 
     /**
-     * Returns a field trial param for the specified feature.
+     * Convenience method to get Chrome-layer feature field trial params, see
+     * {@link FeatureMap#getFieldTrialParamByFeature(String, String)}}.
      *
      * Note: Features queried through this API must be added to the array
      * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
-     *
-     * @param featureName The name of the feature to retrieve a param for.
-     * @param paramName The name of the param for which to get as an integer.
-     * @return The parameter value as a String. The string is empty if the feature does not exist or
-     *   the specified parameter does not exist.
      */
     public static String getFieldTrialParamByFeature(String featureName, String paramName) {
-        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
-        if (testValue != null) return testValue;
-        if (FeatureList.hasTestFeatures()) return "";
-        assert FeatureList.isInitialized();
-        return ChromeFeatureListJni.get().getFieldTrialParamByFeature(featureName, paramName);
+        return ChromeFeatureMap.getInstance().getFieldTrialParamByFeature(featureName, paramName);
     }
 
     /**
-     * Returns a field trial param as an int for the specified feature.
+     * Convenience method to get Chrome-layer feature field trial params, see
+     * {@link FeatureMap#getFieldTrialParamByFeatureAsBoolean(String, boolean)}}.
      *
      * Note: Features queried through this API must be added to the array
      * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
-     *
-     * @param featureName The name of the feature to retrieve a param for.
-     * @param paramName The name of the param for which to get as an integer.
-     * @param defaultValue The integer value to use if the param is not available.
-     * @return The parameter value as an int. Default value if the feature does not exist or the
-     *         specified parameter does not exist or its string value does not represent an int.
-     */
-    public static int getFieldTrialParamByFeatureAsInt(
-            String featureName, String paramName, int defaultValue) {
-        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
-        if (testValue != null) return Integer.valueOf(testValue);
-        if (FeatureList.hasTestFeatures()) return defaultValue;
-        assert FeatureList.isInitialized();
-        return ChromeFeatureListJni.get().getFieldTrialParamByFeatureAsInt(
-                featureName, paramName, defaultValue);
-    }
-
-    /**
-     * Returns a field trial param as a double for the specified feature.
-     *
-     * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
-     *
-     * @param featureName The name of the feature to retrieve a param for.
-     * @param paramName The name of the param for which to get as an integer.
-     * @param defaultValue The double value to use if the param is not available.
-     * @return The parameter value as a double. Default value if the feature does not exist or the
-     *         specified parameter does not exist or its string value does not represent a double.
-     */
-    public static double getFieldTrialParamByFeatureAsDouble(
-            String featureName, String paramName, double defaultValue) {
-        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
-        if (testValue != null) return Double.valueOf(testValue);
-        if (FeatureList.hasTestFeatures()) return defaultValue;
-        assert FeatureList.isInitialized();
-        return ChromeFeatureListJni.get().getFieldTrialParamByFeatureAsDouble(
-                featureName, paramName, defaultValue);
-    }
-
-    /**
-     * Returns all the field trial parameters for the specified feature.
-     */
-    public static Map<String, String> getFieldTrialParamsForFeature(String featureName) {
-        Map<String, String> testValues =
-                FeatureList.getTestValuesForAllFieldTrialParamsForFeature(featureName);
-        if (testValues != null) return testValues;
-        if (FeatureList.hasTestFeatures()) return Collections.emptyMap();
-
-        assert FeatureList.isInitialized();
-        Map<String, String> result = new HashMap<>();
-        String[] flattenedParams =
-                ChromeFeatureListJni.get().getFlattedFieldTrialParamsForFeature(featureName);
-        for (int i = 0; i < flattenedParams.length; i += 2) {
-            result.put(flattenedParams[i], flattenedParams[i + 1]);
-        }
-        return result;
-    }
-
-    /**
-     * Returns a field trial param as a boolean for the specified feature.
-     *
-     * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
-     *
-     * @param featureName The name of the feature to retrieve a param for.
-     * @param paramName The name of the param for which to get as an integer.
-     * @param defaultValue The boolean value to use if the param is not available.
-     * @return The parameter value as a boolean. Default value if the feature does not exist or the
-     *         specified parameter does not exist or its string value is neither "true" nor "false".
      */
     public static boolean getFieldTrialParamByFeatureAsBoolean(
             String featureName, String paramName, boolean defaultValue) {
-        String testValue = FeatureList.getTestValueForFieldTrialParam(featureName, paramName);
-        if (testValue != null) return Boolean.valueOf(testValue);
-        if (FeatureList.hasTestFeatures()) return defaultValue;
-        assert FeatureList.isInitialized();
-        return ChromeFeatureListJni.get().getFieldTrialParamByFeatureAsBoolean(
+        return ChromeFeatureMap.getInstance().getFieldTrialParamByFeatureAsBoolean(
                 featureName, paramName, defaultValue);
     }
 
+    /**
+     * Convenience method to get Chrome-layer feature field trial params, see
+     * {@link FeatureMap#getFieldTrialParamByFeatureAsInt(String, String, int)}}.
+     *
+     * Note: Features queried through this API must be added to the array
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
+     */
+    public static int getFieldTrialParamByFeatureAsInt(
+            String featureName, String paramName, int defaultValue) {
+        return ChromeFeatureMap.getInstance().getFieldTrialParamByFeatureAsInt(
+                featureName, paramName, defaultValue);
+    }
+
+    /**
+     * Convenience method to get Chrome-layer feature field trial params, see
+     * {@link FeatureMap#getFieldTrialParamByFeatureAsDouble(String, String, double)}}.
+     *
+     * Note: Features queried through this API must be added to the array
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
+     */
+    public static double getFieldTrialParamByFeatureAsDouble(
+            String featureName, String paramName, double defaultValue) {
+        return ChromeFeatureMap.getInstance().getFieldTrialParamByFeatureAsDouble(
+                featureName, paramName, defaultValue);
+    }
+
+    /**
+     * Convenience method to get Chrome-layer feature field trial params, see
+     * {@link FeatureMap#getFieldTrialParamsForFeature(String)}}.
+     *
+     * Note: Features queried through this API must be added to the array
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
+     */
+    public static Map<String, String> getFieldTrialParamsForFeature(String featureName) {
+        return ChromeFeatureMap.getInstance().getFieldTrialParamsForFeature(featureName);
+    }
+
     /* Alphabetical: */
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR = "AdaptiveButtonInTopToolbar";
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE =
@@ -667,17 +597,4 @@
             new CachedFlag(USE_LIBUNWINDSTACK_NATIVE_UNWINDER_ANDROID, false);
     public static final CachedFlag sWebApkTrampolineOnInitialIntent =
             new CachedFlag(WEB_APK_TRAMPOLINE_ON_INITIAL_INTENT, true);
-
-    @NativeMethods
-    interface Natives {
-        boolean isEnabled(String featureName);
-        String getFieldTrialParamByFeature(String featureName, String paramName);
-        int getFieldTrialParamByFeatureAsInt(
-                String featureName, String paramName, int defaultValue);
-        double getFieldTrialParamByFeatureAsDouble(
-                String featureName, String paramName, double defaultValue);
-        boolean getFieldTrialParamByFeatureAsBoolean(
-                String featureName, String paramName, boolean defaultValue);
-        String[] getFlattedFieldTrialParamsForFeature(String featureName);
-    }
 }
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureMap.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureMap.java
new file mode 100644
index 0000000..dfe9bc1
--- /dev/null
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureMap.java
@@ -0,0 +1,46 @@
+// Copyright 2023 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.flags;
+
+import org.chromium.base.FeatureMap;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.build.annotations.MainDex;
+
+/**
+ * Java accessor for state of Chrome-layer feature flags.
+ *
+ * This class provides methods to access values of Chrome-layer feature flags, listed in
+ * {@link ChromeFeatureList} and to  their field trial parameters. The API to access those values
+ * is in the base class {@link FeatureMap} and is shared with other FeatureLists and FeatureMaps.
+ *
+ * The same functionality is provided through static methods in {@link ChromeFeatureList} for
+ * backwards compatibility and convenience.
+ */
+@JNINamespace("chrome::android")
+@MainDex
+public class ChromeFeatureMap extends FeatureMap {
+    /** Prevent instantiation. */
+    private ChromeFeatureMap() {
+        super();
+    }
+
+    private static ChromeFeatureMap sInstance;
+
+    public static ChromeFeatureMap getInstance() {
+        if (sInstance == null) sInstance = new ChromeFeatureMap();
+        return sInstance;
+    }
+
+    @Override
+    protected long getNativeMap() {
+        return ChromeFeatureMapJni.get().getNativeMap();
+    }
+
+    @NativeMethods
+    interface Natives {
+        long getNativeMap();
+    }
+}
diff --git a/chrome/browser/local_discovery/OWNERS b/chrome/browser/local_discovery/OWNERS
index e069757..43dbd15 100644
--- a/chrome/browser/local_discovery/OWNERS
+++ b/chrome/browser/local_discovery/OWNERS
@@ -1,6 +1,5 @@
 # For Cast.
 mfoltz@chromium.org
-rwkeane@google.com
 
 # For printing or document scanning.
 file://chromeos/printing/OWNERS
diff --git a/chrome/browser/media/protected_media_identifier_permission_context.cc b/chrome/browser/media/protected_media_identifier_permission_context.cc
index 859f3e8..0db0258 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context.cc
+++ b/chrome/browser/media/protected_media_identifier_permission_context.cc
@@ -31,7 +31,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include <utility>
 
-#include "ash/constants/ash_switches.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chromeos/ash/components/settings/cros_settings_names.h"
@@ -67,7 +66,8 @@
            << embedding_origin.spec() << ")";
 
   if (!requesting_origin.is_valid() || !embedding_origin.is_valid() ||
-      !IsProtectedMediaIdentifierEnabled()) {
+      !IsProtectedMediaIdentifierEnabled(
+          Profile::FromBrowserContext(browser_context()))) {
     return CONTENT_SETTING_BLOCK;
   }
 
@@ -127,12 +127,13 @@
 
 // TODO(xhwang): We should consolidate the "protected content" related pref
 // across platforms.
+// static
 bool ProtectedMediaIdentifierPermissionContext::
-    IsProtectedMediaIdentifierEnabled() const {
+    IsProtectedMediaIdentifierEnabled(Profile* profile) {
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
-  Profile* profile = Profile::FromBrowserContext(browser_context());
   // Identifier is not allowed in incognito or guest mode.
-  if (profile->IsOffTheRecord() || profile->IsGuestSession()) {
+  if (profile != nullptr &&
+      (profile->IsOffTheRecord() || profile->IsGuestSession())) {
     DVLOG(1) << "Protected media identifier disabled in incognito or guest "
                 "mode.";
     return false;
diff --git a/chrome/browser/media/protected_media_identifier_permission_context.h b/chrome/browser/media/protected_media_identifier_permission_context.h
index 07d26d0..47530f80 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context.h
+++ b/chrome/browser/media/protected_media_identifier_permission_context.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_MEDIA_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_CONTEXT_H_
 #define CHROME_BROWSER_MEDIA_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_CONTEXT_H_
 
+#include "chrome/browser/profiles/profile.h"
 #include "components/permissions/permission_context_base.h"
 #include "components/permissions/permission_request_id.h"
 
@@ -29,6 +30,12 @@
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
 
+  // Returns whether "Protected content" is enabled based on factors other than
+  // what 'ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER' is set to. For
+  // example, it can be disabled by a switch in the content settings page, in
+  // incognito or guest mode, or by the device policy.
+  static bool IsProtectedMediaIdentifierEnabled(Profile* profile = nullptr);
+
  private:
   friend class ProtectedMediaIdentifierPermissionContextTest;
   static bool IsOriginAllowed(const GURL& origin);
@@ -36,12 +43,6 @@
   void UpdateTabContext(const permissions::PermissionRequestID& id,
                         const GURL& requesting_frame,
                         bool allowed) override;
-
-  // Returns whether "Protected content" is enabled based on factors other
-  // than the protected media identifier content setting itself. For example,
-  // it can be disabled by a switch in content settings, in incognito or guest
-  // mode, or by the device policy.
-  bool IsProtectedMediaIdentifierEnabled() const;
 };
 
 #endif  // CHROME_BROWSER_MEDIA_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_CONTEXT_H_
diff --git a/chrome/browser/media/protected_media_identifier_permission_context_unittest.cc b/chrome/browser/media/protected_media_identifier_permission_context_unittest.cc
index 9aa6861..ed4b821e 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context_unittest.cc
+++ b/chrome/browser/media/protected_media_identifier_permission_context_unittest.cc
@@ -11,22 +11,55 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/dbus/constants/dbus_switches.h"  // nogncheck
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
+#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
+#include "chromeos/ash/components/settings/cros_settings_names.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+#include "chrome/browser/profiles/profile_testing_helper.h"
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+
 class ProtectedMediaIdentifierPermissionContextTest : public testing::Test {
  public:
   ProtectedMediaIdentifierPermissionContextTest()
       : requesting_origin_("https://example.com"),
         requesting_sub_domain_origin_("https://subdomain.example.com") {
     command_line_ = scoped_command_line_.GetProcessCommandLine();
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
+    profile_testing_helper_.SetUp();
+#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    settings_helper_.ReplaceDeviceSettingsProviderWithStub();
+#endif
   }
 
   bool IsOriginAllowed(const GURL& origin) {
     return ProtectedMediaIdentifierPermissionContext::IsOriginAllowed(origin);
   }
 
+  bool IsProtectedMediaIdentifierEnabled(Profile* profile = nullptr) {
+    return ProtectedMediaIdentifierPermissionContext::
+        IsProtectedMediaIdentifierEnabled(profile);
+  }
+
   GURL requesting_origin_;
   GURL requesting_sub_domain_origin_;
 
   base::test::ScopedCommandLine scoped_command_line_;
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+  ProfileTestingHelper profile_testing_helper_;
+#endif
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  ash::ScopedCrosSettingsTestHelper settings_helper_;
+#endif
   raw_ptr<base::CommandLine> command_line_;
 };
 
@@ -72,3 +105,51 @@
   // The request should no longer need to ask for permission
   ASSERT_TRUE(IsOriginAllowed(requesting_sub_domain_origin_));
 }
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+TEST_F(ProtectedMediaIdentifierPermissionContextTest,
+       ProtectedMediaIdentifierOnDifferentProfiles) {
+  ASSERT_FALSE(IsProtectedMediaIdentifierEnabled(
+      profile_testing_helper_.incognito_profile()));
+
+  ASSERT_FALSE(IsProtectedMediaIdentifierEnabled(
+      profile_testing_helper_.guest_profile()));
+
+  ASSERT_TRUE(IsProtectedMediaIdentifierEnabled(
+      profile_testing_helper_.regular_profile()));
+}
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_CHROMEOS)
+TEST_F(ProtectedMediaIdentifierPermissionContextTest,
+       ProtectedMediaIdentifierDisabledOnDevMode) {
+  command_line_->AppendSwitch(chromeos::switches::kSystemDevMode);
+
+  // The protected media identifier should not be enabled if the system is on
+  // dev mode.
+  ASSERT_FALSE(IsProtectedMediaIdentifierEnabled());
+}
+
+TEST_F(ProtectedMediaIdentifierPermissionContextTest,
+       ProtectedMediaIdentifierEnabledOnDevModeWithAllowinRASwitch) {
+  command_line_->AppendSwitch(chromeos::switches::kSystemDevMode);
+  command_line_->AppendSwitch(switches::kAllowRAInDevMode);
+
+  // As long as `kAllowRAInDevMode` is appended, then even if system is on dev
+  // mode, the protected media identifier should be enabled.
+  ASSERT_TRUE(IsProtectedMediaIdentifierEnabled());
+}
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(ProtectedMediaIdentifierPermissionContextTest,
+       ProtectedMediaIdentifierWithAttestationForContentSwitch) {
+  settings_helper_.SetBoolean(ash::kAttestationForContentProtectionEnabled,
+                              true);
+  ASSERT_TRUE(IsProtectedMediaIdentifierEnabled());
+
+  settings_helper_.SetBoolean(ash::kAttestationForContentProtectionEnabled,
+                              false);
+  ASSERT_FALSE(IsProtectedMediaIdentifierEnabled());
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc b/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
index 6cd0ba0..1c20dcc 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
@@ -7,6 +7,7 @@
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
 #include "build/build_config.h"
@@ -265,9 +266,9 @@
 
   net::AddMultipartValueForUpload("prod", kProduct, kBoundary, std::string(),
                                   upload_data);
-  net::AddMultipartValueForUpload("ver",
-                                  version_info::GetVersionNumber() + "-webrtc",
-                                  kBoundary, std::string(), upload_data);
+  net::AddMultipartValueForUpload(
+      "ver", base::StrCat({version_info::GetVersionNumber(), "-webrtc"}),
+      kBoundary, std::string(), upload_data);
   net::AddMultipartValueForUpload("guid", "0", kBoundary, std::string(),
                                   upload_data);
   net::AddMultipartValueForUpload("type", filename, kBoundary, std::string(),
diff --git a/chrome/browser/media/webrtc/webrtc_log_uploader.cc b/chrome/browser/media/webrtc/webrtc_log_uploader.cc
index fb8757a..9a90da3 100644
--- a/chrome/browser/media/webrtc/webrtc_log_uploader.cc
+++ b/chrome/browser/media/webrtc/webrtc_log_uploader.cc
@@ -377,9 +377,9 @@
 #endif
   net::AddMultipartValueForUpload("prod", product, kWebrtcLogMultipartBoundary,
                                   "", post_data);
-  net::AddMultipartValueForUpload("ver",
-                                  version_info::GetVersionNumber() + "-webrtc",
-                                  kWebrtcLogMultipartBoundary, "", post_data);
+  net::AddMultipartValueForUpload(
+      "ver", base::StrCat({version_info::GetVersionNumber(), "-webrtc"}),
+      kWebrtcLogMultipartBoundary, "", post_data);
   net::AddMultipartValueForUpload("guid", "0", kWebrtcLogMultipartBoundary, "",
                                   post_data);
   net::AddMultipartValueForUpload("type", "webrtc_log",
diff --git a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
index 6518ff3..b058c94 100644
--- a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
+++ b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
@@ -460,9 +460,9 @@
   }
 
   // Chrome version
-  LogToCircularBuffer("Chrome version: " + version_info::GetVersionNumber() +
-                      " " +
-                      chrome::GetChannelName(chrome::WithExtendedStable(true)));
+  LogToCircularBuffer(
+      base::StrCat({"Chrome version: ", version_info::GetVersionNumber(), " ",
+                    chrome::GetChannelName(chrome::WithExtendedStable(true))}));
 
   // OS
   LogToCircularBuffer(base::SysInfo::OperatingSystemName() + " " +
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
index 8bcfa52..0278ad74 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
@@ -34,14 +34,14 @@
 
 // Dummy DiceWebSigninInterceptor::Delegate that does nothing.
 class TestDiceWebSigninInterceptorDelegate
-    : public DiceWebSigninInterceptor::Delegate {
+    : public WebSigninInterceptor::Delegate {
  public:
   bool IsSigninInterceptionSupported(
       const content::WebContents& web_contents) override {
     return true;
   }
 
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
   ShowSigninInterceptionBubble(
       content::WebContents* web_contents,
       const BubbleParameters& bubble_parameters,
@@ -51,8 +51,8 @@
   void ShowFirstRunExperienceInNewProfile(
       Browser* browser,
       const CoreAccountId& account_id,
-      DiceWebSigninInterceptor::SigninInterceptionType interception_type)
-      override {}
+      WebSigninInterceptor::SigninInterceptionType interception_type) override {
+  }
 };
 
 class TestPasswordManagerClient
diff --git a/chrome/browser/password_manager/web_app_profile_switcher_interactive_uitest.cc b/chrome/browser/password_manager/web_app_profile_switcher_interactive_uitest.cc
index edb6311..70290bf 100644
--- a/chrome/browser/password_manager/web_app_profile_switcher_interactive_uitest.cc
+++ b/chrome/browser/password_manager/web_app_profile_switcher_interactive_uitest.cc
@@ -33,20 +33,19 @@
 
 namespace {
 
-const char kTestWebUIAppManifestId[] = "";
+const char kTestWebUIManifestId[] = "chrome://password-manager/";
 const char kTestWebUIAppURL[] = "chrome://password-manager/?source=pwa";
 
 std::unique_ptr<WebAppInstallInfo> GetTestWebAppInstallInfo() {
   auto web_app_info = std::make_unique<WebAppInstallInfo>();
   web_app_info->start_url = GURL(kTestWebUIAppURL);
   web_app_info->title = u"Test app";
-  web_app_info->manifest_id = kTestWebUIAppManifestId;
+  web_app_info->manifest_id = GURL(kTestWebUIManifestId);
   return web_app_info;
 }
 
 web_app::AppId GetTestWebAppId() {
-  return web_app::GenerateAppId(kTestWebUIAppManifestId,
-                                GURL(kTestWebUIAppURL));
+  return web_app::GenerateAppIdFromManifestId(GURL(kTestWebUIManifestId));
 }
 
 Profile* CreateAdditionalProfile() {
diff --git a/chrome/browser/permissions/notification_permission_review_service_unittest.cc b/chrome/browser/permissions/notification_permission_review_service_unittest.cc
index 739d393e..b02748e 100644
--- a/chrome/browser/permissions/notification_permission_review_service_unittest.cc
+++ b/chrome/browser/permissions/notification_permission_review_service_unittest.cc
@@ -31,8 +31,8 @@
        IgnoreOriginForNotificationPermissionReview) {
   HostContentSettingsMap* map =
       HostContentSettingsMapFactory::GetForProfile(profile());
-  std::string urls[] = {"https://google.com:443",
-                        "https://www.youtube.com:443"};
+  std::string urls[] = {"https://google.com:443", "https://www.youtube.com:443",
+                        "https://www.example.com:443"};
   map->SetContentSettingDefaultScope(GURL(urls[0]), GURL(),
                                      ContentSettingsType::NOTIFICATIONS,
                                      CONTENT_SETTING_ALLOW);
@@ -47,13 +47,44 @@
 
   // Add notification permission to block list and check if it will be not be
   // shown on the list.
+  auto pattern_to_ignore = ContentSettingsPattern::FromString(urls[0]);
   service->AddPatternToNotificationPermissionReviewBlocklist(
-      ContentSettingsPattern::FromString(urls[0]),
-      ContentSettingsPattern::Wildcard());
+      pattern_to_ignore, ContentSettingsPattern::Wildcard());
   notification_permissions = service->GetNotificationSiteListForReview();
   EXPECT_EQ(1UL, notification_permissions.size());
   EXPECT_EQ(notification_permissions[0].primary_pattern,
             ContentSettingsPattern::FromString(urls[1]));
+
+  ContentSettingsForOneType ignored_patterns;
+  map->GetSettingsForOneType(
+      ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns);
+  EXPECT_EQ(ignored_patterns.size(), 1UL);
+  EXPECT_EQ(ignored_patterns[0].primary_pattern, pattern_to_ignore);
+
+  // On blocking notifications for an unrelated site, nothing changes.
+  map->SetContentSettingDefaultScope(GURL(urls[1]), GURL(),
+                                     ContentSettingsType::NOTIFICATIONS,
+                                     CONTENT_SETTING_ALLOW);
+  EXPECT_EQ(service->GetNotificationSiteListForReview().size(), 1UL);
+  map->GetSettingsForOneType(
+      ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns);
+  EXPECT_EQ(ignored_patterns.size(), 1UL);
+  EXPECT_EQ(ignored_patterns[0].primary_pattern, pattern_to_ignore);
+
+  // If the permissions for an element of the block list are modified (i.e. no
+  // longer ALLOWed), the element should be removed from the list.
+  map->SetContentSettingDefaultScope(GURL(urls[0]), GURL(),
+                                     ContentSettingsType::NOTIFICATIONS,
+                                     CONTENT_SETTING_BLOCK);
+  map->GetSettingsForOneType(
+      ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns);
+  EXPECT_EQ(ignored_patterns.size(), 0UL);
+  EXPECT_EQ(service->GetNotificationSiteListForReview().size(), 1UL);
+  // The site is presented again if permissions are re-granted.
+  map->SetContentSettingDefaultScope(GURL(urls[0]), GURL(),
+                                     ContentSettingsType::NOTIFICATIONS,
+                                     CONTENT_SETTING_ALLOW);
+  EXPECT_EQ(service->GetNotificationSiteListForReview().size(), 2UL);
 }
 
 // TODO(crbug.com/1363714): Move this test to ContentSettingsPatternTest.
diff --git a/chrome/browser/policy/device_management_service_configuration.cc b/chrome/browser/policy/device_management_service_configuration.cc
index 0804711..c231ed8 100644
--- a/chrome/browser/policy/device_management_service_configuration.cc
+++ b/chrome/browser/policy/device_management_service_configuration.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "build/build_config.h"
@@ -44,10 +45,9 @@
 }
 
 std::string DeviceManagementServiceConfiguration::GetAgentParameter() const {
-  return base::StringPrintf("%s %s(%s)",
-                            version_info::GetProductName().c_str(),
-                            version_info::GetVersionNumber().c_str(),
-                            version_info::GetLastChange().c_str());
+  return base::StrCat({version_info::GetProductName(), " ",
+                       version_info::GetVersionNumber(), "(",
+                       version_info::GetLastChange(), ")"});
 }
 
 std::string DeviceManagementServiceConfiguration::GetPlatformParameter() const {
diff --git a/chrome/browser/printing/system_access_process_print_browsertest.cc b/chrome/browser/printing/system_access_process_print_browsertest.cc
index dc77e84a..ab216e16 100644
--- a/chrome/browser/printing/system_access_process_print_browsertest.cc
+++ b/chrome/browser/printing/system_access_process_print_browsertest.cc
@@ -905,9 +905,16 @@
       print_view_manager.snooped_params();
   ASSERT_TRUE(snooped_params);
   EXPECT_EQ(test::kPrinterCapabilitiesDpi, snooped_params->params->dpi);
+
+#if BUILDFLAG(IS_MAC)
+  EXPECT_EQ(kLegalPhysicalSize, snooped_params->params->page_size);
+  EXPECT_EQ(kLegalPrintableArea, snooped_params->params->printable_area);
+  EXPECT_EQ(kLegalExpectedContentSize, snooped_params->params->content_size);
+#else
   EXPECT_EQ(kLetterPhysicalSize, snooped_params->params->page_size);
   EXPECT_EQ(kLetterPrintableArea, snooped_params->params->printable_area);
   EXPECT_EQ(kLetterExpectedContentSize, snooped_params->params->content_size);
+#endif
 }
 
 #if BUILDFLAG(ENABLE_OOP_PRINTING)
@@ -935,9 +942,16 @@
       print_view_manager.snooped_params();
   ASSERT_TRUE(snooped_params);
   EXPECT_EQ(test::kPrinterCapabilitiesDpi, snooped_params->params->dpi);
+
+#if BUILDFLAG(IS_MAC)
+  EXPECT_EQ(kLetterPhysicalSize, snooped_params->params->page_size);
+  EXPECT_EQ(kLetterPrintableArea, snooped_params->params->printable_area);
+  EXPECT_EQ(kLetterExpectedContentSize, snooped_params->params->content_size);
+#else
   EXPECT_EQ(kLegalPhysicalSize, snooped_params->params->page_size);
   EXPECT_EQ(kLegalPrintableArea, snooped_params->params->printable_area);
   EXPECT_EQ(kLegalExpectedContentSize, snooped_params->params->content_size);
+#endif
 }
 
 IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 9ba4918..ee16ae2 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -304,13 +304,13 @@
     "router.ts",
     "safety_check_page/safety_check_browser_proxy.ts",
     "safety_check_page/safety_check_extensions_browser_proxy.ts",
+    "safety_hub/safety_hub_browser_proxy.ts",
     "search_engines_page/search_engines_browser_proxy.ts",
     "search_settings.ts",
     "settings.ts",
     "settings_page/main_page_mixin.ts",
     "site_settings/constants.ts",
     "site_settings/site_settings_mixin.ts",
-    "site_settings/site_settings_permissions_browser_proxy.ts",
     "site_settings/site_settings_prefs_browser_proxy.ts",
     "site_settings/website_usage_browser_proxy.ts",
     "site_settings_page/site_settings_page_util.ts",
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.ts b/chrome/browser/resources/settings/chromeos/lazy_load.ts
index 8fc45e9..3f7942b 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.ts
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.ts
@@ -148,7 +148,7 @@
 export {LanguageState} from './os_languages_page/languages_types.js';
 export {OsSettingsClearPersonalizedDataDialogElement} from './os_languages_page/os_japanese_clear_ime_data_dialog.js';
 export {OsSettingsSmartInputsPageElement} from './os_languages_page/smart_inputs_page.js';
-export {AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './os_people_page/account_manager_browser_proxy.js';
+export {Account, AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './os_people_page/account_manager_browser_proxy.js';
 export {SettingsUsersAddUserDialogElement} from './os_people_page/add_user_dialog.js';
 export {FingerprintBrowserProxy, FingerprintBrowserProxyImpl, FingerprintInfo, FingerprintResultType} from './os_people_page/fingerprint_browser_proxy.js';
 export {SettingsFingerprintListSubpageElement} from './os_people_page/fingerprint_list_subpage.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_page_availability.ts b/chrome/browser/resources/settings/chromeos/os_page_availability.ts
index dded05ba..bc5edae 100644
--- a/chrome/browser/resources/settings/chromeos/os_page_availability.ts
+++ b/chrome/browser/resources/settings/chromeos/os_page_availability.ts
@@ -16,26 +16,9 @@
  */
 
 import {isGuest, isKerberosEnabled, isPowerwashAllowed} from './common/load_time_booleans.js';
+import {OsPageAvailability} from './mojom-webui/routes.mojom-webui.js';
 
-export interface OsPageAvailability {
-  a11y: boolean;
-  apps: boolean;
-  bluetooth: boolean;
-  crostini: boolean;
-  dateTime: boolean;
-  device: boolean;
-  files: boolean;
-  internet: boolean;
-  kerberos: boolean;
-  languages: boolean;
-  multidevice: boolean;
-  people: boolean;
-  personalization: boolean;
-  printing: boolean;
-  privacy: boolean;
-  reset: boolean;
-  search: boolean;
-}
+export {OsPageAvailability};
 
 /**
  * Used to create the pageAvailability object.
@@ -46,7 +29,6 @@
 export function createPageAvailability(): OsPageAvailability {
   if (isGuest()) {
     return {
-      a11y: true,
       apps: true,
       bluetooth: true,
       crostini: true,
@@ -55,18 +37,18 @@
       files: false,
       internet: true,
       kerberos: isKerberosEnabled(),
-      languages: true,
       multidevice: false,
-      people: false,
+      osAccessibility: true,
+      osLanguages: true,
+      osPeople: false,
+      osPrinting: true,
+      osPrivacy: true,
+      osReset: isPowerwashAllowed(),
+      osSearch: true,
       personalization: false,
-      printing: true,
-      privacy: true,
-      reset: isPowerwashAllowed(),
-      search: true,
     };
   }
   return {
-    a11y: true,
     apps: true,
     bluetooth: true,
     crostini: true,
@@ -75,13 +57,14 @@
     files: true,
     internet: true,
     kerberos: isKerberosEnabled(),
-    languages: true,
     multidevice: true,
-    people: true,
+    osAccessibility: true,
+    osLanguages: true,
+    osPeople: true,
+    osPrinting: true,
+    osPrivacy: true,
+    osReset: isPowerwashAllowed(),
+    osSearch: true,
     personalization: true,
-    printing: true,
-    privacy: true,
-    reset: isPowerwashAllowed(),
-    search: true,
   };
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
index 03b38111..a603982d 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.ts
@@ -41,7 +41,8 @@
 const OsSettingsPeoplePageElementBase =
     LockStateMixin(RouteObserverMixin(DeepLinkingMixin(PolymerElement)));
 
-class OsSettingsPeoplePageElement extends OsSettingsPeoplePageElementBase {
+export class OsSettingsPeoplePageElement extends
+    OsSettingsPeoplePageElementBase {
   static get is() {
     return 'os-settings-people-page' as const;
   }
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.html b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.html
index d41adf8..3b9e3de8 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.html
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.html
@@ -27,7 +27,7 @@
           <iron-icon id="printerStatusIcon"
             hidden="[[!showPrinterIcon_(printerEntry.printerType)]]"
             icon="[[getPrinterIcon_(printerEntry.printerType,
-                printerEntry.printerInfo.printerStatusReason)]]">
+                printerEntry.printerInfo.printerId)]]">
           </iron-icon>
           [[printerEntry.printerInfo.printerName]]
       </span>
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.ts
index fc9b73db..b2b0ffb 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers_entry.ts
@@ -18,7 +18,7 @@
 
 import {PrinterListEntry, PrinterType} from './cups_printer_types.js';
 import {getTemplate} from './cups_printers_entry.html.js';
-import {computePrinterState, PrinterState} from './printer_status.js';
+import {computePrinterState, PrinterState, PrinterStatusReason} from './printer_status.js';
 
 const SettingsCupsPrintersEntryElementBase = FocusRowMixin(PolymerElement);
 
@@ -58,6 +58,13 @@
       },
 
       /**
+       * The cache of printer status reasons used to look up this entry's
+       * current printer status. Populated and maintained by
+       * cups_saved_printers.ts.
+       */
+      printerStatusReasonCache: Map<string, PrinterStatusReason>,
+
+      /**
        * True when the "printer-settings-printer-status" feature flag is
        * enabled.
        */
@@ -87,6 +94,7 @@
   savingPrinter: boolean;
   subtext: string;
   userPrintersAllowed: boolean;
+  printerStatusReasonCache: Map<string, PrinterStatusReason>;
   private isPrinterSettingsRevampEnabled_: boolean;
   private isPrinterSettingsPrinterStatusEnabled_: boolean;
 
@@ -199,9 +207,14 @@
       return `os-settings:printer-status-green`;
     }
 
+    const printerStatusReason = this.printerStatusReasonCache.get(
+        this.printerEntry.printerInfo.printerId);
+    if (!printerStatusReason) {
+      return `os-settings:printer-status-grey`;
+    }
+
     let iconColor = '';
-    switch (computePrinterState(
-        this.printerEntry.printerInfo.printerStatusReason)) {
+    switch (computePrinterState(printerStatusReason)) {
       case PrinterState.GOOD:
         iconColor = 'green';
         break;
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.html b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.html
index 63c484e..c4977bb2 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.html
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.html
@@ -48,7 +48,8 @@
     <settings-cups-printers-entry printer-entry="[[item]]"
         tabindex$="[[tabIndex]]" last-focused="{{lastFocused_}}"
         list-blurred="{{listBlurred_}}" focus-row-index="[[index]]"
-        iron-list-tab-index="[[tabIndex]]">
+        iron-list-tab-index="[[tabIndex]]"
+        printer-status-reason-cache="[[printerStatusReasonCache_]]">
     </settings-cups-printers-entry>
   </template>
 </iron-list>
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.ts
index f87e32c..f8bb12a 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_saved_printers.ts
@@ -28,7 +28,7 @@
 import {CupsPrinterInfo, CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl} from './cups_printers_browser_proxy.js';
 import {CupsPrintersEntryListMixin} from './cups_printers_entry_list_mixin.js';
 import {getTemplate} from './cups_saved_printers.html.js';
-import {getStatusReasonFromPrinterStatus, PrinterStatus} from './printer_status.js';
+import {getStatusReasonFromPrinterStatus, PrinterStatus, PrinterStatusReason} from './printer_status.js';
 
 /**
  * If the Show more button is visible, the minimum number of printers we show
@@ -123,6 +123,17 @@
       listBlurred_: Boolean,
 
       /**
+       * The cache of printer status reasons. Used by printer status entries to
+       * look up their current printer status.
+       */
+      printerStatusReasonCache_: {
+        type: Map<string, PrinterStatusReason>,
+        value() {
+          return new Map();
+        },
+      },
+
+      /**
        * True when the "printer-settings-printer-status" feature flag is
        * enabled.
        */
@@ -157,6 +168,7 @@
   private listBlurred_: boolean;
   private newPrinters_: PrinterListEntry[];
   private visiblePrinterCounter_: number;
+  private printerStatusReasonCache_: Map<string, PrinterStatusReason>;
   private isPrinterSettingsPrinterStatusEnabled_: boolean;
 
   constructor() {
@@ -367,8 +379,8 @@
   }
 
   /**
-   * For each printer status received, search for its respective saved printer
-   * and update its status.
+   * For each printer status received, add it to the printer status cache then
+   * notify its respective printer entry to update its status.
    */
   private onPrinterStatusReceived_(printerStatus: PrinterStatus) {
     assert(this.isPrinterSettingsPrinterStatusEnabled_);
@@ -376,28 +388,19 @@
       return;
     }
 
-    // Find the associated printer and set its status reason.
-    const index = this.savedPrinters.findIndex(
-        printer => printer.printerInfo.printerId === printerStatus.printerId);
-    if (index === -1) {
-      return;
-    }
+    this.printerStatusReasonCache_.set(
+        printerStatus.printerId,
+        getStatusReasonFromPrinterStatus(printerStatus));
 
-    this.savedPrinters[index].printerInfo.printerStatusReason =
-        getStatusReasonFromPrinterStatus(printerStatus);
-
-
-    // Even though the online state is set on `savedPrinters`, the actual
-    // printer entries displayed are from `filteredPrinters_`. So notify the
-    // specific filtered printer entry to update its icon.
+    // The actual printer entries displayed are from `filteredPrinters_`. So
+    // notify the specific filtered printer entry to update its icon.
     const filteredIndex = this.filteredPrinters_.findIndex(
         printer => printer.printerInfo.printerId === printerStatus.printerId);
     if (filteredIndex === -1) {
       return;
     }
 
-    this.notifyPath(
-        `filteredPrinters_.${filteredIndex}.printerInfo.printerStatusReason`);
+    this.notifyPath(`filteredPrinters_.${filteredIndex}.printerInfo.printerId`);
   }
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.ts b/chrome/browser/resources/settings/chromeos/os_settings.ts
index ec3e6bf..72a3fe5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings.ts
@@ -99,7 +99,7 @@
 export {SettingsSliderElement} from '/shared/settings/controls/settings_slider.js';
 export {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
 export {LifetimeBrowserProxyImpl} from '/shared/settings/lifetime_browser_proxy.js';
-export {ProfileInfoBrowserProxyImpl} from '/shared/settings/people_page/profile_info_browser_proxy.js';
+export {ProfileInfoBrowserProxy, ProfileInfoBrowserProxyImpl} from '/shared/settings/people_page/profile_info_browser_proxy.js';
 export {PageStatus, StatusAction, StoredAccount, SyncBrowserProxy, SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '/shared/settings/people_page/sync_browser_proxy.js';
 export {PrivacyPageBrowserProxyImpl, SecureDnsMode, SecureDnsUiManagementMode} from '/shared/settings/privacy_page/privacy_page_browser_proxy.js';
 export {LocalizedLinkElement} from 'chrome://resources/cr_components/localized_link/localized_link.js';
@@ -172,6 +172,7 @@
 export {GoogleDriveBrowserProxy, GoogleDrivePageCallbackRouter, GoogleDrivePageHandlerRemote, GoogleDrivePageRemote, Stage} from './os_files_page/google_drive_browser_proxy.js';
 export {ConfirmationDialogType, SettingsGoogleDriveSubpageElement} from './os_files_page/google_drive_subpage.js';
 export {createPageAvailability as createPageAvailabilityForTesting, OsPageAvailability} from './os_page_availability.js';
+export {OsSettingsPeoplePageElement} from './os_people_page/os_people_page.js';
 export {MetricsConsentBrowserProxy, MetricsConsentBrowserProxyImpl, MetricsConsentState} from './os_privacy_page/metrics_consent_browser_proxy.js';
 export {OsSettingsPrivacyPageElement} from './os_privacy_page/os_privacy_page.js';
 export {DataAccessPolicyState, PeripheralDataAccessBrowserProxy, PeripheralDataAccessBrowserProxyImpl} from './os_privacy_page/peripheral_data_access_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
index 78f27cc..545188f 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
@@ -159,7 +159,7 @@
         label: this.i18n('multidevicePageTitle'),
       },
       {
-        pageName: 'people',
+        pageName: 'osPeople',
         path: routesMojom.PEOPLE_SECTION_PATH,
         icon: 'cr:person',
         label: this.i18n('osPeoplePageTitle'),
@@ -183,13 +183,13 @@
         label: this.i18n('personalizationPageTitle'),
       },
       {
-        pageName: 'search',
+        pageName: 'osSearch',
         path: routesMojom.SEARCH_AND_ASSISTANT_SECTION_PATH,
         icon: 'cr:search',
         label: this.i18n('osSearchPageTitle'),
       },
       {
-        pageName: 'privacy',
+        pageName: 'osPrivacy',
         path: routesMojom.PRIVACY_AND_SECURITY_SECTION_PATH,
         icon: 'cr:security',
         label: this.i18n('privacyPageTitle'),
@@ -201,7 +201,7 @@
         label: this.i18n('appsPageTitle'),
       },
       {
-        pageName: 'a11y',
+        pageName: 'osAccessibility',
         path: routesMojom.ACCESSIBILITY_SECTION_PATH,
         icon: 'os-settings:accessibility',
         label: this.i18n('a11yPageTitle'),
@@ -221,7 +221,7 @@
         label: this.i18n('dateTimePageTitle'),
       },
       {
-        pageName: 'languages',
+        pageName: 'osLanguages',
         path: routesMojom.LANGUAGES_AND_INPUT_SECTION_PATH,
         icon: 'os-settings:language',
         label: this.i18n('osLanguagesPageTitle'),
@@ -233,7 +233,7 @@
         label: this.i18n('filesPageTitle'),
       },
       {
-        pageName: 'printing',
+        pageName: 'osPrinting',
         path: routesMojom.PRINTING_SECTION_PATH,
         icon: 'os-settings:print',
         label: this.i18n('printingPageTitle'),
@@ -245,7 +245,7 @@
         label: this.i18n('crostiniPageTitle'),
       },
       {
-        pageName: 'reset',
+        pageName: 'osReset',
         path: routesMojom.RESET_SECTION_PATH,
         icon: 'os-settings:restore',
         label: this.i18n('resetPageTitle'),
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
index 20f08ba..e538d1fb 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
@@ -249,7 +249,7 @@
         </settings-crostini-page>
       </os-settings-section>
       <template is="dom-if"
-          if="[[shouldStampPage_(pageAvailability.reset)]]" restamp>
+          if="[[shouldStampPage_(pageAvailability.osReset)]]" restamp>
         <os-settings-section page-title="$i18n{resetPageTitle}"
             section="osReset">
           <os-settings-reset-page></os-settings-reset-page>
diff --git a/chrome/browser/resources/settings/lazy_load.ts b/chrome/browser/resources/settings/lazy_load.ts
index d5f7be28..fa555ae 100644
--- a/chrome/browser/resources/settings/lazy_load.ts
+++ b/chrome/browser/resources/settings/lazy_load.ts
@@ -208,6 +208,7 @@
 export {SafeBrowsingSetting, SettingsSecurityPageElement} from './privacy_page/security_page.js';
 export {SettingsResetPageElement} from './reset_page/reset_page.js';
 export {SettingsResetProfileDialogElement} from './reset_page/reset_profile_dialog.js';
+export {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, UnusedSitePermissions} from './safety_hub/safety_hub_browser_proxy.js';
 export {SettingsOmniboxExtensionEntryElement} from './search_engines_page/omnibox_extension_entry.js';
 export {SettingsSearchEngineEditDialogElement} from './search_engines_page/search_engine_edit_dialog.js';
 export {SettingsSearchEngineEntryElement} from './search_engines_page/search_engine_entry.js';
@@ -237,7 +238,6 @@
 export {SiteEntryElement} from './site_settings/site_entry.js';
 export {SiteListElement} from './site_settings/site_list.js';
 export {SiteListEntryElement} from './site_settings/site_list_entry.js';
-export {SiteSettingsPermissionsBrowserProxy, SiteSettingsPermissionsBrowserProxyImpl, UnusedSitePermissions} from './site_settings/site_settings_permissions_browser_proxy.js';
 export {ChooserException, ContentSettingProvider, CookiePrimarySetting, DefaultContentSetting, FileSystemGrantsForOrigin, NotificationPermission, OriginInfo, RawChooserException, RawFileSystemGrant, RawSiteException, RecentSitePermissions, SiteException, SiteGroup, SiteSettingsPrefsBrowserProxy, SiteSettingsPrefsBrowserProxyImpl, ZoomLevelEntry} from './site_settings/site_settings_prefs_browser_proxy.js';
 export {WebsiteUsageBrowserProxy, WebsiteUsageBrowserProxyImpl} from './site_settings/website_usage_browser_proxy.js';
 export {ZoomLevelsElement} from './site_settings/zoom_levels.js';
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_page.ts b/chrome/browser/resources/settings/safety_check_page/safety_check_page.ts
index 3e17d28..fffeee6 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_page.ts
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_page.ts
@@ -31,7 +31,7 @@
 import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyCheckInteractions} from '../metrics_browser_proxy.js';
 import {routes} from '../route.js';
 import {Route, RouteObserverMixin, Router} from '../router.js';
-import {SiteSettingsPermissionsBrowserProxy, SiteSettingsPermissionsBrowserProxyImpl, UnusedSitePermissions} from '../site_settings/site_settings_permissions_browser_proxy.js';
+import {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, UnusedSitePermissions} from '../safety_hub/safety_hub_browser_proxy.js';
 import {NotificationPermission, SiteSettingsPrefsBrowserProxy, SiteSettingsPrefsBrowserProxyImpl} from '../site_settings/site_settings_prefs_browser_proxy.js';
 
 import {SafetyCheckBrowserProxy, SafetyCheckBrowserProxyImpl, SafetyCheckCallbackConstants, SafetyCheckParentStatus} from './safety_check_browser_proxy.js';
@@ -98,8 +98,8 @@
   private shouldRecordMetrics_: boolean = false;
   private siteSettingsBrowserProxy_: SiteSettingsPrefsBrowserProxy =
       SiteSettingsPrefsBrowserProxyImpl.getInstance();
-  private permissionsBrowserProxy_: SiteSettingsPermissionsBrowserProxy =
-      SiteSettingsPermissionsBrowserProxyImpl.getInstance();
+  private permissionsBrowserProxy_: SafetyHubBrowserProxy =
+      SafetyHubBrowserProxyImpl.getInstance();
   private safetyCheckBrowserProxy_: SafetyCheckBrowserProxy =
       SafetyCheckBrowserProxyImpl.getInstance();
   private metricsBrowserProxy_: MetricsBrowserProxy =
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_unused_site_permissions.ts b/chrome/browser/resources/settings/safety_check_page/safety_check_unused_site_permissions.ts
index 0a7e9c1..050faa2 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_unused_site_permissions.ts
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_unused_site_permissions.ts
@@ -19,7 +19,7 @@
 import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyCheckInteractions} from '../metrics_browser_proxy.js';
 import {routes} from '../route.js';
 import {Router} from '../router.js';
-import {UnusedSitePermissions, SiteSettingsPermissionsBrowserProxy, SiteSettingsPermissionsBrowserProxyImpl} from '../site_settings/site_settings_permissions_browser_proxy.js';
+import {UnusedSitePermissions, SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl} from '../safety_hub/safety_hub_browser_proxy.js';
 
 import {SafetyCheckIconStatus, SettingsSafetyCheckChildElement} from './safety_check_child.js';
 import {getTemplate} from './safety_check_unused_site_permissions.html.js';
@@ -60,8 +60,8 @@
   private headerString_: string;
   private iconStatus_: SafetyCheckIconStatus;
 
-  private browserProxy_: SiteSettingsPermissionsBrowserProxy =
-      SiteSettingsPermissionsBrowserProxyImpl.getInstance();
+  private browserProxy_: SafetyHubBrowserProxy =
+      SafetyHubBrowserProxyImpl.getInstance();
   private metricsBrowserProxy_: MetricsBrowserProxy =
       MetricsBrowserProxyImpl.getInstance();
 
diff --git a/chrome/browser/resources/settings/safety_hub/OWNERS b/chrome/browser/resources/settings/safety_hub/OWNERS
new file mode 100644
index 0000000..ac99977
--- /dev/null
+++ b/chrome/browser/resources/settings/safety_hub/OWNERS
@@ -0,0 +1,5 @@
+# Primary
+sideyilmaz@chromium.org
+
+# Secondary
+rainhard@chromium.org
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_permissions_browser_proxy.ts b/chrome/browser/resources/settings/safety_hub/safety_hub_browser_proxy.ts
similarity index 84%
rename from chrome/browser/resources/settings/site_settings/site_settings_permissions_browser_proxy.ts
rename to chrome/browser/resources/settings/safety_hub/safety_hub_browser_proxy.ts
index 84258b34..7c94c4de 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_permissions_browser_proxy.ts
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_browser_proxy.ts
@@ -10,7 +10,7 @@
 // clang-format off
 import {sendWithPromise} from 'chrome://resources/js/cr.js';
 
-import {ContentSettingsTypes} from './constants.js';
+import {ContentSettingsTypes} from '../site_settings/constants.js';
 // clang-format on
 
 export interface UnusedSitePermissions {
@@ -23,7 +23,7 @@
  * TODO(crbug.com/1383197): Move functions related to notification permission
  * review here as well.
  */
-export interface SiteSettingsPermissionsBrowserProxy {
+export interface SafetyHubBrowserProxy {
   /**
    * Mark revoked permissions of unused sites as reviewed by the user so they
    * will not be shown again.
@@ -58,8 +58,7 @@
                                              UnusedSitePermissions): void;
 }
 
-export class SiteSettingsPermissionsBrowserProxyImpl implements
-    SiteSettingsPermissionsBrowserProxy {
+export class SafetyHubBrowserProxyImpl implements SafetyHubBrowserProxy {
   acknowledgeRevokedUnusedSitePermissionsList() {
     chrome.send('acknowledgeRevokedUnusedSitePermissionsList');
   }
@@ -85,14 +84,13 @@
         'undoAllowPermissionsAgainForUnusedSite', [unusedSitePermissions]);
   }
 
-  static getInstance(): SiteSettingsPermissionsBrowserProxy {
-    return instance ||
-        (instance = new SiteSettingsPermissionsBrowserProxyImpl());
+  static getInstance(): SafetyHubBrowserProxy {
+    return instance || (instance = new SafetyHubBrowserProxyImpl());
   }
 
-  static setInstance(obj: SiteSettingsPermissionsBrowserProxy) {
+  static setInstance(obj: SafetyHubBrowserProxy) {
     instance = obj;
   }
 }
 
-let instance: SiteSettingsPermissionsBrowserProxy|null = null;
+let instance: SafetyHubBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
index 27fbef7..9317403 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
@@ -25,8 +25,8 @@
 import {loadTimeData} from '../i18n_setup.js';
 import {routes} from '../route.js';
 import {Router} from '../router.js';
+import {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, UnusedSitePermissions} from '../safety_hub/safety_hub_browser_proxy.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
-import {SiteSettingsPermissionsBrowserProxy, SiteSettingsPermissionsBrowserProxyImpl, UnusedSitePermissions} from '../site_settings/site_settings_permissions_browser_proxy.js';
 
 import {CategoryListItem} from './site_settings_list.js';
 import {getTemplate} from './site_settings_page.html.js';
@@ -478,9 +478,8 @@
   private noRecentSitePermissions_: boolean;
   private showUnusedSitePermissions_: boolean;
   private unusedSitePermissionsEnabled_: boolean;
-  private siteSettingsPermissionsBrowserProxy_:
-      SiteSettingsPermissionsBrowserProxy =
-          SiteSettingsPermissionsBrowserProxyImpl.getInstance();
+  private siteSettingsPermissionsBrowserProxy_: SafetyHubBrowserProxy =
+      SafetyHubBrowserProxyImpl.getInstance();
 
   private lists_: {
     all: CategoryListItem[],
diff --git a/chrome/browser/resources/settings/site_settings_page/unused_site_permissions.ts b/chrome/browser/resources/settings/site_settings_page/unused_site_permissions.ts
index e34f64e..2b5e494 100644
--- a/chrome/browser/resources/settings/site_settings_page/unused_site_permissions.ts
+++ b/chrome/browser/resources/settings/site_settings_page/unused_site_permissions.ts
@@ -27,9 +27,9 @@
 import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyCheckUnusedSitePermissionsModuleInteractions} from '../metrics_browser_proxy.js';
 import {routes} from '../route.js';
 import {Route, RouteObserverMixin} from '../router.js';
+import {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, UnusedSitePermissions} from '../safety_hub/safety_hub_browser_proxy.js';
 import {ContentSettingsTypes, MODEL_UPDATE_DELAY_MS} from '../site_settings/constants.js';
 import {SiteSettingsMixin} from '../site_settings/site_settings_mixin.js';
-import {SiteSettingsPermissionsBrowserProxy, SiteSettingsPermissionsBrowserProxyImpl, UnusedSitePermissions} from '../site_settings/site_settings_permissions_browser_proxy.js';
 import {TooltipMixin} from '../tooltip_mixin.js';
 
 import {getLocalizationStringForContentType} from './site_settings_page_util.js';
@@ -128,8 +128,8 @@
     };
   }
 
-  private browserProxy_: SiteSettingsPermissionsBrowserProxy =
-      SiteSettingsPermissionsBrowserProxyImpl.getInstance();
+  private browserProxy_: SafetyHubBrowserProxy =
+      SafetyHubBrowserProxyImpl.getInstance();
   private eventTracker_: EventTracker = new EventTracker();
   private headerString_: string;
   private lastUnusedSitePermissionsAllowedAgain_: UnusedSitePermissions|null;
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
index 424b694..e0099301 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.html
@@ -130,7 +130,6 @@
     --cr-toolbar-selection-overlay-padding: 16px;
     background: var(--edit-footer-background);
     color: var(--cr-secondary-text-color);
-    font-size: 12px;
   }
 
   cr-toolbar-selection-overlay::part(clearIcon) {
diff --git a/chrome/browser/resources/side_panel/reading_list/BUILD.gn b/chrome/browser/resources/side_panel/reading_list/BUILD.gn
index f763b7b..f20f8e3 100644
--- a/chrome/browser/resources/side_panel/reading_list/BUILD.gn
+++ b/chrome/browser/resources/side_panel/reading_list/BUILD.gn
@@ -30,6 +30,7 @@
   ts_composite = true
   ts_deps = [
     "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/cr_components/color_change_listener:build_ts",
     "//ui/webui/resources/cr_components/help_bubble:build_ts",
     "//ui/webui/resources/cr_elements:build_ts",
     "//ui/webui/resources/js:build_ts",
diff --git a/chrome/browser/resources/side_panel/reading_list/app.ts b/chrome/browser/resources/side_panel/reading_list/app.ts
index 59c351a..f5dbc68 100644
--- a/chrome/browser/resources/side_panel/reading_list/app.ts
+++ b/chrome/browser/resources/side_panel/reading_list/app.ts
@@ -15,6 +15,7 @@
 import './reading_list_item.js';
 import '../strings.m.js';
 
+import {startColorChangeUpdater} from '//resources/cr_components/color_change_listener/colors_css_updater.js';
 import {HelpBubbleMixin, HelpBubbleMixinInterface} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
@@ -97,6 +98,7 @@
 
   constructor() {
     super();
+    startColorChangeUpdater();
 
     this.visibilityChangedListener_ = () => {
       // Refresh Reading List's list data when transitioning into a visible
diff --git a/chrome/browser/resources/side_panel/reading_list/reading_list.html b/chrome/browser/resources/side_panel/reading_list/reading_list.html
index 82a80277..eb07515 100644
--- a/chrome/browser/resources/side_panel/reading_list/reading_list.html
+++ b/chrome/browser/resources/side_panel/reading_list/reading_list.html
@@ -4,6 +4,7 @@
 <head>
   <meta charset="utf-8">
   <title>$i18n{title}</title>
+  <link rel="stylesheet" href="chrome://theme/colors.css?sets=ui,chrome">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <style>
     html,
diff --git a/chrome/browser/resources/side_panel/shared/sp_filter_chip.html b/chrome/browser/resources/side_panel/shared/sp_filter_chip.html
index 5c46e77..086c9998 100644
--- a/chrome/browser/resources/side_panel/shared/sp_filter_chip.html
+++ b/chrome/browser/resources/side_panel/shared/sp_filter_chip.html
@@ -11,7 +11,7 @@
     color: var(--cr-secondary-text-color);
     display: flex;
     flex-direction: row;
-    font-size: 11px;
+    font-size: 13px;
     gap: 4px;
     height: 28px;
     padding: 0 6px;
diff --git a/chrome/browser/resources/side_panel/shared/sp_heading.html b/chrome/browser/resources/side_panel/shared/sp_heading.html
index ee3d179..09fca45 100644
--- a/chrome/browser/resources/side_panel/shared/sp_heading.html
+++ b/chrome/browser/resources/side_panel/shared/sp_heading.html
@@ -40,7 +40,7 @@
   ::slotted([slot=heading]:is(h1, h2, h3, h4, h5, h6)) {
     color: var(--cr-primary-text-color);
     flex: 1;
-    font-size: 15px;
+    font-size: 13px;
     font-weight: 500;
     line-height: 20px;
     margin: 0;
@@ -74,7 +74,7 @@
   ::slotted([slot=metadata]) {
     color: var(--cr-secondary-text-color);
     flex-shrink: 0;
-    font-size: 11px;
+    font-size: 13px;
     font-weight: 400;
     line-height: 20px;
     overflow: hidden;
@@ -83,6 +83,7 @@
   }
 
   :host-context([chrome-refresh-2023]) ::slotted([slot=metadata]) {
+    font-size: 11px;
     line-height: 16px;
   }
 </style>
diff --git a/chrome/browser/safe_browsing/chrome_user_population_helper_unittest.cc b/chrome/browser/safe_browsing/chrome_user_population_helper_unittest.cc
index abd21378..54738a60 100644
--- a/chrome/browser/safe_browsing/chrome_user_population_helper_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_user_population_helper_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/feature_list.h"
 #include "base/metrics/field_trial.h"
+#include "base/strings/strcat.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
@@ -186,8 +187,8 @@
   content::BrowserTaskEnvironment task_environment;
   TestingProfile profile;
   std::string user_agent =
-      version_info::GetProductNameAndVersionForUserAgent() + "/" +
-      version_info::GetOSType();
+      base::StrCat({version_info::GetProductNameAndVersionForUserAgent(), "/",
+                    version_info::GetOSType()});
   ChromeUserPopulation population = GetUserPopulationForProfile(&profile);
   EXPECT_EQ(population.user_agent(), user_agent);
 }
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
index 575e889..b2dfcf3 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
@@ -12,6 +12,7 @@
 #include "base/rand_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
+#include "base/task/bind_post_task.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
 #include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
@@ -51,20 +52,6 @@
       metric_name, FileTypePolicies::GetInstance()->UmaValueForFile(file));
 }
 
-bool CheckUrlAgainstAllowlist(
-    const GURL& url,
-    scoped_refptr<SafeBrowsingDatabaseManager> database_manager) {
-  DCHECK_CURRENTLY_ON(base::FeatureList::IsEnabled(kSafeBrowsingOnUIThread)
-                          ? content::BrowserThread::UI
-                          : content::BrowserThread::IO);
-
-  if (!database_manager.get()) {
-    return false;
-  }
-
-  return (url.is_valid() && database_manager->MatchDownloadAllowlistUrl(url));
-}
-
 std::string SanitizeUrl(const std::string& url) {
   return GURL(url).DeprecatedGetOriginAsURL().spec();
 }
@@ -142,25 +129,27 @@
     return;
   }
 
+  if (!database_manager_ || !source_url_.is_valid()) {
+    OnUrlAllowlistCheckDone(false);
+    return;
+  }
+
   // If allowlist check passes, FinishRequest() will be called to avoid
   // analyzing file. Otherwise, AnalyzeFile() will be called to continue with
   // analysis.
+  auto callback = base::BindOnce(
+      &CheckClientDownloadRequestBase::OnUrlAllowlistCheckDone, GetWeakPtr());
   if (base::FeatureList::IsEnabled(kSafeBrowsingOnUIThread)) {
-    auto weak_ptr = GetWeakPtr();
-    bool is_allowlisted =
-        CheckUrlAgainstAllowlist(source_url_, database_manager_);
-    if (!weak_ptr) {
-      // `CheckUrlAgainstAllowlist` could delete this object.
-      return;
-    }
-    OnUrlAllowlistCheckDone(is_allowlisted);
+    database_manager_->MatchDownloadAllowlistUrl(source_url_,
+                                                 std::move(callback));
   } else {
-    content::GetIOThreadTaskRunner({})->PostTaskAndReplyWithResult(
+    content::GetIOThreadTaskRunner({})->PostTask(
         FROM_HERE,
-        base::BindOnce(&CheckUrlAgainstAllowlist, source_url_,
-                       database_manager_),
-        base::BindOnce(&CheckClientDownloadRequestBase::OnUrlAllowlistCheckDone,
-                       GetWeakPtr()));
+        base::BindOnce(&safe_browsing::SafeBrowsingDatabaseManager::
+                           MatchDownloadAllowlistUrl,
+                       database_manager_, source_url_,
+                       base::BindPostTask(content::GetUIThreadTaskRunner({}),
+                                          std::move(callback))));
   }
 }
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index d611a05c..b1ffcb8 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -161,7 +161,8 @@
   MockSafeBrowsingDatabaseManager& operator=(
       const MockSafeBrowsingDatabaseManager&) = delete;
 
-  MOCK_METHOD1(MatchDownloadAllowlistUrl, bool(const GURL&));
+  MOCK_METHOD2(MatchDownloadAllowlistUrl,
+               void(const GURL&, base::OnceCallback<void(bool)>));
   MOCK_METHOD2(CheckDownloadUrl,
                bool(const std::vector<GURL>& url_chain,
                     SafeBrowsingDatabaseManager::Client* client));
@@ -338,6 +339,13 @@
     sb_service_ =
         base::MakeRefCounted<StrictMock<FakeSafeBrowsingService>>(profile());
     sb_service_->Initialize();
+    ON_CALL(*sb_service_->mock_database_manager(),
+            MatchDownloadAllowlistUrl(_, _))
+        .WillByDefault(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
+
     TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(
         sb_service_.get());
     binary_feature_extractor_ =
@@ -842,8 +850,11 @@
 
   if (policy_value) {
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
     EXPECT_CALL(*binary_feature_extractor_.get(),
                 ExtractImageFeatures(
@@ -941,14 +952,20 @@
 
   // We should not get whilelist checks for other URLs than specified below.
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
+              MatchDownloadAllowlistUrl(_, _))
       .Times(0);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(GURL("http://www.google.com/a.exe")))
-      .WillRepeatedly(Return(true));
+              MatchDownloadAllowlistUrl(GURL("http://www.google.com/a.exe"), _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(true);
+          });
 
   // Set sample rate to 0 to prevent sampling.
   SetAllowlistedDownloadSampleRate(0);
@@ -1035,11 +1052,15 @@
       .Times(1);
   // Assume http://www.allowlist.com/a.exe is on the allowlist.
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
+              MatchDownloadAllowlistUrl(_, _))
       .Times(0);
-  EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(GURL("http://www.allowlist.com/a.exe")))
-      .WillRepeatedly(Return(true));
+  EXPECT_CALL(
+      *sb_service_->mock_database_manager(),
+      MatchDownloadAllowlistUrl(GURL("http://www.allowlist.com/a.exe"), _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(true);
+          });
   url_chain_.emplace_back("http://www.allowlist.com/a.exe");
   // Set sample rate to 1.00, so download_service_ will always send download
   // pings for allowlisted downloads.
@@ -1220,8 +1241,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -1246,8 +1270,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(9);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -1433,8 +1460,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -1465,8 +1495,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -1500,8 +1533,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -1582,8 +1618,11 @@
         file_contents));
     ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     RunLoop run_loop;
     download_service_->CheckClientDownload(
         &item,
@@ -2008,8 +2047,11 @@
   EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
 #if !BUILDFLAG(IS_MAC)
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .WillOnce(SetCertificateContents("dummy cert data"));
@@ -2087,8 +2129,11 @@
   std::string remote_address = "10.11.12.13";
   EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
 #if !BUILDFLAG(IS_MAC)
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -2146,8 +2191,11 @@
   EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .WillRepeatedly(SetCertificateContents("dummy cert data"));
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -2425,8 +2473,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -2459,8 +2510,11 @@
     GURL tab_url("http://www.google.com/tab");
     EXPECT_CALL(item, GetTabUrl()).WillRepeatedly(ReturnRef(tab_url));
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
 
     int expect_count;
     if (base::FeatureList::IsEnabled(kSafeBrowsingOnUIThread)) {
@@ -2514,11 +2568,12 @@
   EXPECT_CALL(*item, GetTabUrl()).WillRepeatedly(ReturnRef(tab_url));
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Invoke([&item](const GURL&) {
-        item.reset();
-        return false;
-      }));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          Invoke([&item](const GURL&, base::OnceCallback<void(bool)> callback) {
+            item.reset();
+            std::move(callback).Run(false);
+          }));
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(0);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -2614,8 +2669,11 @@
   base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
   std::vector<base::FilePath::StringType> alternate_extensions;
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   struct {
     ClientDownloadResponse::Verdict verdict;
     DownloadCheckResult expected_result;
@@ -2654,8 +2712,11 @@
       FILE_PATH_LITERAL(".tmp"), FILE_PATH_LITERAL(".crx")};
   PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK, net::OK);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   SetExtendedReportingPreference(false);
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
@@ -2674,8 +2735,11 @@
   base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
   std::vector<base::FilePath::StringType> alternate_extensions;
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(true));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(true);
+          });
 
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
@@ -2694,8 +2758,11 @@
   PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
                   net::ERR_FAILED);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
       GURL("http://example.com/foo"), /*initiating_frame*/ nullptr,
@@ -2713,8 +2780,11 @@
   sb_service_->GetTestURLLoaderFactory(profile())->AddResponse(
       PPAPIDownloadRequest::GetDownloadRequestUrl().spec(), "Hello world!");
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
       GURL("http://example.com/foo"), /*initiating_frame*/ nullptr,
@@ -2730,8 +2800,11 @@
   base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
   std::vector<base::FilePath::StringType> alternate_extensions;
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
   download_service_->download_request_timeout_ms_ = 0;
   RunLoop run_loop;
@@ -2759,8 +2832,11 @@
       FILE_PATH_LITERAL(".txt"), FILE_PATH_LITERAL(".abc"),
       FILE_PATH_LITERAL(""), FILE_PATH_LITERAL(".sdF")};
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
   const GURL kRequestorUrl("http://example.com/foo");
   RunLoop run_loop;
@@ -3193,7 +3269,7 @@
                            FILE_PATH_LITERAL("a.tmp"),           // tmp_path
                            FILE_PATH_LITERAL("a.exe"));          // final_path
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
+              MatchDownloadAllowlistUrl(_, _))
       .Times(0);
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(_, _)).Times(0);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -3265,8 +3341,11 @@
 
   EXPECT_CALL(item, GetHash()).WillRepeatedly(ReturnRef(blocklisted_hash_));
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -3299,8 +3378,11 @@
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
 
   RunLoop run_loop;
   download_service_->CheckClientDownload(
@@ -3391,8 +3473,11 @@
       .WillRepeatedly(Return(download::DownloadItem::CANCELLED));
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -3446,8 +3531,11 @@
     PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
 
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
 
     RunLoop run_loop;
     download_service_->CheckClientDownload(
@@ -3503,8 +3591,11 @@
   EXPECT_CALL(item, GetReceivedBytes())
       .WillRepeatedly(Return(100 * 1024 * 1024));
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
@@ -3585,8 +3676,11 @@
       enterprise_connectors::ContentAnalysisResponse());
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(2);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -3768,8 +3862,11 @@
       /*final_path_literal=*/FILE_PATH_LITERAL("a.exe"));
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -3791,8 +3888,11 @@
       /*final_path_literal=*/FILE_PATH_LITERAL("a.exe"));
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(9);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -3868,8 +3968,11 @@
   item->web_contents = nullptr;
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -3955,8 +4058,11 @@
   navigation->Commit();
 
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .WillRepeatedly(SetCertificateContents("dummy cert data"));
   EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4109,9 +4215,13 @@
                              FILE_PATH_LITERAL("a.tmp"),       // tmp_path
                              FILE_PATH_LITERAL("a.exe"));      // final_path
 
-    EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-        .WillRepeatedly(Return(false));
+    EXPECT_CALL(
+        *sb_service_->mock_database_manager(),
+        MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
         .Times(1);
     EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4149,9 +4259,13 @@
                              FILE_PATH_LITERAL("a.tmp"),       // tmp_path
                              FILE_PATH_LITERAL("a.exe"));      // final_path
 
-    EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-        .WillRepeatedly(Return(false));
+    EXPECT_CALL(
+        *sb_service_->mock_database_manager(),
+        MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
         .Times(1);
     EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4200,9 +4314,13 @@
                              FILE_PATH_LITERAL("a.tmp"),       // tmp_path
                              FILE_PATH_LITERAL("a.exe"));      // final_path
 
-    EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-        .WillRepeatedly(Return(false));
+    EXPECT_CALL(
+        *sb_service_->mock_database_manager(),
+        MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
         .Times(1);
     EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4243,9 +4361,13 @@
                              FILE_PATH_LITERAL("a.tmp"),       // tmp_path
                              FILE_PATH_LITERAL("a.exe"));      // final_path
 
-    EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-        .WillRepeatedly(Return(false));
+    EXPECT_CALL(
+        *sb_service_->mock_database_manager(),
+        MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
         .Times(1);
     EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4289,9 +4411,13 @@
                              FILE_PATH_LITERAL("a.tmp"),       // tmp_path
                              FILE_PATH_LITERAL("a.exe"));      // final_path
 
-    EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe")))
-        .WillRepeatedly(Return(false));
+    EXPECT_CALL(
+        *sb_service_->mock_database_manager(),
+        MatchDownloadAllowlistUrl(GURL("http://www.evil.com/bla.exe"), _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
         .Times(1);
     EXPECT_CALL(*binary_feature_extractor_.get(),
@@ -4343,8 +4469,11 @@
         response.SerializeAsString());
 
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
     EXPECT_CALL(*binary_feature_extractor_.get(),
                 ExtractImageFeatures(
@@ -4377,8 +4506,11 @@
         response.SerializeAsString());
 
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
     EXPECT_CALL(*binary_feature_extractor_.get(),
                 ExtractImageFeatures(
@@ -4404,8 +4536,11 @@
                            FILE_PATH_LITERAL("a.exe"));           // final_path
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -4455,8 +4590,11 @@
                            FILE_PATH_LITERAL("a.exe"));           // final_path
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
@@ -4505,8 +4643,11 @@
                              FILE_PATH_LITERAL("a.exe"));  // final_path
     content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
     EXPECT_CALL(*sb_service_->mock_database_manager(),
-                MatchDownloadAllowlistUrl(_))
-        .WillRepeatedly(Return(false));
+                MatchDownloadAllowlistUrl(_, _))
+        .WillRepeatedly(
+            [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+              std::move(callback).Run(false);
+            });
     EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
     EXPECT_CALL(*binary_feature_extractor_.get(),
                 ExtractImageFeatures(
@@ -4563,8 +4704,11 @@
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
@@ -4595,8 +4739,11 @@
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
@@ -4626,8 +4773,11 @@
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
@@ -4658,8 +4808,11 @@
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
@@ -4693,8 +4846,11 @@
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
       .Times(1);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
@@ -4803,8 +4959,11 @@
                            FILE_PATH_LITERAL("a.exe"));           // final_path
   content::DownloadItemUtils::AttachInfoForTesting(&item, profile(), nullptr);
   EXPECT_CALL(*sb_service_->mock_database_manager(),
-              MatchDownloadAllowlistUrl(_))
-      .WillRepeatedly(Return(false));
+              MatchDownloadAllowlistUrl(_, _))
+      .WillRepeatedly(
+          [](const GURL& url, base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          });
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
diff --git a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
index d5d389a..b91306c1 100644
--- a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
@@ -10,6 +10,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/escape.h"
+#include "base/task/bind_post_task.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
@@ -163,12 +164,17 @@
           : content::BrowserThread::IO);
   DVLOG(2) << " checking allowlists for requestor URL:" << requestor_url;
 
-  bool url_was_allowlisted =
-      requestor_url.is_valid() && database_manager &&
-      database_manager->MatchDownloadAllowlistUrl(requestor_url);
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&PPAPIDownloadRequest::AllowlistCheckComplete,
-                                download_request, url_was_allowlisted));
+  auto callback = base::BindPostTask(
+      content::GetUIThreadTaskRunner({}),
+      base::BindOnce(&PPAPIDownloadRequest::AllowlistCheckComplete,
+                     download_request));
+  if (!requestor_url.is_valid() || !database_manager) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  database_manager->MatchDownloadAllowlistUrl(requestor_url,
+                                              std::move(callback));
 }
 
 void PPAPIDownloadRequest::AllowlistCheckComplete(bool was_on_allowlist) {
diff --git a/chrome/browser/search/background/ntp_background_service_unittest.cc b/chrome/browser/search/background/ntp_background_service_unittest.cc
index 1258e01..7072496 100644
--- a/chrome/browser/search/background/ntp_background_service_unittest.cc
+++ b/chrome/browser/search/background/ntp_background_service_unittest.cc
@@ -87,10 +87,12 @@
   ntp::background::GetCollectionsRequest collection_request;
   EXPECT_TRUE(collection_request.ParseFromString(request_body));
   EXPECT_EQ("foo", collection_request.language());
-  EXPECT_EQ(2, collection_request.filtering_label_size());
+  EXPECT_EQ(3, collection_request.filtering_label_size());
   EXPECT_EQ("chrome_desktop_ntp", collection_request.filtering_label(0));
   EXPECT_EQ("chrome_desktop_ntp.M" + version_info::GetMajorVersionNumber(),
             collection_request.filtering_label(1));
+  EXPECT_EQ("chrome_desktop_ntp.panorama",
+            collection_request.filtering_label(2));
 }
 
 TEST_F(NtpBackgroundServiceTest, CollectionInfoNetworkError) {
diff --git a/chrome/browser/search_engines/ui_thread_search_terms_data.cc b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
index 1344991..70b2571 100644
--- a/chrome/browser/search_engines/ui_thread_search_terms_data.cc
+++ b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
@@ -6,6 +6,7 @@
 
 #include "base/check.h"
 #include "base/metrics/field_trial.h"
+#include "base/strings/strcat.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/google/google_brand.h"
@@ -122,17 +123,14 @@
 // in UIThreadSearchTermsData since SearchTermsData cannot depend on src/chrome
 // as it is shared with iOS.
 std::string UIThreadSearchTermsData::GoogleImageSearchSource() const {
-  std::string version(version_info::GetProductName() + " " +
-                      version_info::GetVersionNumber());
-  if (version_info::IsOfficialBuild())
-    version += " (Official)";
-  version += " " + version_info::GetOSType();
   // Do not distinguish extended from regular stable in image search queries.
-  std::string modifier(
-      chrome::GetChannelName(chrome::WithExtendedStable(false)));
-  if (!modifier.empty())
-    version += " " + modifier;
-  return version;
+  const std::string channel_name =
+      chrome::GetChannelName(chrome::WithExtendedStable(false));
+  return base::StrCat({version_info::GetProductName(), " ",
+                       version_info::GetVersionNumber(),
+                       version_info::IsOfficialBuild() ? " (Official) " : " ",
+                       version_info::GetOSType(),
+                       channel_name.empty() ? "" : " ", channel_name});
 }
 
 size_t UIThreadSearchTermsData::EstimateMemoryUsage() const {
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index f7d5336..6695564 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -99,50 +99,9 @@
 
 }  // namespace
 
-ScopedDiceWebSigninInterceptionBubbleHandle::
-    ~ScopedDiceWebSigninInterceptionBubbleHandle() = default;
-
-bool SigninInterceptionHeuristicOutcomeIsSuccess(
-    SigninInterceptionHeuristicOutcome outcome) {
-  return outcome == SigninInterceptionHeuristicOutcome::kInterceptEnterprise ||
-         outcome == SigninInterceptionHeuristicOutcome::kInterceptMultiUser ||
-         outcome ==
-             SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch ||
-         outcome ==
-             SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced ||
-         outcome == SigninInterceptionHeuristicOutcome::
-                        kInterceptEnterpriseForcedProfileSwitch;
-}
-
-DiceWebSigninInterceptor::Delegate::BubbleParameters::BubbleParameters(
-    SigninInterceptionType interception_type,
-    AccountInfo intercepted_account,
-    AccountInfo primary_account,
-    SkColor profile_highlight_color,
-    bool show_guest_option,
-    bool show_link_data_option,
-    bool show_managed_disclaimer)
-    : interception_type(interception_type),
-      intercepted_account(intercepted_account),
-      primary_account(primary_account),
-      profile_highlight_color(profile_highlight_color),
-      show_guest_option(show_guest_option),
-      show_link_data_option(show_link_data_option),
-      show_managed_disclaimer(show_managed_disclaimer) {}
-
-DiceWebSigninInterceptor::Delegate::BubbleParameters::BubbleParameters(
-    const BubbleParameters& copy) = default;
-
-DiceWebSigninInterceptor::Delegate::BubbleParameters&
-DiceWebSigninInterceptor::Delegate::BubbleParameters::operator=(
-    const BubbleParameters&) = default;
-
-DiceWebSigninInterceptor::Delegate::BubbleParameters::~BubbleParameters() =
-    default;
-
 DiceWebSigninInterceptor::DiceWebSigninInterceptor(
     Profile* profile,
-    std::unique_ptr<Delegate> delegate)
+    std::unique_ptr<WebSigninInterceptor::Delegate> delegate)
     : profile_(profile),
       identity_manager_(IdentityManagerFactory::GetForProfile(profile)),
       delegate_(std::move(delegate)) {
@@ -328,9 +287,9 @@
 void DiceWebSigninInterceptor::CreateBrowserAfterSigninInterception(
     CoreAccountId account_id,
     content::WebContents* intercepted_contents,
-    std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> bubble_handle,
+    std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> bubble_handle,
     bool is_new_profile,
-    SigninInterceptionType interception_type) {
+    WebSigninInterceptor::SigninInterceptionType interception_type) {
   DCHECK(!session_startup_helper_);
   DCHECK(bubble_handle);
   interception_bubble_handle_ = std::move(bubble_handle);
@@ -479,7 +438,7 @@
 }
 
 void DiceWebSigninInterceptor::ShowSigninInterceptionBubble(
-    const Delegate::BubbleParameters& bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(SigninInterceptionResult)> callback) {
   interception_bubble_handle_ = delegate_->ShowSigninInterceptionBubble(
       web_contents_.get(), bubble_parameters, std::move(callback));
@@ -492,7 +451,8 @@
   DCHECK_EQ(info.account_id, account_id_);
   DCHECK(info.IsValid());
 
-  absl::optional<SigninInterceptionType> interception_type;
+  absl::optional<WebSigninInterceptor::SigninInterceptionType>
+      interception_type;
 
   ProfileAttributesEntry* entry =
       g_browser_process->profile_manager()
@@ -522,7 +482,8 @@
 
   if (force_profile_separation) {
     if (switch_to_entry) {
-      interception_type = SigninInterceptionType::kProfileSwitchForced;
+      interception_type =
+          WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced;
       RecordSigninInterceptionHeuristicOutcome(
           SigninInterceptionHeuristicOutcome::
               kInterceptEnterpriseForcedProfileSwitch);
@@ -538,7 +499,8 @@
       Reset();
       return;
     } else {
-      interception_type = SigninInterceptionType::kEnterpriseForced;
+      interception_type =
+          WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced;
       auto primary_account_id =
           identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
       show_link_data_option =
@@ -552,7 +514,8 @@
           SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
     }
   } else if (ShouldShowEnterpriseDialog(info)) {
-    interception_type = SigninInterceptionType::kEnterpriseAcceptManagement;
+    interception_type = WebSigninInterceptor::SigninInterceptionType::
+        kEnterpriseAcceptManagement;
     show_link_data_option = true;
     RecordSigninInterceptionHeuristicOutcome(
         SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
@@ -566,15 +529,18 @@
     // Propose account switching if we skipped in GetHeuristicOutcome because we
     // returned a nullptr to get more information about forced enterprise
     // profile separation.
-    interception_type = SigninInterceptionType::kProfileSwitch;
+    interception_type =
+        WebSigninInterceptor::SigninInterceptionType::kProfileSwitch;
     RecordSigninInterceptionHeuristicOutcome(
         SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
   } else if (ShouldShowEnterpriseBubble(info)) {
-    interception_type = SigninInterceptionType::kEnterprise;
+    interception_type =
+        WebSigninInterceptor::SigninInterceptionType::kEnterprise;
     RecordSigninInterceptionHeuristicOutcome(
         SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
   } else if (ShouldShowMultiUserBubble(info)) {
-    interception_type = SigninInterceptionType::kMultiUser;
+    interception_type =
+        WebSigninInterceptor::SigninInterceptionType::kMultiUser;
     RecordSigninInterceptionHeuristicOutcome(
         SigninInterceptionHeuristicOutcome::kInterceptMultiUser);
   }
@@ -588,13 +554,14 @@
   }
 
   bool show_managed_disclaimer =
-      *interception_type != SigninInterceptionType::kProfileSwitch &&
+      *interception_type !=
+          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch &&
       (base::FeatureList::IsEnabled(kSigninInterceptBubbleV2) ||
        base::FeatureList::IsEnabled(kSyncPromoAfterSigninIntercept)) &&
       (info.IsManaged() ||
        policy::ManagementServiceFactory::GetForPlatform()->IsManaged());
 
-  Delegate::BubbleParameters bubble_parameters(
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
       *interception_type, info, GetPrimaryAccountInfo(identity_manager_),
       GetAutogeneratedThemeColors(profile_color).frame_color,
       /*show_guest_option=*/false, show_link_data_option,
@@ -602,20 +569,21 @@
 
   base::OnceCallback<void(SigninInterceptionResult)> callback;
   switch (*interception_type) {
-    case SigninInterceptionType::kProfileSwitch:
-    case SigninInterceptionType::kProfileSwitchForced:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
       callback = base::BindOnce(
           &DiceWebSigninInterceptor::OnProfileSwitchChoice,
           base::Unretained(this), info.email, switch_to_entry->GetPath());
       break;
-    case SigninInterceptionType::kEnterpriseForced:
-    case SigninInterceptionType::kEnterpriseAcceptManagement:
+    case WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
+    case WebSigninInterceptor::SigninInterceptionType::
+        kEnterpriseAcceptManagement:
       callback = base::BindOnce(
           &DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult,
           base::Unretained(this), info, profile_color);
       break;
-    case SigninInterceptionType::kEnterprise:
-    case SigninInterceptionType::kMultiUser:
+    case WebSigninInterceptor::SigninInterceptionType::kEnterprise:
+    case WebSigninInterceptor::SigninInterceptionType::kMultiUser:
       callback =
           base::BindOnce(&DiceWebSigninInterceptor::OnProfileCreationChoice,
                          base::Unretained(this), info, profile_color);
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index ef5266a3..60999f3 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -14,6 +14,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
+#include "chrome/browser/signin/web_signin_interceptor.h"
 #include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -38,100 +39,12 @@
 }
 
 struct AccountInfo;
-class Browser;
 class DiceSignedInProfileCreator;
 class DiceInterceptedSessionStartupHelper;
 class Profile;
 class ProfileAttributesEntry;
 class ProfileAttributesStorage;
 
-// Outcome of the interception heuristic (decision whether the interception
-// bubble is shown or not).
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class SigninInterceptionHeuristicOutcome {
-  // Interception succeeded:
-  kInterceptProfileSwitch = 0,
-  kInterceptMultiUser = 1,
-  kInterceptEnterprise = 2,
-
-  // Interception aborted:
-  // This is a "Sync" sign in and not a "web" sign in.
-  kAbortSyncSignin = 3,
-  // Another interception is already in progress.
-  kAbortInterceptInProgress = 4,
-  // This is not a new account (reauth).
-  kAbortAccountNotNew = 5,
-  // New profile is not offered when there is only one account.
-  kAbortSingleAccount = 6,
-  // Extended account info could not be downloaded.
-  kAbortAccountInfoTimeout = 7,
-  // Account info not compatible with interception (e.g. same Gaia name).
-  kAbortAccountInfoNotCompatible = 8,
-  // Profile creation disallowed.
-  kAbortProfileCreationDisallowed = 9,
-  // The interceptor was shut down before the heuristic completed.
-  kAbortShutdown = 10,
-  // The interceptor is not offered when  the `WebContents` has no browser
-  // associated, or its browser does not support displaying the interception UI.
-  kAbortNoSupportedBrowser = 11,
-  // A password update is required for the account, and this takes priority over
-  // signin interception.
-  kAbortPasswordUpdate = 12,
-  // A password update will be required for the account: the password used on
-  // the form does not match the stored password.
-  kAbortPasswordUpdatePending = 13,
-  // The user already declined a new profile for this account, the UI is not
-  // shown again.
-  kAbortUserDeclinedProfileForAccount = 14,
-  // Signin interception is disabled by the SigninInterceptionEnabled policy.
-  kAbortInterceptionDisabled = 15,
-
-  // Interception succeeded when enteprise account separation is mandatory.
-  kInterceptEnterpriseForced = 16,
-  kInterceptEnterpriseForcedProfileSwitch = 17,
-
-  // The interceptor is not triggered if the tab has already been closed.
-  kAbortTabClosed = 18,
-
-  kMaxValue = kAbortTabClosed,
-};
-
-// User selection in the interception bubble.
-enum class SigninInterceptionUserChoice { kAccept, kDecline, kGuest };
-
-// User action resulting from the interception bubble.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class SigninInterceptionResult {
-  kAccepted = 0,
-  kDeclined = 1,
-  kIgnored = 2,
-
-  // Used when the bubble was not shown because it's not implemented.
-  kNotDisplayed = 3,
-
-  // Accepted to be opened in Guest profile.
-  kAcceptedWithGuest = 4,
-
-  kAcceptedWithExistingProfile = 5,
-
-  kMaxValue = kAcceptedWithExistingProfile,
-};
-
-// The ScopedDiceWebSigninInterceptionBubbleHandle closes the signin intercept
-// bubble when it is destroyed, if the bubble is still opened. Note that this
-// handle does not prevent the bubble from being closed for other reasons.
-class ScopedDiceWebSigninInterceptionBubbleHandle {
- public:
-  virtual ~ScopedDiceWebSigninInterceptionBubbleHandle() = 0;
-};
-
-// Returns whether the heuristic outcome is a success (the signin should be
-// intercepted).
-bool SigninInterceptionHeuristicOutcomeIsSuccess(
-    SigninInterceptionHeuristicOutcome outcome);
-
 // Called after web signed in, after a successful token exchange through Dice.
 // The DiceWebSigninInterceptor may offer the user to create a new profile or
 // switch to another existing profile.
@@ -153,74 +66,9 @@
 class DiceWebSigninInterceptor : public KeyedService,
                                  public signin::IdentityManager::Observer {
  public:
-  enum class SigninInterceptionType {
-    kProfileSwitch,
-    kEnterprise,
-    kMultiUser,
-    kEnterpriseForced,
-    kEnterpriseAcceptManagement,
-    kProfileSwitchForced
-  };
-
-  // Delegate class responsible for showing the various interception UIs.
-  class Delegate {
-   public:
-    // Parameters for interception bubble UIs.
-    struct BubbleParameters {
-      BubbleParameters(SigninInterceptionType interception_type,
-                       AccountInfo intercepted_account,
-                       AccountInfo primary_account,
-                       SkColor profile_highlight_color = SkColor(),
-                       bool show_guest_option = false,
-                       bool show_link_data_option = false,
-                       bool show_managed_disclaimer = false);
-
-      BubbleParameters(const BubbleParameters& copy);
-      BubbleParameters& operator=(const BubbleParameters&);
-      ~BubbleParameters();
-
-      SigninInterceptionType interception_type;
-      AccountInfo intercepted_account;
-      AccountInfo primary_account;
-      SkColor profile_highlight_color;
-      bool show_guest_option;
-      bool show_link_data_option;
-      bool show_managed_disclaimer;
-    };
-
-    virtual ~Delegate() = default;
-
-    // Returns whether the `web_contents` supports signin interception.
-    virtual bool IsSigninInterceptionSupported(
-        const content::WebContents& web_contents) = 0;
-
-    // Shows the signin interception bubble and calls |callback| to indicate
-    // whether the user should continue in a new profile.
-    // The callback is never called if the delegate is deleted before it
-    // completes.
-    // May return a nullptr handle if the bubble cannot be shown.
-    // Warning: the handle closes the bubble when it is destroyed ; it is the
-    // responsibility of the caller to keep the handle alive until the bubble
-    // should be closed.
-    // The callback must not be called synchronously if this function returns a
-    // valid handle (because the caller needs to be able to close the bubble
-    // from the callback).
-    virtual std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
-    ShowSigninInterceptionBubble(
-        content::WebContents* web_contents,
-        const BubbleParameters& bubble_parameters,
-        base::OnceCallback<void(SigninInterceptionResult)> callback) = 0;
-
-    // Shows the first run experience for `account_id` in `browser` opened for
-    // a newly created profile.
-    virtual void ShowFirstRunExperienceInNewProfile(
-        Browser* browser,
-        const CoreAccountId& account_id,
-        SigninInterceptionType interception_type) = 0;
-  };
-
-  DiceWebSigninInterceptor(Profile* profile,
-                           std::unique_ptr<Delegate> delegate);
+  DiceWebSigninInterceptor(
+      Profile* profile,
+      std::unique_ptr<WebSigninInterceptor::Delegate> delegate);
   ~DiceWebSigninInterceptor() override;
 
   DiceWebSigninInterceptor(const DiceWebSigninInterceptor&) = delete;
@@ -252,10 +100,9 @@
   void CreateBrowserAfterSigninInterception(
       CoreAccountId account_id,
       content::WebContents* intercepted_contents,
-      std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
-          bubble_handle,
+      std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> bubble_handle,
       bool is_new_profile,
-      SigninInterceptionType interception_type);
+      WebSigninInterceptor::SigninInterceptionType interception_type);
 
   // Returns the outcome of the interception heuristic.
   // If the outcome is kInterceptProfileSwitch, the target profile is returned
@@ -330,7 +177,7 @@
 
   // Helper function to call `delegate_->ShowSigninInterceptionBubble()`.
   void ShowSigninInterceptionBubble(
-      const Delegate::BubbleParameters& bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(SigninInterceptionResult)> callback);
 
   void OnInterceptionReadyToBeProcessed(const AccountInfo& info);
@@ -412,7 +259,7 @@
 
   const raw_ptr<Profile> profile_;
   const raw_ptr<signin::IdentityManager> identity_manager_;
-  std::unique_ptr<Delegate> delegate_;
+  std::unique_ptr<WebSigninInterceptor::Delegate> delegate_;
 
   // Used in the profile that was created after the interception succeeded.
   std::unique_ptr<DiceInterceptedSessionStartupHelper> session_startup_helper_;
@@ -423,7 +270,8 @@
   CoreAccountId account_id_;
   bool new_account_interception_ = false;
   bool intercepted_account_management_accepted_ = false;
-  absl::optional<SigninInterceptionType> interception_type_;
+  absl::optional<WebSigninInterceptor::SigninInterceptionType>
+      interception_type_;
   base::ScopedObservation<signin::IdentityManager,
                           signin::IdentityManager::Observer>
       account_info_update_observation_{this};
@@ -432,7 +280,7 @@
   base::CancelableOnceCallback<void()> on_account_info_update_timeout_;
   std::unique_ptr<DiceSignedInProfileCreator> dice_signed_in_profile_creator_;
   // Used to retain the interception UI bubble until profile creation completes.
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
       interception_bubble_handle_;
   // Used for metrics:
   bool was_interception_ui_displayed_ = false;
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
index 6bb6053a..913f5083 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
@@ -76,7 +76,7 @@
 
 class FakeDiceWebSigninInterceptorDelegate;
 
-class FakeBubbleHandle : public ScopedDiceWebSigninInterceptionBubbleHandle,
+class FakeBubbleHandle : public ScopedWebSigninInterceptionBubbleHandle,
                          public base::SupportsWeakPtr<FakeBubbleHandle> {
  public:
   ~FakeBubbleHandle() override = default;
@@ -87,7 +87,7 @@
 class FakeDiceWebSigninInterceptorDelegate
     : public DiceWebSigninInterceptorDelegate {
  public:
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
   ShowSigninInterceptionBubble(
       content::WebContents* web_contents,
       const BubbleParameters& bubble_parameters,
@@ -106,8 +106,7 @@
   void ShowFirstRunExperienceInNewProfile(
       Browser* browser,
       const CoreAccountId& account_id,
-      DiceWebSigninInterceptor::SigninInterceptionType interception_type)
-      override {
+      WebSigninInterceptor::SigninInterceptionType interception_type) override {
     EXPECT_FALSE(fre_browser_)
         << "First run experience must be shown only once.";
     EXPECT_EQ(interception_type, expected_interception_type_);
@@ -120,7 +119,7 @@
   const CoreAccountId& fre_account_id() { return fre_account_id_; }
 
   void set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType type) {
+      WebSigninInterceptor::SigninInterceptionType type) {
     expected_interception_type_ = type;
   }
 
@@ -137,8 +136,8 @@
  private:
   raw_ptr<Browser, DanglingUntriaged> fre_browser_ = nullptr;
   CoreAccountId fre_account_id_;
-  DiceWebSigninInterceptor::SigninInterceptionType expected_interception_type_ =
-      DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser;
+  WebSigninInterceptor::SigninInterceptionType expected_interception_type_ =
+      WebSigninInterceptor::SigninInterceptionType::kMultiUser;
   SigninInterceptionResult expected_interception_result_ =
       SigninInterceptionResult::kAccepted;
   base::WeakPtr<FakeBubbleHandle> weak_bubble_handle_;
@@ -420,7 +419,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
   Profile* new_profile =
       InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
   ASSERT_TRUE(new_profile);
@@ -508,7 +507,7 @@
   // Start the interception.
   GetInterceptorDelegate(GetProfile())
       ->set_expected_interception_type(
-          DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
+          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
   DiceWebSigninInterceptor* interceptor =
       DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
   interceptor->MaybeInterceptWebSignin(web_contents, account_info.account_id,
@@ -703,7 +702,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise);
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
   Profile* new_profile =
       InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
   EXPECT_FALSE(
@@ -718,7 +717,7 @@
   FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
       GetInterceptorDelegate(new_profile);
   new_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise);
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
 
   IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
   adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
@@ -801,7 +800,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise);
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
   source_interceptor_delegate->set_expected_interception_result(
       SigninInterceptionResult::kDeclined);
 
@@ -867,7 +866,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
   Profile* new_profile =
       InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
   EXPECT_TRUE(
@@ -882,7 +881,7 @@
   FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
       GetInterceptorDelegate(new_profile);
   new_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
 
   IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
   adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
@@ -954,7 +953,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
   source_interceptor_delegate->set_expected_interception_result(
       SigninInterceptionResult::kDeclined);
 
@@ -1022,7 +1021,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
   source_interceptor_delegate->set_expected_interception_result(
       SigninInterceptionResult::kDeclined);
 
@@ -1086,7 +1085,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
   Profile* new_profile =
       InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
   EXPECT_TRUE(
@@ -1101,7 +1100,7 @@
   FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
       GetInterceptorDelegate(new_profile);
   new_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
 
   IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
   adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
@@ -1176,7 +1175,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
 
   EXPECT_FALSE(
       chrome::enterprise_util::UserAcceptedAccountManagement(GetProfile()));
@@ -1243,7 +1242,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
 
   EXPECT_FALSE(
       chrome::enterprise_util::UserAcceptedAccountManagement(GetProfile()));
@@ -1317,7 +1316,7 @@
   FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
       GetInterceptorDelegate(GetProfile());
   source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced);
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced);
   Profile* new_profile =
       InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
   ASSERT_TRUE(new_profile);
@@ -1412,8 +1411,7 @@
   // Start the interception.
   GetInterceptorDelegate(GetProfile())
       ->set_expected_interception_type(
-          DiceWebSigninInterceptor::SigninInterceptionType::
-              kProfileSwitchForced);
+          WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced);
   DiceWebSigninInterceptor* interceptor =
       DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
   interceptor->MaybeInterceptWebSignin(web_contents, account_info.account_id,
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
index b87c5a1..384a34d2 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
@@ -41,25 +41,26 @@
 namespace {
 
 class MockDiceWebSigninInterceptorDelegate
-    : public DiceWebSigninInterceptor::Delegate {
+    : public WebSigninInterceptor::Delegate {
  public:
   bool IsSigninInterceptionSupported(
       const content::WebContents& web_contents) override {
     return true;
   }
 
-  MOCK_METHOD(std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>,
+  MOCK_METHOD(std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>,
               ShowSigninInterceptionBubble,
               (content::WebContents * web_contents,
-               const BubbleParameters& bubble_parameters,
+               const WebSigninInterceptor::Delegate::BubbleParameters&
+                   bubble_parameters,
                base::OnceCallback<void(SigninInterceptionResult)> callback),
               (override));
 
   void ShowFirstRunExperienceInNewProfile(
       Browser* browser,
       const CoreAccountId& account_id,
-      DiceWebSigninInterceptor::SigninInterceptionType interception_type)
-      override {}
+      WebSigninInterceptor::SigninInterceptionType interception_type) override {
+  }
 };
 
 MATCHER_P(HasSameAccountIdAs, other, "") {
@@ -68,28 +69,28 @@
 
 // Matches BubbleParameters fields excepting the color. This is useful in the
 // test because the color is randomly generated.
-testing::Matcher<const DiceWebSigninInterceptor::Delegate::BubbleParameters&>
+testing::Matcher<const WebSigninInterceptor::Delegate::BubbleParameters&>
 MatchBubbleParameters(
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters& parameters) {
+    const WebSigninInterceptor::Delegate::BubbleParameters& parameters) {
   return testing::AllOf(
-      testing::Field("interception_type",
-                     &DiceWebSigninInterceptor::Delegate::BubbleParameters::
-                         interception_type,
-                     parameters.interception_type),
+      testing::Field(
+          "interception_type",
+          &WebSigninInterceptor::Delegate::BubbleParameters::interception_type,
+          parameters.interception_type),
       testing::Field("intercepted_account",
-                     &DiceWebSigninInterceptor::Delegate::BubbleParameters::
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
                          intercepted_account,
                      HasSameAccountIdAs(parameters.intercepted_account)),
-      testing::Field("primary_account",
-                     &DiceWebSigninInterceptor::Delegate::BubbleParameters::
-                         primary_account,
-                     HasSameAccountIdAs(parameters.primary_account)),
+      testing::Field(
+          "primary_account",
+          &WebSigninInterceptor::Delegate::BubbleParameters::primary_account,
+          HasSameAccountIdAs(parameters.primary_account)),
       testing::Field("show_link_data_option",
-                     &DiceWebSigninInterceptor::Delegate::BubbleParameters::
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
                          show_link_data_option,
                      parameters.show_link_data_option),
       testing::Field("show_managed_disclaimer",
-                     &DiceWebSigninInterceptor::Delegate::BubbleParameters::
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
                          show_managed_disclaimer,
                      parameters.show_managed_disclaimer));
 }
@@ -478,9 +479,8 @@
   identity_test_env()->UpdateAccountInfoForAccount(account_info);
   interceptor()->SetAccountLevelSigninRestrictionFetchResultForTesting("");
 
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::
-          kEnterpriseAcceptManagement,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseAcceptManagement,
       account_info, account_info, SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/true,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -531,8 +531,8 @@
                                    "primary_account");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, account_info, SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -557,8 +557,8 @@
                                    "primary_account_strict");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, AccountInfo(), SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -582,8 +582,8 @@
       "primary_account_keep_existing_data");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, AccountInfo(), SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/true,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -615,8 +615,8 @@
                                    "primary_account_keep_existing_data");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -640,8 +640,8 @@
                                    "primary_account_strict_keep_existing_data");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, AccountInfo(), SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/true,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -671,8 +671,8 @@
                                    "primary_account_strict_keep_existing_data");
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced,
       account_info, primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -706,8 +706,8 @@
   entry->SetAuthInfo(account_info.gaia, base::UTF8ToUTF16(account_info.email),
                      /*is_consented_primary_account=*/false);
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced,
       account_info, AccountInfo(), SkColor(), /*show_guest_option=*/false,
       /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
@@ -838,8 +838,8 @@
       SigninInterceptionHeuristicOutcome::kAbortAccountNotNew);
 
   // Check that interception works otherwise, as a sanity check.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
       account_info, AccountInfo());
   EXPECT_CALL(*mock_delegate(),
               ShowSigninInterceptionBubble(
@@ -947,8 +947,8 @@
                      /*is_consented_primary_account=*/false);
 
   // Start an interception.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
       account_info, AccountInfo());
   base::OnceCallback<void(SigninInterceptionResult)> delegate_callback;
   EXPECT_CALL(*mock_delegate(),
@@ -998,9 +998,9 @@
 
   const int kMaxProfileCreationDeclinedCount = 2;
   // Decline the interception kMaxProfileCreationDeclinedCount times.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
-      account_info, primary_account_info, SkColor(),
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, account_info,
+      primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
   for (int i = 0; i < kMaxProfileCreationDeclinedCount; ++i) {
@@ -1060,9 +1060,9 @@
 
   const int kMaxProfileCreationDeclinedCount = 2;
   // Decline the interception kMaxProfileCreationDeclinedCount times.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
-      account_info, primary_account_info, SkColor(),
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, account_info,
+      primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
   for (int i = 0; i < kMaxProfileCreationDeclinedCount; ++i) {
@@ -1122,8 +1122,8 @@
                      /*is_consented_primary_account=*/false);
 
   // Test that the profile switch can be declined multiple times.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
       account_info, AccountInfo());
   for (int i = 0; i < 10; ++i) {
     EXPECT_CALL(*mock_delegate(),
@@ -1204,8 +1204,8 @@
       SigninInterceptionHeuristicOutcome::kAbortProfileCreationDisallowed);
 
   // Profile switch interception still works.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
       account_info, AccountInfo());
   EXPECT_CALL(*mock_delegate(),
               ShowSigninInterceptionBubble(
@@ -1233,9 +1233,9 @@
   testing::Mock::VerifyAndClearExpectations(mock_delegate());
 
   // Account info becomes available, interception happens.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
-      account_info, primary_account_info, SkColor(),
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, account_info,
+      primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
   EXPECT_CALL(*mock_delegate(),
@@ -1260,9 +1260,9 @@
   identity_test_env()->UpdateAccountInfoForAccount(account_info);
 
   // Account info is already available, interception happens immediately.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
-      account_info, primary_account_info, SkColor(),
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, account_info,
+      primary_account_info, SkColor(),
       /*show_guest_option=*/false, /*show_link_data_option=*/false,
       /*show_managed_disclaimer=*/sync_promo_enabled_);
   EXPECT_CALL(*mock_delegate(),
@@ -1288,9 +1288,9 @@
   identity_test_env()->UpdateAccountInfoForAccount(account_info);
 
   // Account info is already available, interception happens immediately.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
-      account_info, primary_account_info);
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kMultiUser, account_info,
+      primary_account_info);
   EXPECT_CALL(*mock_delegate(),
               ShowSigninInterceptionBubble(
                   web_contents(), MatchBubbleParameters(expected_parameters),
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
index ebec810..f4c2c606 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
+++ b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
@@ -40,7 +40,7 @@
 
 // Dummy delegate that declines all interceptions.
 class TestDiceWebSigninInterceptorDelegate
-    : public DiceWebSigninInterceptor::Delegate {
+    : public WebSigninInterceptor::Delegate {
  public:
   ~TestDiceWebSigninInterceptorDelegate() override = default;
 
@@ -49,7 +49,7 @@
     return false;
   }
 
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
   ShowSigninInterceptionBubble(
       content::WebContents* web_contents,
       const BubbleParameters& bubble_parameters,
@@ -61,8 +61,8 @@
   void ShowFirstRunExperienceInNewProfile(
       Browser* browser,
       const CoreAccountId& account_id,
-      DiceWebSigninInterceptor::SigninInterceptionType interception_type)
-      override {}
+      WebSigninInterceptor::SigninInterceptionType interception_type) override {
+  }
 };
 
 class MockDiceWebSigninInterceptor : public DiceWebSigninInterceptor {
diff --git a/chrome/browser/signin/profile_token_web_signin_interceptor.cc b/chrome/browser/signin/profile_token_web_signin_interceptor.cc
index 3f24e01..0ee9f659 100644
--- a/chrome/browser/signin/profile_token_web_signin_interceptor.cc
+++ b/chrome/browser/signin/profile_token_web_signin_interceptor.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/profile_token_web_signin_interceptor_factory.h"
 #include "chrome/browser/signin/token_managed_profile_creator.h"
 #include "chrome/browser/themes/theme_service.h"
@@ -23,34 +24,14 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/signin/profile_colors_util.h"
+#include "chrome/common/themes/autogenerated_theme_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/browser/web_contents.h"
-
-namespace {
-class FakeDelegate : public ProfileTokenWebSigninInterceptor::Delegate {
- public:
-  FakeDelegate() = default;
-
-  ~FakeDelegate() override = default;
-
-  void ShowCreateNewProfileBubble(
-      const ProfileAttributesEntry* switch_to_profile,
-      base::OnceCallback<void(bool)> callback) override {
-    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), true));
-  }
-};
-
-}  // namespace
-
-ProfileTokenWebSigninInterceptor::ProfileTokenWebSigninInterceptor(
-    Profile* profile)
-    : ProfileTokenWebSigninInterceptor(profile,
-                                       std::make_unique<FakeDelegate>()) {}
+#include "third_party/skia/include/core/SkColor.h"
 
 ProfileTokenWebSigninInterceptor::ProfileTokenWebSigninInterceptor(
     Profile* profile,
-    std::unique_ptr<Delegate> delegate)
+    std::unique_ptr<WebSigninInterceptor::Delegate> delegate)
     : profile_(profile), delegate_(std::move(delegate)) {
   DCHECK(profile_);
   DCHECK(delegate_);
@@ -98,8 +79,27 @@
     return;
   }
 
-  delegate_->ShowCreateNewProfileBubble(
-      switch_to_entry_,
+  ProfileAttributesEntry* entry =
+      g_browser_process->profile_manager()
+          ->GetProfileAttributesStorage()
+          .GetProfileAttributesWithPath(profile_->GetPath());
+  profile_color_ = GenerateNewProfileColor(entry).color;
+
+  auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
+  WebSigninInterceptor::SigninInterceptionType interception_type =
+      switch_to_entry_
+          ? WebSigninInterceptor::SigninInterceptionType::kProfileSwitch
+          : WebSigninInterceptor::SigninInterceptionType::kEnterprise;
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
+      interception_type, AccountInfo(),
+      identity_manager->FindExtendedAccountInfoByAccountId(
+          identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin)),
+      GetAutogeneratedThemeColors(profile_color_).frame_color,
+      /*show_guest_option=*/false, /*show_link_data_option=*/false,
+      /*show_managed_disclaimer=*/true);
+
+  interception_bubble_handle_ = delegate_->ShowSigninInterceptionBubble(
+      web_contents_.get(), bubble_parameters,
       base::BindOnce(&ProfileTokenWebSigninInterceptor::OnProfileCreationChoice,
                      base::Unretained(this)));
 }
@@ -114,6 +114,8 @@
   intercepted_id_.clear();
   enrollment_token_.clear();
   profile_creator_.reset();
+  profile_color_ = SkColor();
+  interception_bubble_handle_.reset();
 }
 
 bool ProfileTokenWebSigninInterceptor::IsValidEnrollmentToken(
@@ -121,8 +123,9 @@
   return !enrollment_token.empty();
 }
 
-void ProfileTokenWebSigninInterceptor::OnProfileCreationChoice(bool accepted) {
-  if (!accepted) {
+void ProfileTokenWebSigninInterceptor::OnProfileCreationChoice(
+    SigninInterceptionResult create) {
+  if (create != SigninInterceptionResult::kAccepted) {
     if (switch_to_entry_) {
       DVLOG(1) << "Profile switch refused by the user";
     } else {
@@ -163,14 +166,9 @@
 
   // Generate a color theme for new profiles
   if (!switch_to_entry_) {
-    DVLOG(1) << "New profile created";
-    ProfileAttributesEntry* entry =
-        g_browser_process->profile_manager()
-            ->GetProfileAttributesStorage()
-            .GetProfileAttributesWithPath(new_profile->GetPath());
-    SkColor profile_color = GenerateNewProfileColor(entry).color;
+    DCHECK_NE(SkColor(), profile_color_);
     ThemeServiceFactory::GetForProfile(new_profile)
-        ->BuildAutogeneratedThemeFromColor(profile_color);
+        ->BuildAutogeneratedThemeFromColor(profile_color_);
   } else {
     DVLOG(1) << "Profile switched sucessfully";
   }
diff --git a/chrome/browser/signin/profile_token_web_signin_interceptor.h b/chrome/browser/signin/profile_token_web_signin_interceptor.h
index 69c378d9..c44b32c 100644
--- a/chrome/browser/signin/profile_token_web_signin_interceptor.h
+++ b/chrome/browser/signin/profile_token_web_signin_interceptor.h
@@ -12,6 +12,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/signin/token_managed_profile_creator.h"
+#include "chrome/browser/signin/web_signin_interceptor.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace content {
@@ -34,24 +35,17 @@
 // * When the account is available on the web in the new profile:
 //   - A new browser window is created for the new profile,
 //   - The tab is moved to the new profile.
-class ProfileTokenWebSigninInterceptor : public KeyedService {
+class ProfileTokenWebSigninInterceptor : public WebSigninInterceptor,
+                                         public KeyedService {
  public:
   enum class SigninInterceptionType {
     kProfileSwitch,
     kEnterprise,
   };
 
-  class Delegate {
-   public:
-    virtual ~Delegate() = default;
-    virtual void ShowCreateNewProfileBubble(
-        const ProfileAttributesEntry* switch_to_profile,
-        base::OnceCallback<void(bool)> callback) = 0;
-  };
-
-  explicit ProfileTokenWebSigninInterceptor(Profile* profile);
-  ProfileTokenWebSigninInterceptor(Profile* profile,
-                                   std::unique_ptr<Delegate> delegate);
+  ProfileTokenWebSigninInterceptor(
+      Profile* profile,
+      std::unique_ptr<WebSigninInterceptor::Delegate> delegate);
   ~ProfileTokenWebSigninInterceptor() override;
 
   ProfileTokenWebSigninInterceptor(const ProfileTokenWebSigninInterceptor&) =
@@ -78,7 +72,7 @@
 
   bool IsValidEnrollmentToken(const std::string& enrollment_token) const;
 
-  void OnProfileCreationChoice(bool accepted);
+  void OnProfileCreationChoice(SigninInterceptionResult create);
 
   // Called when the new browser is created after interception. Passed as
   // callback to `session_startup_helper_`.
@@ -96,6 +90,10 @@
   std::string intercepted_id_;
   bool disable_browser_creation_after_interception_for_testing_ = false;
   raw_ptr<const ProfileAttributesEntry> switch_to_entry_ = nullptr;
+  SkColor profile_color_;
+  // Used to retain the interception UI bubble until profile creation completes.
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
+      interception_bubble_handle_;
 };
 
 #endif  // CHROME_BROWSER_SIGNIN_PROFILE_TOKEN_WEB_SIGNIN_INTERCEPTOR_H_
diff --git a/chrome/browser/signin/profile_token_web_signin_interceptor_factory.cc b/chrome/browser/signin/profile_token_web_signin_interceptor_factory.cc
index c0299d8..d72d659 100644
--- a/chrome/browser/signin/profile_token_web_signin_interceptor_factory.cc
+++ b/chrome/browser/signin/profile_token_web_signin_interceptor_factory.cc
@@ -6,6 +6,7 @@
 
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/profile_token_web_signin_interceptor.h"
+#include "chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h"
 
 // static
 ProfileTokenWebSigninInterceptor*
@@ -31,5 +32,6 @@
 KeyedService* ProfileTokenWebSigninInterceptorFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
   return new ProfileTokenWebSigninInterceptor(
-      Profile::FromBrowserContext(context));
+      Profile::FromBrowserContext(context),
+      std::make_unique<DiceWebSigninInterceptorDelegate>());
 }
diff --git a/chrome/browser/signin/profile_token_web_signin_interceptor_unittest.cc b/chrome/browser/signin/profile_token_web_signin_interceptor_unittest.cc
index c9ad70e..700b0ee 100644
--- a/chrome/browser/signin/profile_token_web_signin_interceptor_unittest.cc
+++ b/chrome/browser/signin/profile_token_web_signin_interceptor_unittest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_test_util.h"
+#include "chrome/browser/signin/web_signin_interceptor.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -31,12 +32,56 @@
   MockDelegate() = default;
   ~MockDelegate() override = default;
 
+  MOCK_METHOD(bool,
+              IsSigninInterceptionSupported,
+              (const content::WebContents&),
+              (override));
+  MOCK_METHOD(std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>,
+              ShowSigninInterceptionBubble,
+              (content::WebContents*,
+               const WebSigninInterceptor::Delegate::BubbleParameters&,
+               base::OnceCallback<void(SigninInterceptionResult)>),
+              (override));
   MOCK_METHOD(void,
-              ShowCreateNewProfileBubble,
-              (const ProfileAttributesEntry*, base::OnceCallback<void(bool)>),
+              ShowFirstRunExperienceInNewProfile,
+              (Browser*,
+               const CoreAccountId&,
+               WebSigninInterceptor::SigninInterceptionType),
               (override));
 };
 
+MATCHER_P(HasSameAccountIdAs, other, "") {
+  return arg.account_id == other.account_id;
+}
+
+// Matches BubbleParameters fields excepting the color. This is useful in the
+// test because the color is randomly generated.
+testing::Matcher<const WebSigninInterceptor::Delegate::BubbleParameters&>
+MatchBubbleParameters(
+    const WebSigninInterceptor::Delegate::BubbleParameters& parameters) {
+  return testing::AllOf(
+      testing::Field(
+          "interception_type",
+          &WebSigninInterceptor::Delegate::BubbleParameters::interception_type,
+          parameters.interception_type),
+      testing::Field("intercepted_account",
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
+                         intercepted_account,
+                     HasSameAccountIdAs(parameters.intercepted_account)),
+      testing::Field(
+          "primary_account",
+          &WebSigninInterceptor::Delegate::BubbleParameters::primary_account,
+          HasSameAccountIdAs(parameters.primary_account)),
+      testing::Field("show_link_data_option",
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
+                         show_link_data_option,
+                     parameters.show_link_data_option),
+      testing::Field("show_managed_disclaimer",
+                     &WebSigninInterceptor::Delegate::BubbleParameters::
+                         show_managed_disclaimer,
+                     parameters.show_managed_disclaimer));
+}
+
 }  // namespace
 
 class ProfileTokenWebSigninInterceptorTest : public BrowserWithTestWindowTest {
@@ -67,18 +112,18 @@
 };
 
 TEST_F(ProfileTokenWebSigninInterceptorTest, NoInterceptionWithInvalidToken) {
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(_, _)).Times(0);
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(_, _, _)).Times(0);
   interceptor_->MaybeInterceptSigninProfile(web_contents(), "id",
                                             /*enrollment_token=*/std::string());
 }
 
 TEST_F(ProfileTokenWebSigninInterceptorTest, NoInterceptionWithNoWebContents) {
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(_, _)).Times(0);
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(_, _, _)).Times(0);
   interceptor_->MaybeInterceptSigninProfile(nullptr, "id", "token");
 }
 
 TEST_F(ProfileTokenWebSigninInterceptorTest, NoInterceptionWithSameProfile) {
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(_, _)).Times(0);
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(_, _, _)).Times(0);
 
   auto* entry = TestingBrowserProcess::GetGlobal()
                     ->profile_manager()
@@ -95,12 +140,23 @@
   const int num_profiles_before = TestingBrowserProcess::GetGlobal()
                                       ->profile_manager()
                                       ->GetNumberOfProfiles();
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(nullptr, _))
+
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, AccountInfo(),
+      AccountInfo(), SkColor(), /*show_guest_option=*/false,
+      /*show_link_data_option=*/false,
+      /*show_managed_disclaimer=*/true);
+
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(
+                              _, MatchBubbleParameters(expected_parameters), _))
       .Times(1)
-      .WillOnce(Invoke([](const ProfileAttributesEntry* switch_to_profile,
-                          base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(false);
-      }));
+      .WillOnce(Invoke(
+          [](content::WebContents*,
+             const WebSigninInterceptor::Delegate::BubbleParameters&,
+             base::OnceCallback<void(SigninInterceptionResult)> callback) {
+            std::move(callback).Run(SigninInterceptionResult::kDeclined);
+            return nullptr;
+          }));
   interceptor_->MaybeInterceptSigninProfile(web_contents(), "id", "token");
 
   base::RunLoop().RunUntilIdle();
@@ -116,12 +172,21 @@
   const int num_profiles_before = TestingBrowserProcess::GetGlobal()
                                       ->profile_manager()
                                       ->GetNumberOfProfiles();
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(nullptr, _))
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, AccountInfo(),
+      AccountInfo(), SkColor(), /*show_guest_option=*/false,
+      /*show_link_data_option=*/false,
+      /*show_managed_disclaimer=*/true);
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(
+                              _, MatchBubbleParameters(expected_parameters), _))
       .Times(1)
-      .WillOnce(Invoke([](const ProfileAttributesEntry* switch_to_profile,
-                          base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(true);
-      }));
+      .WillOnce(Invoke(
+          [](content::WebContents*,
+             const WebSigninInterceptor::Delegate::BubbleParameters&,
+             base::OnceCallback<void(SigninInterceptionResult)> callback) {
+            std::move(callback).Run(SigninInterceptionResult::kAccepted);
+            return nullptr;
+          }));
   interceptor_->MaybeInterceptSigninProfile(web_contents(), "id", "token");
 
   base::RunLoop().RunUntilIdle();
@@ -137,12 +202,21 @@
   const int num_profiles_before = TestingBrowserProcess::GetGlobal()
                                       ->profile_manager()
                                       ->GetNumberOfProfiles();
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(nullptr, _))
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise, AccountInfo(),
+      AccountInfo(), SkColor(), /*show_guest_option=*/false,
+      /*show_link_data_option=*/false,
+      /*show_managed_disclaimer=*/true);
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(
+                              _, MatchBubbleParameters(expected_parameters), _))
       .Times(1)
-      .WillOnce(Invoke([](const ProfileAttributesEntry* switch_to_profile,
-                          base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(true);
-      }));
+      .WillOnce(Invoke(
+          [](content::WebContents*,
+             const WebSigninInterceptor::Delegate::BubbleParameters&,
+             base::OnceCallback<void(SigninInterceptionResult)> callback) {
+            std::move(callback).Run(SigninInterceptionResult::kAccepted);
+            return nullptr;
+          }));
   interceptor_->MaybeInterceptSigninProfile(web_contents(), std::string(),
                                             "token");
 
@@ -165,12 +239,22 @@
   entry->SetProfileManagementId("id");
   entry->SetProfileManagementEnrollmentToken("token");
 
-  EXPECT_CALL(*delegate_, ShowCreateNewProfileBubble(entry, _))
+  WebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+      AccountInfo(), AccountInfo(), SkColor(), /*show_guest_option=*/false,
+      /*show_link_data_option=*/false,
+      /*show_managed_disclaimer=*/true);
+
+  EXPECT_CALL(*delegate_, ShowSigninInterceptionBubble(
+                              _, MatchBubbleParameters(expected_parameters), _))
       .Times(1)
-      .WillOnce(Invoke([](const ProfileAttributesEntry* switch_to_profile,
-                          base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(true);
-      }));
+      .WillOnce(Invoke(
+          [](content::WebContents*,
+             const WebSigninInterceptor::Delegate::BubbleParameters&,
+             base::OnceCallback<void(SigninInterceptionResult)> callback) {
+            std::move(callback).Run(SigninInterceptionResult::kAccepted);
+            return nullptr;
+          }));
 
   const int num_profiles_before = TestingBrowserProcess::GetGlobal()
                                       ->profile_manager()
diff --git a/chrome/browser/signin/web_signin_interceptor.cc b/chrome/browser/signin/web_signin_interceptor.cc
new file mode 100644
index 0000000..84d8f256
--- /dev/null
+++ b/chrome/browser/signin/web_signin_interceptor.cc
@@ -0,0 +1,54 @@
+// Copyright 2023 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/signin/web_signin_interceptor.h"
+
+#include <string>
+
+#include "base/metrics/histogram_functions.h"
+#include "components/signin/public/identity_manager/account_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+
+ScopedWebSigninInterceptionBubbleHandle::
+    ~ScopedWebSigninInterceptionBubbleHandle() = default;
+
+bool SigninInterceptionHeuristicOutcomeIsSuccess(
+    SigninInterceptionHeuristicOutcome outcome) {
+  return outcome == SigninInterceptionHeuristicOutcome::kInterceptEnterprise ||
+         outcome == SigninInterceptionHeuristicOutcome::kInterceptMultiUser ||
+         outcome ==
+             SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch ||
+         outcome ==
+             SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced ||
+         outcome == SigninInterceptionHeuristicOutcome::
+                        kInterceptEnterpriseForcedProfileSwitch;
+}
+
+WebSigninInterceptor::Delegate::BubbleParameters::BubbleParameters(
+    SigninInterceptionType interception_type,
+    AccountInfo intercepted_account,
+    AccountInfo primary_account,
+    SkColor profile_highlight_color,
+    bool show_guest_option,
+    bool show_link_data_option,
+    bool show_managed_disclaimer)
+    : interception_type(interception_type),
+      intercepted_account(intercepted_account),
+      primary_account(primary_account),
+      profile_highlight_color(profile_highlight_color),
+      show_guest_option(show_guest_option),
+      show_link_data_option(show_link_data_option),
+      show_managed_disclaimer(show_managed_disclaimer) {}
+
+WebSigninInterceptor::Delegate::BubbleParameters::BubbleParameters(
+    const BubbleParameters& copy) = default;
+
+WebSigninInterceptor::Delegate::BubbleParameters&
+WebSigninInterceptor::Delegate::BubbleParameters::operator=(
+    const BubbleParameters&) = default;
+
+WebSigninInterceptor::Delegate::BubbleParameters::~BubbleParameters() = default;
+
+WebSigninInterceptor::WebSigninInterceptor() = default;
+WebSigninInterceptor::~WebSigninInterceptor() = default;
diff --git a/chrome/browser/signin/web_signin_interceptor.h b/chrome/browser/signin/web_signin_interceptor.h
new file mode 100644
index 0000000..285e0c6d
--- /dev/null
+++ b/chrome/browser/signin/web_signin_interceptor.h
@@ -0,0 +1,195 @@
+// Copyright 2023 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_SIGNIN_WEB_SIGNIN_INTERCEPTOR_H_
+#define CHROME_BROWSER_SIGNIN_WEB_SIGNIN_INTERCEPTOR_H_
+
+#include <memory>
+
+#include "base/cancelable_callback.h"
+#include "base/feature_list.h"
+#include "base/functional/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h"
+#include "chrome/browser/ui/webui/signin/signin_utils.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "google_apis/gaia/core_account_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace content {
+class WebContents;
+}
+
+struct AccountInfo;
+class Browser;
+
+// Outcome of the interception heuristic (decision whether the interception
+// bubble is shown or not).
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class SigninInterceptionHeuristicOutcome {
+  // Interception succeeded:
+  kInterceptProfileSwitch = 0,
+  kInterceptMultiUser = 1,
+  kInterceptEnterprise = 2,
+
+  // Interception aborted:
+  // This is a "Sync" sign in and not a "web" sign in.
+  kAbortSyncSignin = 3,
+  // Another interception is already in progress.
+  kAbortInterceptInProgress = 4,
+  // This is not a new account (reauth).
+  kAbortAccountNotNew = 5,
+  // New profile is not offered when there is only one account.
+  kAbortSingleAccount = 6,
+  // Extended account info could not be downloaded.
+  kAbortAccountInfoTimeout = 7,
+  // Account info not compatible with interception (e.g. same Gaia name).
+  kAbortAccountInfoNotCompatible = 8,
+  // Profile creation disallowed.
+  kAbortProfileCreationDisallowed = 9,
+  // The interceptor was shut down before the heuristic completed.
+  kAbortShutdown = 10,
+  // The interceptor is not offered when  the `WebContents` has no browser
+  // associated, or its browser does not support displaying the interception UI.
+  kAbortNoSupportedBrowser = 11,
+  // A password update is required for the account, and this takes priority over
+  // signin interception.
+  kAbortPasswordUpdate = 12,
+  // A password update will be required for the account: the password used on
+  // the form does not match the stored password.
+  kAbortPasswordUpdatePending = 13,
+  // The user already declined a new profile for this account, the UI is not
+  // shown again.
+  kAbortUserDeclinedProfileForAccount = 14,
+  // Signin interception is disabled by the SigninInterceptionEnabled policy.
+  kAbortInterceptionDisabled = 15,
+
+  // Interception succeeded when enteprise account separation is mandatory.
+  kInterceptEnterpriseForced = 16,
+  kInterceptEnterpriseForcedProfileSwitch = 17,
+
+  // The interceptor is not triggered if the tab has already been closed.
+  kAbortTabClosed = 18,
+
+  kMaxValue = kAbortTabClosed,
+};
+
+// User selection in the interception bubble.
+enum class SigninInterceptionUserChoice { kAccept, kDecline, kGuest };
+
+// User action resulting from the interception bubble.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class SigninInterceptionResult {
+  kAccepted = 0,
+  kDeclined = 1,
+  kIgnored = 2,
+
+  // Used when the bubble was not shown because it's not implemented.
+  kNotDisplayed = 3,
+
+  // Accepted to be opened in Guest profile.
+  kAcceptedWithGuest = 4,
+
+  kAcceptedWithExistingProfile = 5,
+
+  kMaxValue = kAcceptedWithExistingProfile,
+};
+
+// The ScopedWebSigninInterceptionBubbleHandle closes the signin intercept
+// bubble when it is destroyed, if the bubble is still opened. Note that this
+// handle does not prevent the bubble from being closed for other reasons.
+class ScopedWebSigninInterceptionBubbleHandle {
+ public:
+  virtual ~ScopedWebSigninInterceptionBubbleHandle() = 0;
+};
+
+// Returns whether the heuristic outcome is a success (the signin should be
+// intercepted).
+bool SigninInterceptionHeuristicOutcomeIsSuccess(
+    SigninInterceptionHeuristicOutcome outcome);
+
+class WebSigninInterceptor {
+ public:
+  enum class SigninInterceptionType {
+    kProfileSwitch,
+    kEnterprise,
+    kMultiUser,
+    kEnterpriseForced,
+    kEnterpriseAcceptManagement,
+    kProfileSwitchForced
+  };
+
+  // Delegate class responsible for showing the various interception UIs.
+  class Delegate {
+   public:
+    // Parameters for interception bubble UIs.
+    struct BubbleParameters {
+      BubbleParameters(SigninInterceptionType interception_type,
+                       AccountInfo intercepted_account,
+                       AccountInfo primary_account,
+                       SkColor profile_highlight_color = SkColor(),
+                       bool show_guest_option = false,
+                       bool show_link_data_option = false,
+                       bool show_managed_disclaimer = false);
+
+      BubbleParameters(const BubbleParameters& copy);
+      BubbleParameters& operator=(const BubbleParameters&);
+      ~BubbleParameters();
+
+      SigninInterceptionType interception_type;
+      AccountInfo intercepted_account;
+      AccountInfo primary_account;
+      SkColor profile_highlight_color;
+      bool show_guest_option;
+      bool show_link_data_option;
+      bool show_managed_disclaimer;
+    };
+
+    virtual ~Delegate() = default;
+
+    // Returns whether the `web_contents` supports signin interception.
+    virtual bool IsSigninInterceptionSupported(
+        const content::WebContents& web_contents) = 0;
+
+    // Shows the signin interception bubble and calls |callback| to indicate
+    // whether the user should continue in a new profile.
+    // The callback is never called if the delegate is deleted before it
+    // completes.
+    // May return a nullptr handle if the bubble cannot be shown.
+    // Warning: the handle closes the bubble when it is destroyed ; it is the
+    // responsibility of the caller to keep the handle alive until the bubble
+    // should be closed.
+    // The callback must not be called synchronously if this function returns a
+    // valid handle (because the caller needs to be able to close the bubble
+    // from the callback).
+    virtual std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
+    ShowSigninInterceptionBubble(
+        content::WebContents* web_contents,
+        const BubbleParameters& bubble_parameters,
+        base::OnceCallback<void(SigninInterceptionResult)> callback) = 0;
+
+    // Shows the first run experience for `account_id` in `browser` opened for
+    // a newly created profile.
+    virtual void ShowFirstRunExperienceInNewProfile(
+        Browser* browser,
+        const CoreAccountId& account_id,
+        SigninInterceptionType interception_type) = 0;
+  };
+
+  WebSigninInterceptor(const WebSigninInterceptor&) = delete;
+  WebSigninInterceptor& operator=(const WebSigninInterceptor&) = delete;
+
+ protected:
+  WebSigninInterceptor();
+  virtual ~WebSigninInterceptor();
+};
+
+#endif  // CHROME_BROWSER_SIGNIN_WEB_SIGNIN_INTERCEPTOR_H_
diff --git a/chrome/browser/speech/tts_controller_delegate_impl_unittest.cc b/chrome/browser/speech/tts_controller_delegate_impl_unittest.cc
index 703073b..2fd5b15 100644
--- a/chrome/browser/speech/tts_controller_delegate_impl_unittest.cc
+++ b/chrome/browser/speech/tts_controller_delegate_impl_unittest.cc
@@ -107,14 +107,12 @@
 
   TestingPrefServiceSimple pref_service;
   // Uses default pref voices.
-  base::Value lang_to_voices(base::Value::Type::DICT);
-  lang_to_voices.SetKey(
-      "es", base::Value("{\"name\":\"Voice7\",\"extension\":\"id7\"}"));
-  lang_to_voices.SetKey(
-      "he", base::Value("{\"name\":\"Voice8\",\"extension\":\"id8\"}"));
-  lang_to_voices.SetKey(
-      "noLanguageCode",
-      base::Value("{\"name\":\"Android\",\"extension\":\"x\"}"));
+  auto lang_to_voices =
+      base::Value::Dict()
+          .Set("es", base::Value("{\"name\":\"Voice7\",\"extension\":\"id7\"}"))
+          .Set("he", base::Value("{\"name\":\"Voice8\",\"extension\":\"id8\"}"))
+          .Set("noLanguageCode",
+               base::Value("{\"name\":\"Android\",\"extension\":\"x\"}"));
   pref_service.registry()->RegisterDictionaryPref(
       prefs::kTextToSpeechLangToVoiceName, std::move(lang_to_voices));
   delegate.pref_service_ = &pref_service;
diff --git a/chrome/browser/sync/test/integration/saved_tab_groups_helper.cc b/chrome/browser/sync/test/integration/saved_tab_groups_helper.cc
new file mode 100644
index 0000000..161164f
--- /dev/null
+++ b/chrome/browser/sync/test/integration/saved_tab_groups_helper.cc
@@ -0,0 +1,102 @@
+// Copyright 2023 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/sync/test/integration/saved_tab_groups_helper.h"
+
+#include <vector>
+
+#include "base/uuid.h"
+#include "chrome/browser/sync/test/integration/status_change_checker.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
+#include "components/saved_tab_groups/saved_tab_group_model_observer.h"
+
+class SavedTabGroupKeyedService;
+
+namespace saved_tab_groups_helper {
+
+// ====================================
+// --- SavedTabOrGroupExistsChecker ---
+// ====================================
+SavedTabOrGroupExistsChecker::SavedTabOrGroupExistsChecker(
+    SavedTabGroupKeyedService* service,
+    const base::Uuid& uuid)
+    : uuid_(uuid), service_(service) {
+  CHECK(service_);
+  service_->model()->AddObserver(this);
+}
+
+SavedTabOrGroupExistsChecker::~SavedTabOrGroupExistsChecker() {
+  service_->model()->RemoveObserver(this);
+}
+
+bool SavedTabOrGroupExistsChecker::IsExitConditionSatisfied(std::ostream* os) {
+  *os << "Waiting for data for uuid '" + uuid_.AsLowercaseString() +
+             "' to be added.";
+
+  const SavedTabGroupModel* const model = service_->model();
+
+  // Expect that `uuid_` exists in the SavedTabGroupModel.
+  for (const SavedTabGroup& group : model->saved_tab_groups()) {
+    if (group.saved_guid() == uuid_ || group.ContainsTab(uuid_)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void SavedTabOrGroupExistsChecker::SavedTabGroupAddedFromSync(
+    const base::Uuid& uuid) {
+  CheckExitCondition();
+}
+
+void SavedTabOrGroupExistsChecker::SavedTabGroupUpdatedFromSync(
+    const base::Uuid& group_uuid,
+    const absl::optional<base::Uuid>& tab_uuid) {
+  CheckExitCondition();
+}
+
+// ==========================================
+// --- SavedTabOrGroupDoesNotExistChecker ---
+// ==========================================
+SavedTabOrGroupDoesNotExistChecker::SavedTabOrGroupDoesNotExistChecker(
+    SavedTabGroupKeyedService* service,
+    const base::Uuid& uuid)
+    : uuid_(uuid), service_(service) {
+  CHECK(service_);
+  service_->model()->AddObserver(this);
+}
+
+SavedTabOrGroupDoesNotExistChecker::~SavedTabOrGroupDoesNotExistChecker() {
+  service_->model()->RemoveObserver(this);
+}
+
+bool SavedTabOrGroupDoesNotExistChecker::IsExitConditionSatisfied(
+    std::ostream* os) {
+  *os << "Waiting for data for uuid '" + uuid_.AsLowercaseString() +
+             "' to be deleted.";
+
+  const SavedTabGroupModel* const model = service_->model();
+
+  // Expect that `uuid_` does not exist in the SavedTabGroupModel.
+  for (const SavedTabGroup& group : model->saved_tab_groups()) {
+    if (group.saved_guid() == uuid_ || group.ContainsTab(uuid_)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void SavedTabOrGroupDoesNotExistChecker::SavedTabGroupRemovedFromSync(
+    const SavedTabGroup* removed_group) {
+  CheckExitCondition();
+}
+
+void SavedTabOrGroupDoesNotExistChecker::SavedTabGroupUpdatedFromSync(
+    const base::Uuid& group_uuid,
+    const absl::optional<base::Uuid>& tab_uuid) {
+  CheckExitCondition();
+}
+}  // namespace saved_tab_groups_helper
diff --git a/chrome/browser/sync/test/integration/saved_tab_groups_helper.h b/chrome/browser/sync/test/integration/saved_tab_groups_helper.h
new file mode 100644
index 0000000..18b2506
--- /dev/null
+++ b/chrome/browser/sync/test/integration/saved_tab_groups_helper.h
@@ -0,0 +1,79 @@
+// Copyright 2023 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_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_
+#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_
+
+#include <vector>
+
+#include "base/uuid.h"
+#include "chrome/browser/sync/test/integration/status_change_checker.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
+#include "components/saved_tab_groups/saved_tab_group_model_observer.h"
+
+class SavedTabGroupKeyedService;
+
+namespace saved_tab_groups_helper {
+
+// Checks that a tab or group with a particular uuid exists in the model.
+class SavedTabOrGroupExistsChecker : public StatusChangeChecker,
+                                     public SavedTabGroupModelObserver {
+ public:
+  // The caller must ensure that `service` is not null and will outlive this
+  // object.
+  SavedTabOrGroupExistsChecker(SavedTabGroupKeyedService* service,
+                               const base::Uuid& uuid);
+  SavedTabOrGroupExistsChecker(const SavedTabOrGroupExistsChecker&) = delete;
+  SavedTabOrGroupExistsChecker& operator=(const SavedTabOrGroupExistsChecker&) =
+      delete;
+  ~SavedTabOrGroupExistsChecker() override;
+
+  // StatusChangeChecker:
+  bool IsExitConditionSatisfied(std::ostream* os) override;
+
+  // SavedTabGroupModelObserver:
+  void SavedTabGroupAddedFromSync(const base::Uuid& uuid) override;
+  void SavedTabGroupUpdatedFromSync(
+      const base::Uuid& group_uuid,
+      const absl::optional<base::Uuid>& tab_uuid = absl::nullopt) override;
+
+ private:
+  const base::Uuid uuid_;
+  raw_ptr<SavedTabGroupKeyedService> const service_;
+};
+
+// Checks that a tab or group with a particular uuid does not exists in the
+// model.
+class SavedTabOrGroupDoesNotExistChecker : public StatusChangeChecker,
+                                           public SavedTabGroupModelObserver {
+ public:
+  // The caller must ensure that `service` is not null and will outlive this
+  // object.
+  SavedTabOrGroupDoesNotExistChecker(SavedTabGroupKeyedService* service,
+                                     const base::Uuid& uuid);
+  SavedTabOrGroupDoesNotExistChecker(
+      const SavedTabOrGroupDoesNotExistChecker&) = delete;
+  SavedTabOrGroupDoesNotExistChecker& operator=(
+      const SavedTabOrGroupDoesNotExistChecker&) = delete;
+  ~SavedTabOrGroupDoesNotExistChecker() override;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override;
+
+  // SavedTabGroupModelObserver
+  void SavedTabGroupRemovedFromSync(
+      const SavedTabGroup* removed_group) override;
+
+  // Note: Also handles the removal of tabs.
+  void SavedTabGroupUpdatedFromSync(
+      const base::Uuid& group_uuid,
+      const absl::optional<base::Uuid>& tab_uuid = absl::nullopt) override;
+
+ private:
+  const base::Uuid uuid_;
+  raw_ptr<SavedTabGroupKeyedService> const service_;
+};
+}  // namespace saved_tab_groups_helper
+
+#endif  // CHROME_BROWSER_SYNC_TEST_INTEGRATION_SAVED_TAB_GROUPS_HELPER_H_
diff --git a/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc b/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc
new file mode 100644
index 0000000..c2bc3e8
--- /dev/null
+++ b/chrome/browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc
@@ -0,0 +1,150 @@
+// Copyright 2023 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/sync/test/integration/saved_tab_groups_helper.h"
+#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
+#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_service_factory.h"
+#include "chrome/browser/ui/ui_features.h"
+#include "components/sync/base/model_type.h"
+#include "components/sync/protocol/saved_tab_group_specifics.pb.h"
+#include "components/sync/protocol/sync.pb.h"
+#include "components/sync/test/fake_server.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class SingleClientSavedTabGroupsSyncTest : public SyncTest {
+ public:
+  SingleClientSavedTabGroupsSyncTest() : SyncTest(SINGLE_CLIENT) {
+    features_.InitWithFeatures(
+        {features::kTabGroupsSave, features::kTabGroupsSaveSyncIntegration},
+        /*disabled_features=*/{});
+  }
+  ~SingleClientSavedTabGroupsSyncTest() override = default;
+  SingleClientSavedTabGroupsSyncTest(
+      const SingleClientSavedTabGroupsSyncTest&) = delete;
+  SingleClientSavedTabGroupsSyncTest& operator=(
+      const SingleClientSavedTabGroupsSyncTest&) = delete;
+
+  void AddDataToFakeServer(const sync_pb::SavedTabGroupSpecifics& specifics) {
+    sync_pb::EntitySpecifics group_entity_specifics;
+    sync_pb::SavedTabGroupSpecifics* group_specifics =
+        group_entity_specifics.mutable_saved_tab_group();
+    group_specifics->CopyFrom(specifics);
+
+    std::string client_tag = group_specifics->guid();
+    int64_t creation_time =
+        group_specifics->creation_time_windows_epoch_micros();
+    int64_t update_time = group_specifics->update_time_windows_epoch_micros();
+
+    fake_server_->InjectEntity(
+        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
+            "non_unique_name", client_tag, group_entity_specifics,
+            /*creation_time=*/creation_time,
+            /*last_modified_time=*/update_time));
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
+// Save a group with two tabs and validate they are added to the model.
+IN_PROC_BROWSER_TEST_F(SingleClientSavedTabGroupsSyncTest,
+                       DownloadsGroupAndTabs) {
+  SavedTabGroup group1(u"Group 1", tab_groups::TabGroupColorId::kGrey, {});
+  SavedTabGroupTab tab1(GURL("about:blank"), u"about:blank",
+                        group1.saved_guid());
+  SavedTabGroupTab tab2(GURL("about:blank"), u"about:blank",
+                        group1.saved_guid());
+
+  // Add a group with two tabs to sync.
+  AddDataToFakeServer(*group1.ToSpecifics());
+  AddDataToFakeServer(*tab1.ToSpecifics());
+  AddDataToFakeServer(*tab2.ToSpecifics());
+
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  ASSERT_TRUE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::SAVED_TAB_GROUP));
+
+  SavedTabGroupKeyedService* const service =
+      SavedTabGroupServiceFactory::GetForProfile(GetProfile(0));
+
+  // Verify they are added to the model.
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, group1.saved_guid())
+                  .Wait());
+
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, tab1.saved_tab_guid())
+                  .Wait());
+
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, tab2.saved_tab_guid())
+                  .Wait());
+}
+
+// Save a group with no tabs and validate it is added to the model.
+IN_PROC_BROWSER_TEST_F(SingleClientSavedTabGroupsSyncTest,
+                       DownloadsGroupWithNoTabs) {
+  SavedTabGroup group1(u"Group 1", tab_groups::TabGroupColorId::kGrey, {});
+  SavedTabGroupTab tab1(GURL("about:blank"), u"about:blank",
+                        group1.saved_guid());
+
+  // Add a group with no tabs from sync.
+  AddDataToFakeServer(*group1.ToSpecifics());
+
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  ASSERT_TRUE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::SAVED_TAB_GROUP));
+
+  SavedTabGroupKeyedService* const service =
+      SavedTabGroupServiceFactory::GetForProfile(GetProfile(0));
+
+  // Verify the group is added to the model but not the tab.
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, group1.saved_guid())
+                  .Wait());
+
+  EXPECT_TRUE(service->model()->Contains(group1.saved_guid()));
+  EXPECT_TRUE(service->model()->Get(group1.saved_guid())->saved_tabs().empty());
+}
+
+// Save a tab with no group and validate it is added to the model.
+IN_PROC_BROWSER_TEST_F(SingleClientSavedTabGroupsSyncTest,
+                       DownloadsTabWithNoGroup) {
+  SavedTabGroup group1(u"Group 1", tab_groups::TabGroupColorId::kGrey, {});
+  SavedTabGroupTab tab1(GURL("about:blank"), u"about:blank",
+                        group1.saved_guid());
+
+  // Add a group with no tabs from sync.
+  AddDataToFakeServer(*tab1.ToSpecifics());
+
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  ASSERT_TRUE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::SAVED_TAB_GROUP));
+
+  SavedTabGroupKeyedService* const service =
+      SavedTabGroupServiceFactory::GetForProfile(GetProfile(0));
+
+  // TODO(crbug/1445672): Verify that the orphaned tab was exists but isn't
+  // linked to any group.
+
+  // Verify adding the corresponding group adds the orphaned tab to the model.
+  AddDataToFakeServer(*group1.ToSpecifics());
+
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, group1.saved_guid())
+                  .Wait());
+
+  EXPECT_TRUE(saved_tab_groups_helper::SavedTabOrGroupExistsChecker(
+                  service, tab1.saved_tab_guid())
+                  .Wait());
+}
+
+// TODO(crbug/1445146): Implement remaining integration tests.
+}  // namespace
diff --git a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
index 069ae956..ea97225 100644
--- a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
@@ -74,12 +74,11 @@
   void InjectWebAppEntityToFakeServer(
       const std::string& app_id,
       const GURL& url,
-      absl::optional<std::string> manifest_id = absl::nullopt) {
+      absl::optional<std::string> relative_manifest_id = absl::nullopt) {
     WebApp app(app_id);
     app.SetName(app_id);
     app.SetStartUrl(url);
     app.SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
-    app.SetManifestId(manifest_id);
 
     WebApp::SyncFallbackData sync_fallback_data;
     sync_fallback_data.name = app_id;
@@ -88,6 +87,12 @@
     sync_pb::EntitySpecifics entity_specifics;
 
     *(entity_specifics.mutable_web_app()) = WebAppToSyncProto(app);
+    if (relative_manifest_id) {
+      entity_specifics.mutable_web_app()->set_relative_manifest_id(
+          relative_manifest_id.value());
+    } else {
+      entity_specifics.mutable_web_app()->clear_relative_manifest_id();
+    }
 
     fake_server_->InjectEntity(
         syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
@@ -152,11 +157,11 @@
 
 IN_PROC_BROWSER_TEST_F(SingleClientWebAppsSyncTest,
                        AppWithIdSpecifiedSyncInstalled) {
-  const absl::optional<std::string> manifest_id("explicit_id");
+  const std::string relative_manifest_id = "explicit_id";
   GURL url("https://example.com/start");
-  const std::string app_id = GenerateAppId(manifest_id, url);
+  const std::string app_id = GenerateAppId(relative_manifest_id, url);
 
-  InjectWebAppEntityToFakeServer(app_id, url, manifest_id);
+  InjectWebAppEntityToFakeServer(app_id, url, relative_manifest_id);
   ASSERT_TRUE(SetupSync());
   AwaitWebAppQuiescence();
 
@@ -171,7 +176,7 @@
   info.description = u"Test description";
   info.start_url = url;
   info.scope = url;
-  info.manifest_id = manifest_id;
+  info.manifest_id = GenerateManifestId(relative_manifest_id, url);
   const AppId installed_app_id =
       apps_helper::InstallWebApp(GetProfile(0), info);
 
@@ -182,11 +187,11 @@
 
 IN_PROC_BROWSER_TEST_F(SingleClientWebAppsSyncTest,
                        AppWithIdSpecifiedAsEmptyStringSyncInstalled) {
-  const absl::optional<std::string> manifest_id("");
+  const std::string relative_manifest_id = "";
   GURL url("https://example.com/start");
-  const std::string app_id = GenerateAppId(manifest_id, url);
+  const std::string app_id = GenerateAppId(relative_manifest_id, url);
 
-  InjectWebAppEntityToFakeServer(app_id, url, manifest_id);
+  InjectWebAppEntityToFakeServer(app_id, url, relative_manifest_id);
   ASSERT_TRUE(SetupSync());
   AwaitWebAppQuiescence();
 
@@ -201,7 +206,7 @@
   info.description = u"Test description";
   info.start_url = url;
   info.scope = url;
-  info.manifest_id = manifest_id;
+  info.manifest_id = GenerateManifestId(relative_manifest_id, url);
   const AppId installed_app_id =
       apps_helper::InstallWebApp(GetProfile(0), info);
 
@@ -209,5 +214,6 @@
       /*manifest_id=*/absl::nullopt, GURL("https://example.com/"));
   EXPECT_EQ(expected_app_id, installed_app_id);
 }
+
 }  // namespace
 }  // namespace web_app
diff --git a/chrome/browser/sync/test/integration/sync_integration_tests_sources.gni b/chrome/browser/sync/test/integration/sync_integration_tests_sources.gni
index 9d26dc9..cb1d961 100644
--- a/chrome/browser/sync/test/integration/sync_integration_tests_sources.gni
+++ b/chrome/browser/sync/test/integration/sync_integration_tests_sources.gni
@@ -35,6 +35,7 @@
     "../browser/sync/test/integration/single_client_passwords_sync_test.cc",
     "../browser/sync/test/integration/single_client_polling_sync_test.cc",
     "../browser/sync/test/integration/single_client_preferences_sync_test.cc",
+    "../browser/sync/test/integration/single_client_saved_tab_groups_sync_test.cc",
     "../browser/sync/test/integration/single_client_search_engines_sync_test.cc",
     "../browser/sync/test/integration/single_client_secondary_account_sync_test.cc",
     "../browser/sync/test/integration/single_client_send_tab_to_self_sync_test.cc",
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9bf2722..a47213fb 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1678,6 +1678,8 @@
       "webui/settings/safety_check_extensions_handler.h",
       "webui/settings/safety_check_handler.cc",
       "webui/settings/safety_check_handler.h",
+      "webui/settings/safety_hub_handler.cc",
+      "webui/settings/safety_hub_handler.h",
       "webui/settings/search_engines_handler.cc",
       "webui/settings/search_engines_handler.h",
       "webui/settings/settings_clear_browsing_data_handler.cc",
@@ -1704,8 +1706,6 @@
       "webui/settings/site_settings_handler.h",
       "webui/settings/site_settings_helper.cc",
       "webui/settings/site_settings_helper.h",
-      "webui/settings/site_settings_permissions_handler.cc",
-      "webui/settings/site_settings_permissions_handler.h",
       "webui/side_panel/bookmarks/bookmarks_page_handler.cc",
       "webui/side_panel/bookmarks/bookmarks_page_handler.h",
       "webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 3c1e19b..6687530 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1083,7 +1083,7 @@
         To measure the performance of an ad, limited types of data can be shared among sites, such as the time of day an ad was shown to you.
       </message>
       <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID" desc="First description in the restricted notice which applies to Android only">
-        Chrome now shares only very limited information among sites and apps, such as when an ad was shown to you, to help sites measure the performance of ads.
+        We’re launching a new ad privacy feature called ad measurement. Chrome shares only very limited information among sites and apps, such as when an ad was shown to you, to help measure the performance of ads.
       </message>
       <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_ANDROID" desc="Special description in the restricted notice which applies to Android only">
         Your Android device may include a similar setting. If this setting is turned on in Chrome and on your Android device, a company may be able to measure the effectiveness of an ad across websites you visit and apps you use.
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID.png.sha1
index d04ba348..d619eee8 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID.png.sha1
@@ -1 +1 @@
-da39a3ee5e6b4b0d3255bfef95601890afd80709
\ No newline at end of file
+482d7ca6ae19a406af26d7402ab15d534580d62c
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/app_access_notifier_unittest.cc b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
index 0e2b340..e68d663 100644
--- a/chrome/browser/ui/ash/app_access_notifier_unittest.cc
+++ b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
@@ -593,7 +593,7 @@
 
   // Make sure privacy indicators work on multiple displays.
   display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
-      .UpdateDisplay("800x800,801+0-800x800");
+      .UpdateDisplay("800x700,801+0-800x700");
 
   ExpectPrivacyIndicatorsVisible(/*visible=*/false);
 
diff --git a/chrome/browser/ui/side_panel/side_panel_entry_id.h b/chrome/browser/ui/side_panel/side_panel_entry_id.h
index f825b82..93c440e 100644
--- a/chrome/browser/ui/side_panel/side_panel_entry_id.h
+++ b/chrome/browser/ui/side_panel/side_panel_entry_id.h
@@ -24,6 +24,7 @@
   kAboutThisSite,
   kCustomizeChrome,
   kSearchCompanion,
+  kShoppingInsights,
   // Extensions (nothing more should be added below here)
   kExtension
 };
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
index 4cd72ef7..0465b2d 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
@@ -27,7 +27,7 @@
 namespace {
 
 class ForcedProfileSwitchInterceptionHandle
-    : public ScopedDiceWebSigninInterceptionBubbleHandle {
+    : public ScopedWebSigninInterceptionBubbleHandle {
  public:
   explicit ForcedProfileSwitchInterceptionHandle(
       base::OnceCallback<void(SigninInterceptionResult)> callback) {
@@ -45,18 +45,16 @@
 };
 
 class ForcedEnterpriseSigninInterceptionHandle
-    : public ScopedDiceWebSigninInterceptionBubbleHandle {
+    : public ScopedWebSigninInterceptionBubbleHandle {
  public:
   ForcedEnterpriseSigninInterceptionHandle(
       Browser* browser,
-      const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-          bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(SigninInterceptionResult)> callback)
       : browser_(browser->AsWeakPtr()),
         profile_creation_required_by_policy_(
             bubble_parameters.interception_type ==
-            DiceWebSigninInterceptor::SigninInterceptionType::
-                kEnterpriseForced),
+            WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced),
         show_link_data_option_(bubble_parameters.show_link_data_option),
         callback_(std::move(callback)) {
     DCHECK(browser_);
@@ -122,13 +120,13 @@
   return IsSigninInterceptionSupportedInternal(*browser);
 }
 
-std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
 DiceWebSigninInterceptorDelegate::ShowSigninInterceptionBubble(
     content::WebContents* web_contents,
-    const BubbleParameters& bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(SigninInterceptionResult)> callback) {
   if (bubble_parameters.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced) {
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced) {
     return std::make_unique<ForcedProfileSwitchInterceptionHandle>(
         std::move(callback));
   }
@@ -139,9 +137,9 @@
   }
 
   if (bubble_parameters.interception_type ==
-          DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced ||
+          WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced ||
       bubble_parameters.interception_type ==
-          DiceWebSigninInterceptor::SigninInterceptionType::
+          WebSigninInterceptor::SigninInterceptionType::
               kEnterpriseAcceptManagement) {
     return std::make_unique<ForcedEnterpriseSigninInterceptionHandle>(
         chrome::FindBrowserWithWebContents(web_contents), bubble_parameters,
@@ -156,12 +154,12 @@
 void DiceWebSigninInterceptorDelegate::ShowFirstRunExperienceInNewProfile(
     Browser* browser,
     const CoreAccountId& account_id,
-    DiceWebSigninInterceptor::SigninInterceptionType interception_type) {
+    WebSigninInterceptor::SigninInterceptionType interception_type) {
   if (base::FeatureList::IsEnabled(kSyncPromoAfterSigninIntercept)) {
     browser->signin_view_controller()
         ->ShowModalInterceptFirstRunExperienceDialog(
             account_id, interception_type ==
-                            DiceWebSigninInterceptor::SigninInterceptionType::
+                            WebSigninInterceptor::SigninInterceptionType::
                                 kEnterpriseForced);
   } else {
     // Don't show the customization bubble if a valid policy theme is set.
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
index b26629b..2b668b2 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_UI_SIGNIN_DICE_WEB_SIGNIN_INTERCEPTOR_DELEGATE_H_
 #define CHROME_BROWSER_UI_SIGNIN_DICE_WEB_SIGNIN_INTERCEPTOR_DELEGATE_H_
 
-#include "chrome/browser/signin/dice_web_signin_interceptor.h"
+#include "chrome/browser/signin/web_signin_interceptor.h"
 
 #include "base/functional/callback_forward.h"
 
@@ -16,8 +16,7 @@
 class Browser;
 struct CoreAccountId;
 
-class DiceWebSigninInterceptorDelegate
-    : public DiceWebSigninInterceptor::Delegate {
+class DiceWebSigninInterceptorDelegate : public WebSigninInterceptor::Delegate {
  public:
   DiceWebSigninInterceptorDelegate();
   ~DiceWebSigninInterceptorDelegate() override;
@@ -25,7 +24,7 @@
   // DiceWebSigninInterceptor::Delegate
   bool IsSigninInterceptionSupported(
       const content::WebContents& web_contents) override;
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
   ShowSigninInterceptionBubble(
       content::WebContents* web_contents,
       const BubbleParameters& bubble_parameters,
@@ -33,12 +32,11 @@
   void ShowFirstRunExperienceInNewProfile(
       Browser* browser,
       const CoreAccountId& account_id,
-      DiceWebSigninInterceptor::SigninInterceptionType interception_type)
-      override;
+      WebSigninInterceptor::SigninInterceptionType interception_type) override;
 
  private:
   // Implemented in dice_web_signin_interception_bubble_view.cc
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
   ShowSigninInterceptionBubbleInternal(
       Browser* browser,
       const BubbleParameters& bubble_parameters,
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 97029ad..7331286 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -144,6 +144,7 @@
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/user_notes/user_notes_features.h"
 #include "components/webapps/browser/installable/installable_manager.h"
+#include "components/webapps/browser/installable/ml_installability_promoter.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/buildflags/buildflags.h"
 #include "media/base/media_switches.h"
@@ -358,6 +359,7 @@
   HistoryClustersTabHelper::CreateForWebContents(web_contents);
   HttpsOnlyModeTabHelper::CreateForWebContents(web_contents);
   webapps::InstallableManager::CreateForWebContents(web_contents);
+  webapps::MLInstallabilityPromoter::CreateForWebContents(web_contents);
   login_detection::LoginDetectionTabHelper::MaybeCreateForWebContents(
       web_contents);
   if (MediaEngagementService::IsEnabled())
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view.cc b/chrome/browser/ui/views/page_action/pwa_install_view.cc
index 0e61c82..d77a602 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view.cc
@@ -31,6 +31,7 @@
 #include "components/webapps/browser/banners/app_banner_manager.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "content/public/browser/browser_thread.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/view_class_properties.h"
@@ -187,6 +188,10 @@
 
 bool PwaInstallView::ShouldShowIph(content::WebContents* web_contents,
                                    webapps::AppBannerManager* manager) {
+  if (blink::IsEmptyManifest(manager->manifest()) ||
+      !manager->manifest().id.is_valid()) {
+    return false;
+  }
   web_app::AppId app_id =
       web_app::GenerateAppIdFromManifest(manager->manifest());
 
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
index 22988add..080bfd8 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
@@ -179,11 +179,6 @@
 PermissionPromptBubbleBaseView::~PermissionPromptBubbleBaseView() = default;
 
 void PermissionPromptBubbleBaseView::Show() {
-  CreateWidget();
-  ShowWidget();
-}
-
-void PermissionPromptBubbleBaseView::CreateWidget() {
   DCHECK(browser_->window());
 
   UpdateAnchorPosition();
@@ -198,15 +193,13 @@
   if (base::FeatureList::IsEnabled(views::features::kWidgetLayering)) {
     widget->SetZOrderSublevel(ChromeWidgetSublevel::kSublevelSecurity);
   }
-}
 
-void PermissionPromptBubbleBaseView::ShowWidget() {
   // If a browser window (or popup) other than the bubble parent has focus,
   // don't take focus.
   if (browser_->window()->IsActive()) {
-    GetWidget()->Show();
+    widget->Show();
   } else {
-    GetWidget()->ShowInactive();
+    widget->ShowInactive();
   }
 
   SizeToContents();
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h
index ef2a1a8e..10864d2 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h
@@ -55,14 +55,12 @@
       const PermissionPromptBubbleBaseView&) = delete;
   ~PermissionPromptBubbleBaseView() override;
 
-  virtual void Show();
+  void Show();
 
   // Anchors the bubble to the view or rectangle returned from
   // bubble_anchor_util::GetPageInfoAnchorConfiguration.
   void UpdateAnchorPosition();
 
-  void ShowWidget();
-
   void SetPromptStyle(PermissionPromptStyle prompt_style);
 
   // views::BubbleDialogDelegateView:
@@ -82,8 +80,6 @@
   void ClosingPermission();
 
  protected:
-  void CreateWidget();
-
   UrlIdentity GetUrlIdentityObject() { return url_identity_; }
 
   // Determines whether the current request should also display an
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.cc
index b9e9ab84..2872bd3c 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.cc
@@ -4,26 +4,13 @@
 
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.h"
 
-#include "chrome/browser/favicon/favicon_service_factory.h"
-#include "chrome/browser/ui/browser.h"
-#include "components/favicon/core/favicon_service.h"
-#include "components/favicon_base/favicon_callback.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/resources/grit/ui_resources.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/vector_icons.h"
+#include "ui/gfx/text_constants.h"
 
 namespace {
 
-// TODO(b/278181254): We might need to fetch larger icons on higher dpi
-// screens.
-constexpr int kDesiredFaviconSizeInPixel = 32;
-// TODO(b/278181254): Add metrics for how long the favicons take to be fetched,
-// so we can adjust this delay accordingly.
-constexpr int kMaxShowDelayMs = 200;
-
 absl::optional<std::u16string> GetExtraTextTwoOrigin(
     permissions::PermissionPrompt::Delegate& delegate) {
   CHECK_GT(delegate.Requests().size(), 0u);
@@ -77,33 +64,7 @@
   CHECK_EQ(delegate->Requests()[0]->request_type(),
            permissions::RequestType::kStorageAccess);
 
-  AddFaviconRow();
-
-  CHECK(browser);
-
-  // Initializing favicon service.
-  favicon::FaviconService* const favicon_service =
-      FaviconServiceFactory::GetForProfile(browser->profile(),
-                                           ServiceAccessType::EXPLICIT_ACCESS);
-  favicon_tracker_ = std::make_unique<base::CancelableTaskTracker>();
-
-  // Fetching requesting origin favicon.
-  favicon_service->GetRawFaviconForPageURL(
-      delegate->GetRequestingOrigin(), {favicon_base::IconType::kFavicon},
-      kDesiredFaviconSizeInPixel, /*fallback_to_host=*/true,
-      base::BindOnce(&PermissionPromptBubbleTwoOriginsView::
-                         OnRequestingOriginFaviconLoaded,
-                     base::Unretained(this)),
-      favicon_tracker_.get());
-
-  // Fetching embedding origin favicon.
-  favicon_service->GetRawFaviconForPageURL(
-      delegate->GetEmbeddingOrigin(), {favicon_base::IconType::kFavicon},
-      kDesiredFaviconSizeInPixel, /*fallback_to_host=*/true,
-      base::BindOnce(
-          &PermissionPromptBubbleTwoOriginsView::OnEmbeddingOriginFaviconLoaded,
-          base::Unretained(this)),
-      favicon_tracker_.get());
+  // TODO(crbug/1433644): Call favicon factory and create favicon custom row.
 }
 
 PermissionPromptBubbleTwoOriginsView::~PermissionPromptBubbleTwoOriginsView() =
@@ -122,87 +83,3 @@
     GetBubbleFrameView()->SetTitleView(std::move(label));
   }
 }
-
-void PermissionPromptBubbleTwoOriginsView::Show() {
-  CreateWidget();
-
-  if (favicon_left_received_ && favicon_right_received_) {
-    ShowWidget();
-    return;
-  }
-
-  show_timer_.Start(FROM_HERE, base::Milliseconds(kMaxShowDelayMs),
-                    base::BindOnce(&PermissionPromptBubbleBaseView::ShowWidget,
-                                   base::Unretained(this)));
-}
-
-void PermissionPromptBubbleTwoOriginsView::AddFaviconRow() {
-  // Line container for the favicon icons.
-  auto* line_container =
-      AddChildViewAt(std::make_unique<views::View>(), /*index=*/0);
-
-  views::BoxLayout* box_layout =
-      line_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
-          /*between_child_spacing=*/4));
-
-  // Center box_layout children horizontally and vertically.
-  box_layout->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::kCenter);
-  box_layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kCenter);
-
-  // Getting default favicon.
-  const ui::NativeTheme* native_theme =
-      ui::NativeTheme::GetInstanceForNativeUi();
-  bool is_dark = native_theme && native_theme->ShouldUseDarkColors();
-  int resource_id =
-      is_dark ? IDR_DEFAULT_FAVICON_DARK_32 : IDR_DEFAULT_FAVICON_32;
-  ui::ImageModel default_favicon_ = ui::ImageModel::FromResourceId(resource_id);
-
-  // Left favicon for embedding origin.
-  favicon_left_ = line_container->AddChildView(
-      std::make_unique<views::ImageView>(default_favicon_));
-  favicon_left_->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
-
-  // Three dots.
-  line_container->AddChildView(
-      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
-          views::kOptionsIcon, ui::kColorIcon, /*icon_size=*/40)));
-
-  // Right favicon for requesting origin.
-  favicon_right_ = line_container->AddChildView(
-      std::make_unique<views::ImageView>(default_favicon_));
-  favicon_right_->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
-}
-
-void PermissionPromptBubbleTwoOriginsView::OnEmbeddingOriginFaviconLoaded(
-    const favicon_base::FaviconRawBitmapResult& favicon_result) {
-  favicon_left_received_ = true;
-
-  if (favicon_result.is_valid()) {
-    favicon_left_->SetImage(ui::ImageModel::FromImage(
-        gfx::Image::CreateFrom1xPNGBytes(favicon_result.bitmap_data->front(),
-                                         favicon_result.bitmap_data->size())));
-  }
-  MaybeShow();
-}
-
-void PermissionPromptBubbleTwoOriginsView::OnRequestingOriginFaviconLoaded(
-    const favicon_base::FaviconRawBitmapResult& favicon_result) {
-  favicon_right_received_ = true;
-
-  if (favicon_result.is_valid()) {
-    favicon_right_->SetImage(ui::ImageModel::FromImage(
-        gfx::Image::CreateFrom1xPNGBytes(favicon_result.bitmap_data->front(),
-                                         favicon_result.bitmap_data->size())));
-  }
-  MaybeShow();
-}
-
-void PermissionPromptBubbleTwoOriginsView::MaybeShow() {
-  if (favicon_left_received_ && favicon_right_received_ &&
-      show_timer_.IsRunning()) {
-    show_timer_.FireNow();
-  }
-}
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.h b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.h
index fde9dc4..ff300431 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.h
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view.h
@@ -5,10 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PERMISSIONS_PERMISSION_PROMPT_BUBBLE_TWO_ORIGINS_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_PERMISSIONS_PERMISSION_PROMPT_BUBBLE_TWO_ORIGINS_VIEW_H_
 
-#include "base/task/cancelable_task_tracker.h"
-#include "base/timer/timer.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h"
-#include "components/favicon_base/favicon_types.h"
 
 // Bubble that prompts the user to grant or deny a permission request from from
 // a pair of origins.
@@ -39,31 +36,6 @@
 
   // views::BubbleDialogDelegateView:
   void AddedToWidget() override;
-
-  // PermissionPromptBubbleBaseView
-  void Show() override;
-
- private:
-  void AddFaviconRow();
-
-  void OnEmbeddingOriginFaviconLoaded(
-      const favicon_base::FaviconRawBitmapResult& favicon_result);
-  void OnRequestingOriginFaviconLoaded(
-      const favicon_base::FaviconRawBitmapResult& favicon_result);
-
-  void MaybeShow();
-
-  // The task tracker for loading favicons.
-  std::unique_ptr<base::CancelableTaskTracker> favicon_tracker_;
-
-  raw_ptr<views::ImageView> favicon_right_;
-  raw_ptr<views::ImageView> favicon_left_;
-  bool favicon_right_received_ = false;
-  bool favicon_left_received_ = false;
-
-  // Timer that waits for a short period of time before showing the prompt to
-  // give the favicon service a chance to fetch the origins' favicons.
-  base::OneShotTimer show_timer_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_PERMISSIONS_PERMISSION_PROMPT_BUBBLE_TWO_ORIGINS_VIEW_H_
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view_unittest.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view_unittest.cc
index 07b9ead..0628d2b1 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view_unittest.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_two_origins_view_unittest.cc
@@ -6,13 +6,8 @@
 
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/favicon/favicon_service_factory.h"
-#include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_style.h"
 #include "chrome/grit/generated_resources.h"
-#include "chrome/test/base/test_browser_window.h"
-#include "chrome/test/base/testing_profile.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/permissions/permission_util.h"
 #include "components/permissions/request_type.h"
@@ -20,6 +15,8 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
+using PermissionPromptBubbleTwoOriginsViewTest = ChromeViewsTestBase;
+
 namespace {
 
 class TestDelegateTwoOrigins : public permissions::PermissionPrompt::Delegate {
@@ -87,46 +84,16 @@
   std::vector<permissions::PermissionRequest*> raw_requests_;
   base::WeakPtrFactory<TestDelegateTwoOrigins> weak_factory_{this};
 };
+
+std::unique_ptr<PermissionPromptBubbleBaseView> CreateBubble(
+    TestDelegateTwoOrigins* delegate) {
+  return std::make_unique<PermissionPromptBubbleTwoOriginsView>(
+      nullptr, delegate->GetWeakPtr(), base::TimeTicks::UnixEpoch(),
+      PermissionPromptStyle::kBubbleOnly);
+}
+
 }  // namespace
 
-class PermissionPromptBubbleTwoOriginsViewTest : public ChromeViewsTestBase {
- public:
-  void SetUp() override {
-    ChromeViewsTestBase::SetUp();
-    CreateBrowser();
-  }
-
-  Browser* browser() { return browser_.get(); }
-
-  std::unique_ptr<PermissionPromptBubbleBaseView> CreateBubble(
-      TestDelegateTwoOrigins* delegate) {
-    return std::make_unique<PermissionPromptBubbleTwoOriginsView>(
-        browser(), delegate->GetWeakPtr(), base::TimeTicks::Now(),
-        PermissionPromptStyle::kBubbleOnly);
-  }
-
- private:
-  void CreateBrowser() {
-    TestingProfile::Builder profile_builder;
-    profile_builder.AddTestingFactory(
-        HistoryServiceFactory::GetInstance(),
-        HistoryServiceFactory::GetDefaultFactory());
-    profile_builder.AddTestingFactory(
-        FaviconServiceFactory::GetInstance(),
-        FaviconServiceFactory::GetDefaultFactory());
-    profile_ = profile_builder.Build();
-    browser_window_ = std::make_unique<TestBrowserWindow>();
-    Browser::CreateParams params(profile_.get(), /*user_gesture=*/true);
-    params.type = Browser::TYPE_NORMAL;
-    params.window = browser_window_.get();
-    browser_.reset(Browser::Create(params));
-  }
-
-  std::unique_ptr<TestingProfile> profile_;
-  std::unique_ptr<Browser> browser_;
-  std::unique_ptr<TestBrowserWindow> browser_window_;
-};
-
 TEST_F(PermissionPromptBubbleTwoOriginsViewTest,
        TitleMentionsTwoOriginsAndPermission) {
   TestDelegateTwoOrigins delegate(GURL("https://test.requesting.origin"),
diff --git a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.cc b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.cc
index ce70470..971967cc 100644
--- a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.cc
+++ b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.cc
@@ -63,17 +63,16 @@
 }
 
 // static
-std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
 DiceWebSigninInterceptionBubbleView::CreateBubble(
     Browser* browser,
     views::View* anchor_view,
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(SigninInterceptionResult)> callback) {
   auto interception_bubble =
       base::WrapUnique(new DiceWebSigninInterceptionBubbleView(
           browser, anchor_view, bubble_parameters, std::move(callback)));
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> handle =
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> handle =
       interception_bubble->GetHandle();
   // The widget is owned by the views system and shown after the view is loaded
   // and the final height of the bubble is sent from
@@ -101,23 +100,22 @@
 
 // static
 void DiceWebSigninInterceptionBubbleView::RecordInterceptionResult(
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     Profile* profile,
     SigninInterceptionResult result) {
   std::string histogram_base_name = "Signin.InterceptResult";
   switch (bubble_parameters.interception_type) {
-    case DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise:
-    case DiceWebSigninInterceptor::SigninInterceptionType::
+    case WebSigninInterceptor::SigninInterceptionType::kEnterprise:
+    case WebSigninInterceptor::SigninInterceptionType::
         kEnterpriseAcceptManagement:
-    case DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
+    case WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
       histogram_base_name.append(".Enterprise");
       break;
-    case DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser:
+    case WebSigninInterceptor::SigninInterceptionType::kMultiUser:
       histogram_base_name.append(".MultiUser");
       break;
-    case DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
-    case DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
       histogram_base_name.append(".Switch");
       break;
   }
@@ -134,7 +132,7 @@
   base::UmaHistogramEnumeration(histogram_base_name + sync_suffix, result);
   // For Enterprise, slice per enterprise status for each account.
   if (bubble_parameters.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise) {
+      WebSigninInterceptor::SigninInterceptionType::kEnterprise) {
     if (bubble_parameters.intercepted_account.IsManaged()) {
       std::string histogram_name = histogram_base_name + ".NewIsEnterprise";
       base::UmaHistogramEnumeration(histogram_name, result);
@@ -168,8 +166,7 @@
 DiceWebSigninInterceptionBubbleView::DiceWebSigninInterceptionBubbleView(
     Browser* browser,
     views::View* anchor_view,
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(SigninInterceptionResult)> callback)
     : views::BubbleDialogDelegateView(anchor_view,
                                       views::BubbleBorder::TOP_RIGHT),
@@ -219,7 +216,7 @@
   GetWidget()->Show();
 }
 
-std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
 DiceWebSigninInterceptionBubbleView::GetHandle() {
   return std::make_unique<ScopedHandle>(weak_factory_.GetWeakPtr());
 }
@@ -266,11 +263,10 @@
   return GetBubbleAnchorView(browser) != nullptr;
 }
 
-std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
+std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
 DiceWebSigninInterceptorDelegate::ShowSigninInterceptionBubbleInternal(
     Browser* browser,
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(SigninInterceptionResult)> callback) {
   DCHECK(browser);
 
diff --git a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.h b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.h
index d21f19f..a8f726ba 100644
--- a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.h
+++ b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view.h
@@ -40,18 +40,16 @@
   // Warning: the bubble is closed when the handle is destroyed ; it is the
   // responsibility of the caller to keep the handle alive until the bubble
   // should be closed.
-  [[nodiscard]] static std::unique_ptr<
-      ScopedDiceWebSigninInterceptionBubbleHandle>
-  CreateBubble(Browser* browser,
-               views::View* anchor_view,
-               const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-                   bubble_parameters,
-               base::OnceCallback<void(SigninInterceptionResult)> callback);
+  [[nodiscard]] static std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
+  CreateBubble(
+      Browser* browser,
+      views::View* anchor_view,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
+      base::OnceCallback<void(SigninInterceptionResult)> callback);
 
   // Record metrics about the result of the signin interception.
   static void RecordInterceptionResult(
-      const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-          bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       Profile* profile,
       SigninInterceptionResult result);
 
@@ -85,7 +83,7 @@
 
   // Closes the bubble when `ScopedHandle` is destroyed. Does nothing if the
   // bubble has been already closed.
-  class ScopedHandle : public ScopedDiceWebSigninInterceptionBubbleHandle {
+  class ScopedHandle : public ScopedWebSigninInterceptionBubbleHandle {
    public:
     explicit ScopedHandle(
         base::WeakPtr<DiceWebSigninInterceptionBubbleView> bubble);
@@ -101,14 +99,13 @@
   DiceWebSigninInterceptionBubbleView(
       Browser* browser,
       views::View* anchor_view,
-      const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-          bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(SigninInterceptionResult)> callback);
 
   // Gets a handle on the bubble. Warning: the bubble is closed when the handle
   // is destroyed ; it is the responsibility of the caller to keep the handle
   // alive until the bubble should be closed.
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> GetHandle();
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> GetHandle();
 
   // This bubble has no native buttons. The user accepts or cancels or selects
   // Guest profile through this method, which is called by the inner web UI.
@@ -127,7 +124,7 @@
   base::WeakPtr<Browser> browser_;
   raw_ptr<Profile> profile_;
   bool accepted_ = false;
-  DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters_;
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters_;
   base::OnceCallback<void(SigninInterceptionResult)> callback_;
   raw_ptr<views::WebView> web_view_;
 
diff --git a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_browsertest.cc b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_browsertest.cc
index bcff1135..cf61a851 100644
--- a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_browsertest.cc
@@ -46,8 +46,8 @@
 
 struct TestParam {
   std::string test_suffix = "";
-  DiceWebSigninInterceptor::SigninInterceptionType interception_type =
-      DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser;
+  WebSigninInterceptor::SigninInterceptionType interception_type =
+      WebSigninInterceptor::SigninInterceptionType::kMultiUser;
   policy::EnterpriseManagementAuthority management_authority =
       policy::EnterpriseManagementAuthority::NONE;
   // Note: changes strings for kEnterprise type, otherwise adds badge on pic.
@@ -68,16 +68,14 @@
 const TestParam kTestParams[] = {
     // Common consumer user case: regular account signing in to a profile having
     // a regular account on a non-managed device.
-    {"ConsumerSimple",
-     DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+    {"ConsumerSimple", WebSigninInterceptor::SigninInterceptionType::kMultiUser,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/false,
      /*use_dark_theme=*/false,
      /*intercepted_profile_color=*/SkColors::kMagenta},
 
     // Ditto, with a different color scheme
-    {"ConsumerDark",
-     DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+    {"ConsumerDark", WebSigninInterceptor::SigninInterceptionType::kMultiUser,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/false,
      /*use_dark_theme=*/true,
@@ -86,7 +84,7 @@
     // Regular account signing in to a profile having a regular account on a
     // managed device (having policies configured locally for example).
     {"ConsumerManagedDevice",
-     DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+     WebSigninInterceptor::SigninInterceptionType::kMultiUser,
      policy::EnterpriseManagementAuthority::COMPUTER_LOCAL,
      /*is_intercepted_account_managed=*/false,
      /*use_dark_theme=*/false,
@@ -96,20 +94,20 @@
     // Regular account signing in to a profile having a managed account on a
     // non-managed device.
     {"EnterpriseSimple",
-     DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+     WebSigninInterceptor::SigninInterceptionType::kEnterprise,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/false},
 
     // Managed account signing in to a profile having a regular account on a
     // non-managed device.
     {"EnterpriseManagedIntercepted",
-     DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+     WebSigninInterceptor::SigninInterceptionType::kEnterprise,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/true},
 
     // Ditto, with a different color scheme
     {"EnterpriseManagedInterceptedDark",
-     DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+     WebSigninInterceptor::SigninInterceptionType::kEnterprise,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/true,
      /*use_dark_theme=*/true},
@@ -117,14 +115,14 @@
     // Regular account signing in to a profile having a managed account on a
     // managed device.
     {"EntepriseManagedDevice",
-     DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+     WebSigninInterceptor::SigninInterceptionType::kEnterprise,
      policy::EnterpriseManagementAuthority::CLOUD_DOMAIN,
      /*is_intercepted_account_managed=*/false},
 
     // Profile switch bubble: the account used for signing in is already
     // associated with another profile.
     {"ProfileSwitch",
-     DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
+     WebSigninInterceptor::SigninInterceptionType::kProfileSwitch,
      policy::EnterpriseManagementAuthority::NONE,
      /*is_intercepted_account_managed=*/false},
 };
@@ -206,8 +204,7 @@
   }
 
   // Generates bubble parameters for testing.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters
-  GetTestBubbleParameters() {
+  WebSigninInterceptor::Delegate::BubbleParameters GetTestBubbleParameters() {
     AccountInfo intercepted_account;
     intercepted_account.account_id =
         CoreAccountId::FromGaiaId("intercepted_ID");
@@ -224,7 +221,7 @@
     // since no test config has both accounts being managed.
     bool is_primary_account_managed =
         GetParam().interception_type ==
-            DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise &&
+            WebSigninInterceptor::SigninInterceptionType::kEnterprise &&
         !GetParam().is_intercepted_account_managed;
     AccountInfo primary_account;
     primary_account.account_id = CoreAccountId::FromGaiaId("primary_ID");
@@ -249,7 +246,7 @@
             show_managed_disclaimer};
   }
 
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> bubble_handle_;
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> bubble_handle_;
   base::test::ScopedFeatureList base_scoped_feature_list_;
 };
 
@@ -313,19 +310,18 @@
   }
 
   // Returns dummy bubble parameters for testing.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters
-  GetTestBubbleParameters() {
+  WebSigninInterceptor::Delegate::BubbleParameters GetTestBubbleParameters() {
     AccountInfo account;
     account.account_id = CoreAccountId::FromGaiaId("ID1");
     AccountInfo primary_account;
     primary_account.account_id = CoreAccountId::FromGaiaId("ID2");
-    return DiceWebSigninInterceptor::Delegate::BubbleParameters(
-        DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser, account,
+    return WebSigninInterceptor::Delegate::BubbleParameters(
+        WebSigninInterceptor::SigninInterceptionType::kMultiUser, account,
         primary_account);
   }
 
   absl::optional<SigninInterceptionResult> callback_result_;
-  std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> bubble_handle_;
+  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> bubble_handle_;
 };
 
 // Tests that the callback is called once when the bubble is closed.
@@ -494,9 +490,9 @@
  public:
   DiceWebSigninInterceptionBubbleV2BrowserTest() = default;
 
-  DiceWebSigninInterceptor::Delegate::BubbleParameters
+  WebSigninInterceptor::Delegate::BubbleParameters
   GetTestBubbleParametersForManagedProfile() {
-    DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters =
+    WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters =
         GetTestBubbleParameters();
     bubble_parameters.show_managed_disclaimer = true;
     return bubble_parameters;
diff --git a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_unittest.cc b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_unittest.cc
index c77505b4..fdb9ab2 100644
--- a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_unittest.cc
@@ -12,7 +12,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using SigninInterceptionType = DiceWebSigninInterceptor::SigninInterceptionType;
+using SigninInterceptionType = WebSigninInterceptor::SigninInterceptionType;
 
 class DiceWebSigninInterceptionBubbleViewTestBase : public testing::Test {
  public:
@@ -62,7 +62,7 @@
 
   base::HistogramTester histogram_tester;
 
-  DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
       type, enterprise_account_, personal_account_);
 
   DiceWebSigninInterceptionBubbleView::RecordInterceptionResult(
@@ -125,7 +125,7 @@
 
 TEST_F(DiceWebSigninInterceptionBubbleViewTestBase, SyncHistograms) {
   SigninInterceptionResult result = SigninInterceptionResult::kAccepted;
-  DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
       SigninInterceptionType::kEnterprise, enterprise_account_,
       personal_account_);
 
@@ -160,7 +160,7 @@
   // New account is Enterprise.
   {
     base::HistogramTester histogram_tester;
-    DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
+    WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
         SigninInterceptionType::kEnterprise, enterprise_account_,
         personal_account_);
     DiceWebSigninInterceptionBubbleView::RecordInterceptionResult(
@@ -176,7 +176,7 @@
                                          signin::ConsentLevel::kSync);
   {
     base::HistogramTester histogram_tester;
-    DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
+    WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
         SigninInterceptionType::kEnterprise, personal_account_,
         enterprise_account_);
     DiceWebSigninInterceptionBubbleView::RecordInterceptionResult(
diff --git a/chrome/browser/ui/views/profiles/profile_bubble_interactive_uitest.cc b/chrome/browser/ui/views/profiles/profile_bubble_interactive_uitest.cc
index dcbfcbf..eaf0051 100644
--- a/chrome/browser/ui/views/profiles/profile_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/profiles/profile_bubble_interactive_uitest.cc
@@ -50,14 +50,13 @@
   }
 
   // Returns dummy parameters for the interception bubble.
-  DiceWebSigninInterceptor::Delegate::BubbleParameters
-  GetTestBubbleParameters() {
+  WebSigninInterceptor::Delegate::BubbleParameters GetTestBubbleParameters() {
     AccountInfo account;
     account.account_id = CoreAccountId::FromGaiaId("ID1");
     AccountInfo primary_account;
     primary_account.account_id = CoreAccountId::FromGaiaId("ID2");
-    return DiceWebSigninInterceptor::Delegate::BubbleParameters(
-        DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser, account,
+    return WebSigninInterceptor::Delegate::BubbleParameters(
+        WebSigninInterceptor::SigninInterceptionType::kMultiUser, account,
         primary_account);
   }
 
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index 939f37fe..bbbf2e2 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -54,6 +54,7 @@
 #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_paths.h"
@@ -150,13 +151,14 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
 
+const char kPasswordManagerId[] = "chrome://password-manager/";
 const char kPasswordManagerPWAUrl[] = "chrome://password-manager/?source=pwa";
 
 std::unique_ptr<WebAppInstallInfo> CreatePasswordManagerWebAppInfo() {
   auto web_app_info = std::make_unique<WebAppInstallInfo>();
   web_app_info->start_url = GURL(kPasswordManagerPWAUrl);
   web_app_info->title = u"Password Manager";
-  web_app_info->manifest_id = "";
+  web_app_info->manifest_id = GURL(kPasswordManagerId);
   return web_app_info;
 }
 
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
index fc6c4f62..ec9d7b6 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
@@ -37,6 +37,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/tab_helper.h"
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+
 using side_panel::mojom::MethodType;
 using side_panel::mojom::PromoAction;
 using side_panel::mojom::PromoType;
@@ -201,6 +205,12 @@
         GetCompanionWebContents(browser());
     EXPECT_TRUE(companion_web_contents);
 
+    // Verify that extensions do not have access to the companion web contents.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+    CHECK_EQ(nullptr,
+             extensions::TabHelper::FromWebContents(companion_web_contents));
+#endif
+
     // Wait for the navigations in both the frames to complete.
     content::TestNavigationObserver nav_observer(companion_web_contents, 2);
     nav_observer.Wait();
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index 8addf08e..fe484544 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -56,6 +56,7 @@
            {SidePanelEntry::Id::kCustomizeChrome, "CustomizeChrome"},
            {SidePanelEntry::Id::kWebView, "WebView"},
            {SidePanelEntry::Id::kSearchCompanion, "Companion"},
+           {SidePanelEntry::Id::kShoppingInsights, "ShoppingInsights"},
            {SidePanelEntry::Id::kExtension, "Extension"}});
   auto* i = id_to_histogram_name_map.find(id);
   DCHECK(i != id_to_histogram_name_map.cend());
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_model.cc b/chrome/browser/ui/views/toolbar/chrome_labs_model.cc
index a1fa2d1..e83dfe5 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_model.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_model.cc
@@ -39,6 +39,17 @@
   static const base::NoDestructor<std::vector<LabInfo>> lab_info_([]() {
     std::vector<LabInfo> lab_info;
 
+    // ChromeRefresh2023
+    std::vector<std::u16string> chrome_refresh_variation_descriptions = {
+        l10n_util::GetStringUTF16(IDS_CHROMEREFRESH2023_WITHOUT_OMNIBOX)};
+
+    lab_info.emplace_back(LabInfo(
+        flag_descriptions::kChromeRefresh2023Id,
+        l10n_util::GetStringUTF16(IDS_CHROMEREFRESH2023_EXPERIMENT_NAME),
+        l10n_util::GetStringUTF16(IDS_CHROMEREFRESH2023_DESCRIPTION),
+        "chrome-refresh", version_info::Channel::BETA,
+        chrome_refresh_variation_descriptions));
+
     // Tab Scrolling.
     std::vector<std::u16string> tab_scrolling_variation_descriptions = {
         l10n_util::GetStringUTF16(IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH),
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc b/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
index 7038946..f248ee9 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_view_controller.cc
@@ -54,7 +54,8 @@
   // kLensRegionSearchSelected = 5,
   kWebUITabStripSelected = 6,
   // kTabSearchMediaTabsSelected = 7,
-  kMaxValue = kWebUITabStripSelected,
+  kChromeRefresh2023Selected = 8,
+  kMaxValue = kChromeRefresh2023Selected,
 };
 
 void EmitToHistogram(const std::u16string& selected_lab_state,
@@ -76,6 +77,9 @@
   };
 
   const auto get_enum = [](const std::string& internal_name) {
+    if (internal_name == flag_descriptions::kChromeRefresh2023Id) {
+      return ChromeLabsSelectedLab::kChromeRefresh2023Selected;
+    }
     if (internal_name == flag_descriptions::kScrollableTabStripFlagId)
       return ChromeLabsSelectedLab::kTabScrollingSelected;
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP) && \
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
index c33577a..58a50ef 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
@@ -142,13 +142,13 @@
             .Get(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
                                          /*component_id=*/std::string()))
             .Clone();
-    policy::PolicyMap::Entry* const existing_entry =
-        policy_map.GetMutable(policy::key::kExtensionSettings);
+    auto* existing_entry = policy_map.GetMutableValue(
+        policy::key::kExtensionSettings, base::Value::Type::DICT);
 
-    if (existing_entry && existing_entry->value(base::Value::Type::DICT)) {
+    if (existing_entry) {
       // Append to the existing policy.
-      existing_entry->value(base::Value::Type::DICT)
-          ->SetKey(policy_item_key, base::Value(std::move(policy_item_value)));
+      existing_entry->GetDict().Set(policy_item_key,
+                                    std::move(policy_item_value));
     } else {
       // Set the new policy value.
       base::Value::Dict policy_value;
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
index 29ea5f2ec..4d42913f 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
@@ -181,8 +181,8 @@
     base::RecordAction(base::UserMetricsAction("WebAppInstallCancelled"));
 
     if (iph_state_ == chrome::PwaInProductHelpState::kShown) {
-      web_app::AppId app_id = web_app::GenerateAppId(web_app_info_->manifest_id,
-                                                     web_app_info_->start_url);
+      web_app::AppId app_id =
+          web_app::GenerateAppIdFromManifestId(web_app_info_->manifest_id);
       web_app::RecordInstallIphIgnored(prefs_, app_id, base::Time::Now());
     }
   } else {
@@ -203,8 +203,8 @@
           : web_app::mojom::UserDisplayMode::kStandalone;
 
   if (iph_state_ == chrome::PwaInProductHelpState::kShown) {
-    web_app::AppId app_id = web_app::GenerateAppId(web_app_info_->manifest_id,
-                                                   web_app_info_->start_url);
+    web_app::AppId app_id =
+        web_app::GenerateAppIdFromManifestId(web_app_info_->manifest_id);
     web_app::RecordInstallIphInstalled(prefs_, app_id);
     tracker_->NotifyEvent(feature_engagement::events::kDesktopPwaInstalled);
   }
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view_browsertest.cc b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view_browsertest.cc
index 2ba48263..915dfdf 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_id.h"
@@ -36,12 +37,15 @@
     auto app_info = std::make_unique<WebAppInstallInfo>();
     app_info->title = u"Test app 2";
     app_info->start_url = GURL("https://example2.com");
+    app_info->manifest_id = GURL("https://example2.com");
     app_info->user_display_mode = web_app::mojom::UserDisplayMode::kStandalone;
     return app_info;
   }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
+  web_app::OsIntegrationManager::ScopedSuppressForTesting
+      scoped_os_suppression_;
 };
 
 IN_PROC_BROWSER_TEST_F(PWAConfirmationBubbleViewBrowserTest,
diff --git a/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc b/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
index 7df2791..884f3af8 100644
--- a/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
@@ -422,8 +422,8 @@
 void WebAppDetailedInstallDialogDelegate::OnAccept() {
   base::RecordAction(base::UserMetricsAction("WebAppDetailedInstallAccepted"));
   if (iph_state_ == chrome::PwaInProductHelpState::kShown) {
-    web_app::AppId app_id = web_app::GenerateAppId(install_info_->manifest_id,
-                                                   install_info_->start_url);
+    web_app::AppId app_id =
+        web_app::GenerateAppIdFromManifestId(install_info_->manifest_id);
     web_app::RecordInstallIphInstalled(prefs_, app_id);
     tracker_->NotifyEvent(feature_engagement::events::kDesktopPwaInstalled);
   }
@@ -437,8 +437,8 @@
 
   base::RecordAction(base::UserMetricsAction("WebAppDetailedInstallCancelled"));
   if (iph_state_ == chrome::PwaInProductHelpState::kShown && install_info_) {
-    web_app::AppId app_id = web_app::GenerateAppId(install_info_->manifest_id,
-                                                   install_info_->start_url);
+    web_app::AppId app_id =
+        web_app::GenerateAppIdFromManifestId(install_info_->manifest_id);
     web_app::RecordInstallIphIgnored(prefs_, app_id, base::Time::Now());
   }
 
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index fee03bc..7f8d903d 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -1268,8 +1268,8 @@
 
   std::string sub_url = GetSiteConfiguration(subapp).relative_url;
   // The argument of add() is a dictionary-valued dictionary:
-  // { $unhashed_app_id : {'installURL' : $installURL} }
-  // In our case, both $unhashed_app_id and $installURL are sub_url.
+  // { $manifest_id : {'installURL' : $installURL} }
+  // In our case, both $manifest_id and $installURL are sub_url.
   base::Value::Dict inner_dict;
   inner_dict.Set("installURL", sub_url);
   base::Value::Dict outer_dict;
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
index db4f2d0d..51abd45 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
@@ -48,9 +48,8 @@
 // Resolve string `path` with `origin`, and if the resulting GURL isn't same
 // origin with `origin` then return an error (for which the caller needs to
 // raise a `ReportBadMessageAndDeleteThis`).
-base::expected<std::string, std::string> ConvertPathToUrl(
-    const std::string& path,
-    const url::Origin& origin) {
+base::expected<GURL, std::string> ConvertPathToUrl(const std::string& path,
+                                                   const url::Origin& origin) {
   GURL resolved = origin.GetURL().Resolve(path);
 
   if (!origin.IsSameOriginWith(resolved)) {
@@ -58,30 +57,38 @@
         "SubAppsServiceImpl: Different origin arg to that of the calling app.");
   }
 
-  return base::ok(resolved.spec());
+  if (resolved.is_empty()) {
+    return base::unexpected("SubAppsServiceImpl: Empty url.");
+  }
+
+  if (!resolved.is_valid()) {
+    return base::unexpected("SubAppsServiceImpl: Invalid url.");
+  }
+
+  return base::ok(resolved);
 }
 
-std::string ConvertUrlToPath(const UnhashedAppId& unhashed_app_id) {
-  return GURL(unhashed_app_id).PathForRequest();
+std::string ConvertUrlToPath(const ManifestId& manifest_id) {
+  return manifest_id.PathForRequest();
 }
 
-base::expected<std::vector<std::pair<UnhashedAppId, GURL>>, std::string>
+base::expected<std::vector<std::pair<ManifestId, GURL>>, std::string>
 AddOptionsFromMojo(
     const url::Origin& origin,
     const std::vector<SubAppsServiceAddParametersPtr>& sub_apps_to_add_mojo) {
-  std::vector<std::pair<UnhashedAppId, GURL>> sub_apps;
+  std::vector<std::pair<ManifestId, GURL>> sub_apps;
   for (const auto& sub_app : sub_apps_to_add_mojo) {
-    base::expected<std::string, std::string> unhashed_app_id =
-        ConvertPathToUrl(sub_app->unhashed_app_id_path, origin);
-    if (!unhashed_app_id.has_value()) {
-      return base::unexpected(unhashed_app_id.error());
+    base::expected<ManifestId, std::string> manifest_id =
+        ConvertPathToUrl(sub_app->manifest_id_path, origin);
+    if (!manifest_id.has_value()) {
+      return base::unexpected(manifest_id.error());
     }
-    base::expected<std::string, std::string> install_url =
+    base::expected<GURL, std::string> install_url =
         ConvertPathToUrl(sub_app->install_url_path, origin);
     if (!install_url.has_value()) {
       return base::unexpected(install_url.error());
     }
-    sub_apps.emplace_back(unhashed_app_id.value(), install_url.value());
+    sub_apps.emplace_back(manifest_id.value(), install_url.value());
   }
   return sub_apps;
 }
@@ -89,9 +96,10 @@
 SubAppsServiceImpl::AddResultsMojo AddResultsToMojo(
     const SubAppsServiceImpl::AddResults& add_results) {
   SubAppsServiceImpl::AddResultsMojo add_results_mojo;
-  for (const auto& [unhashed_app_id, result_code] : add_results) {
+  for (const auto& [manifest_id, result_code] : add_results) {
+    CHECK(manifest_id.is_valid());
     add_results_mojo.emplace_back(SubAppsServiceAddResult::New(
-        ConvertUrlToPath(unhashed_app_id), result_code));
+        ConvertUrlToPath(manifest_id), result_code));
   }
   return add_results_mojo;
 }
@@ -168,12 +176,12 @@
     std::vector<SubAppsServiceAddResultPtr> result;
     for (const auto& sub_app : sub_apps_to_add) {
       result.emplace_back(SubAppsServiceAddResult::New(
-          sub_app->unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
+          sub_app->manifest_id_path, SubAppsServiceResultCode::kFailure));
     }
     return std::move(result_callback).Run(/*mojom_results=*/std::move(result));
   }
 
-  base::expected<std::vector<std::pair<UnhashedAppId, GURL>>, std::string>
+  base::expected<std::vector<std::pair<ManifestId, GURL>>, std::string>
       add_options = AddOptionsFromMojo(
           render_frame_host().GetLastCommittedOrigin(), sub_apps_to_add);
   if (!add_options.has_value()) {
@@ -215,10 +223,9 @@
   std::vector<SubAppsServiceListResultEntryPtr> sub_apps_list;
   for (const AppId& sub_app_id : registrar.GetAllSubAppIds(*parent_app_id)) {
     const WebApp* sub_app = registrar.GetAppById(sub_app_id);
-    UnhashedAppId unhashed_app_id =
-        GenerateAppIdUnhashed(sub_app->manifest_id(), sub_app->start_url());
+    ManifestId manifest_id = sub_app->manifest_id();
     sub_apps_list.push_back(SubAppsServiceListResultEntry::New(
-        ConvertUrlToPath(unhashed_app_id), sub_app->untranslated_name()));
+        ConvertUrlToPath(manifest_id), sub_app->untranslated_name()));
   }
 
   std::move(result_callback)
@@ -227,14 +234,14 @@
 }
 
 void SubAppsServiceImpl::Remove(
-    const std::vector<std::string>& unhashed_app_id_paths,
+    const std::vector<std::string>& manifest_id_paths,
     RemoveCallback result_callback) {
   WebAppProvider* provider = GetWebAppProvider(render_frame_host());
   if (!provider->on_registry_ready().is_signaled()) {
     provider->on_registry_ready().Post(
         FROM_HERE,
         base::BindOnce(&SubAppsServiceImpl::Remove,
-                       weak_ptr_factory_.GetWeakPtr(), unhashed_app_id_paths,
+                       weak_ptr_factory_.GetWeakPtr(), manifest_id_paths,
                        std::move(result_callback)));
     return;
   }
@@ -243,9 +250,9 @@
   const AppId* calling_app_id = GetAppId(render_frame_host());
   if (!calling_app_id) {
     std::vector<SubAppsServiceRemoveResultPtr> result;
-    for (const std::string& unhashed_app_id_path : unhashed_app_id_paths) {
+    for (const std::string& manifest_id_path : manifest_id_paths) {
       result.emplace_back(SubAppsServiceRemoveResult::New(
-          unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
+          manifest_id_path, SubAppsServiceResultCode::kFailure));
     }
 
     return std::move(result_callback).Run(std::move(result));
@@ -253,28 +260,27 @@
 
   auto remove_barrier_callback =
       base::BarrierCallback<SubAppsServiceRemoveResultPtr>(
-          unhashed_app_id_paths.size(), std::move(result_callback));
+          manifest_id_paths.size(), std::move(result_callback));
 
-  for (const std::string& unhashed_app_id_path : unhashed_app_id_paths) {
-    RemoveSubApp(unhashed_app_id_path, remove_barrier_callback, calling_app_id);
+  for (const std::string& manifest_id_path : manifest_id_paths) {
+    RemoveSubApp(manifest_id_path, remove_barrier_callback, calling_app_id);
   }
 }
 
 void SubAppsServiceImpl::RemoveSubApp(
-    const std::string& unhashed_app_id_path,
+    const std::string& manifest_id_path,
     base::OnceCallback<void(SubAppsServiceRemoveResultPtr)> callback,
     const AppId* calling_app_id) {
-  // Convert `unhashed_app_id_path` from path form to full URL form.
-  base::expected<std::string, std::string> unhashed_app_id_with_error =
-      ConvertPathToUrl(unhashed_app_id_path,
-                       render_frame_host().GetLastCommittedOrigin());
-  if (!unhashed_app_id_with_error.has_value()) {
+  // Convert `manifest_id_path` from path form to full URL form.
+  base::expected<GURL, std::string> manifest_id_with_error = ConvertPathToUrl(
+      manifest_id_path, render_frame_host().GetLastCommittedOrigin());
+  if (!manifest_id_with_error.has_value()) {
     // Compromised renderer, bail immediately (this call deletes *this).
-    return ReportBadMessageAndDeleteThis(unhashed_app_id_with_error.error());
+    return ReportBadMessageAndDeleteThis(manifest_id_with_error.error());
   }
 
-  const UnhashedAppId unhashed_app_id = unhashed_app_id_with_error.value();
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_app_id);
+  const ManifestId manifest_id = GURL(manifest_id_with_error.value());
+  AppId sub_app_id = GenerateAppIdFromManifestId(manifest_id);
   WebAppProvider* provider = GetWebAppProvider(render_frame_host());
   const WebApp* app = provider->registrar_unsafe().GetAppById(sub_app_id);
 
@@ -284,23 +290,22 @@
       *calling_app_id != *app->parent_app_id() ||
       !provider->registrar_unsafe().IsInstalled(sub_app_id)) {
     return std::move(callback).Run(SubAppsServiceRemoveResult::New(
-        unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
+        manifest_id_path, SubAppsServiceResultCode::kFailure));
   }
 
   provider->install_finalizer().UninstallExternalWebApp(
       sub_app_id, WebAppManagement::Type::kSubApp,
       webapps::WebappUninstallSource::kSubApp,
       base::BindOnce(
-          [](std::string unhashed_app_id_path,
+          [](std::string manifest_id_path,
              webapps::UninstallResultCode result_code) {
             SubAppsServiceResultCode result =
                 result_code == webapps::UninstallResultCode::kSuccess
                     ? SubAppsServiceResultCode::kSuccess
                     : SubAppsServiceResultCode::kFailure;
-            return SubAppsServiceRemoveResult::New(unhashed_app_id_path,
-                                                   result);
+            return SubAppsServiceRemoveResult::New(manifest_id_path, result);
           },
-          unhashed_app_id_path)
+          manifest_id_path)
           .Then(std::move(callback)));
 }
 
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl.h b/chrome/browser/ui/web_applications/sub_apps_service_impl.h
index 4917f2a..aaaa457 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl.h
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl.h
@@ -23,7 +23,7 @@
     : public content::DocumentService<blink::mojom::SubAppsService> {
  public:
   using AddResults = std::vector<
-      std::pair<UnhashedAppId, blink::mojom::SubAppsServiceResultCode>>;
+      std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode>>;
   using AddResultsMojo = std::vector<blink::mojom::SubAppsServiceAddResultPtr>;
 
   SubAppsServiceImpl(const SubAppsServiceImpl&) = delete;
@@ -41,12 +41,12 @@
       std::vector<blink::mojom::SubAppsServiceAddParametersPtr> sub_apps_to_add,
       AddCallback result_callback) override;
   void List(ListCallback result_callback) override;
-  void Remove(const std::vector<std::string>& unhashed_app_id_paths,
+  void Remove(const std::vector<std::string>& manifest_id_paths,
               RemoveCallback result_callback) override;
 
  private:
   void RemoveSubApp(
-      const UnhashedAppId& unhashed_app_id,
+      const std::string& manifest_id_path,
       base::OnceCallback<void(blink::mojom::SubAppsServiceRemoveResultPtr)>
           remove_barrier_callback,
       const AppId* calling_app_id);
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
index 14014ba..dade9d5 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
@@ -30,6 +30,8 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 
@@ -111,8 +113,7 @@
   void UninstallParentAppBySource(WebAppManagement::Type source) {
     base::test::TestFuture<void> uninstall_future;
     provider().install_finalizer().UninstallExternalWebApp(
-        parent_app_id_, source,
-        webapps::WebappUninstallSource::kParentUninstall,
+        parent_app_id_, source, webapps::WebappUninstallSource::kAppsPage,
         base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
           EXPECT_EQ(code, webapps::UninstallResultCode::kSuccess);
           uninstall_future.SetValue();
@@ -138,9 +139,9 @@
       std::vector<std::pair<std::string, std::string>> subapps) {
     // Convert params to mojo before making the call.
     std::vector<SubAppsServiceAddParametersPtr> sub_apps_mojo;
-    for (const auto& [unhashed_app_id_path, install_url_path] : subapps) {
-      sub_apps_mojo.emplace_back(SubAppsServiceAddParameters::New(
-          unhashed_app_id_path, install_url_path));
+    for (const auto& [manifest_id_path, install_url_path] : subapps) {
+      sub_apps_mojo.emplace_back(
+          SubAppsServiceAddParameters::New(manifest_id_path, install_url_path));
     }
 
     base::test::TestFuture<SubAppsServiceImpl::AddResultsMojo> future;
@@ -150,22 +151,17 @@
     // Unpack the mojo results before returning them.
     SubAppsServiceImpl::AddResults add_results;
     for (const auto& result : future.Take()) {
-      add_results.emplace_back(result->unhashed_app_id_path,
+      add_results.emplace_back(GetURLFromPath(result->manifest_id_path),
                                result->result_code);
     }
     return add_results;
   }
 
   void ExpectCallAdd(
-      base::flat_set<std::pair<std::string, SubAppsServiceResultCode>> expected,
+      base::flat_set<std::pair<ManifestId, SubAppsServiceResultCode>> expected,
       std::vector<std::pair<std::string, std::string>> subapps) {
     SubAppsServiceImpl::AddResults actual = CallAdd(subapps);
-    // We need to use a set for comparison because the ordering changes between
-    // invocations (due to embedded test server using a random port each time).
-    base::flat_set<
-        std::pair<UnhashedAppId, blink::mojom::SubAppsServiceResultCode>>
-        actual_set{actual};
-    EXPECT_EQ(expected, actual_set);
+    EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
   }
 
   // Calls the List() method on the mojo interface which is async, and waits for
@@ -180,27 +176,28 @@
   // Calls the Remove() method on the mojo interface which is async, and waits
   // for it to finish.
   RemoveResultsMojo CallRemove(
-      const std::vector<std::string>& unhashed_app_id_paths) {
+      const std::vector<std::string>& manifest_id_paths) {
     base::test::TestFuture<RemoveResultsMojo> future;
-    remote_->Remove(unhashed_app_id_paths, future.GetCallback());
+    remote_->Remove(manifest_id_paths, future.GetCallback());
     EXPECT_TRUE(future.Wait()) << "Remove did not trigger the callback.";
     return future.Take();
   }
 
   RemoveResultsMojo SingleRemoveResultMojo(
-      UnhashedAppId unhashed_app_id,
+      const std::string& manifest_id_path,
       SubAppsServiceResultCode result_code) {
     std::vector<blink::mojom::SubAppsServiceRemoveResultPtr> result;
     result.emplace_back(
-        SubAppsServiceRemoveResult::New(unhashed_app_id, result_code));
+        SubAppsServiceRemoveResult::New(manifest_id_path, result_code));
     return result;
   }
 
-  std::vector<std::pair<UnhashedAppId, SubAppsServiceResultCode>>
+  std::vector<std::pair<ManifestId, SubAppsServiceResultCode>>
   RemoveResultsToList(RemoveResultsMojo results) {
-    std::vector<std::pair<UnhashedAppId, SubAppsServiceResultCode>> list;
+    std::vector<std::pair<ManifestId, SubAppsServiceResultCode>> list;
     for (auto& result : results) {
-      list.emplace_back(result->unhashed_app_id_path, result->result_code);
+      list.emplace_back(GetURLFromPath(result->manifest_id_path),
+                        result->result_code);
     }
     return list;
   }
@@ -246,8 +243,9 @@
   InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
 
   // Verify a bunch of things for the newly installed sub-app.
   AppId sub_app_id = GenerateAppIdFromPath(kSubAppPath);
@@ -276,8 +274,9 @@
   content::WebContents* web_contents = OpenApplication(parent_app_id_);
   BindRemote(web_contents);
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
 }
 
 // Add call should fail if the parent app isn't installed.
@@ -286,8 +285,9 @@
   NavigateToParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kFailure}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kFailure}},
+      {{kSubAppPath, kSubAppPath}});
 }
 
 // Add call should fail if the call wasn't made from the context of parent app.
@@ -296,8 +296,9 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kFailure}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kFailure}},
+      {{kSubAppPath, kSubAppPath}});
 }
 
 // Verify that Add call rejects a sub-app with the wrong specified app_id.
@@ -306,8 +307,9 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppIdInvalid, SubAppsServiceResultCode::kFailure}},
-                {{kSubAppIdInvalid, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppIdInvalid), SubAppsServiceResultCode::kFailure}},
+      {{kSubAppIdInvalid, kSubAppPath}});
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
 }
 
@@ -317,8 +319,9 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPathInvalid, SubAppsServiceResultCode::kFailure}},
-                {{kSubAppPathInvalid, kSubAppPathInvalid}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppIdInvalid), SubAppsServiceResultCode::kFailure}},
+      {{kSubAppIdInvalid, kSubAppPathInvalid}});
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
 }
 
@@ -354,15 +357,17 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
   AppId sub_app_id = GenerateAppIdFromPath(kSubAppPath);
   EXPECT_EQ(
       DisplayMode::kStandalone,
       provider().registrar_unsafe().GetAppEffectiveDisplayMode(sub_app_id));
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPathMinimalUi}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPathMinimalUi}});
   EXPECT_EQ(
       DisplayMode::kStandalone,
       provider().registrar_unsafe().GetAppEffectiveDisplayMode(sub_app_id));
@@ -377,8 +382,9 @@
       profile(), "App that is already a sub app",
       GetURLFromPath(kParentAppPath), webapps::WebappInstallSource::SUB_APP);
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kFailure}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kFailure}},
+      {{kSubAppPath, kSubAppPath}});
   EXPECT_EQ(0ul, GetAllSubAppIds(app_id).size());
 }
 
@@ -394,18 +400,21 @@
 
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
   EXPECT_EQ(1ul, GetAllSubAppIds(parent_app_id_).size());
 
   // Try to add first sub app again.
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
   EXPECT_EQ(1ul, GetAllSubAppIds(parent_app_id_).size());
 
   // Add second sub app.
-  ExpectCallAdd({{kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath2, kSubAppPath2}});
   EXPECT_EQ(2ul, GetAllSubAppIds(parent_app_id_).size());
 }
 
@@ -415,12 +424,13 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath2, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath3, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath},
-                 {kSubAppPath2, kSubAppPath2},
-                 {kSubAppPath3, kSubAppPath3}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath3), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath},
+       {kSubAppPath2, kSubAppPath2},
+       {kSubAppPath3, kSubAppPath3}});
 
   EXPECT_EQ(3ul, GetAllSubAppIds(parent_app_id_).size());
 }
@@ -432,12 +442,13 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPathInvalid, SubAppsServiceResultCode::kFailure},
-                 {kSubAppPath3, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath},
-                 {kSubAppPathInvalid, kSubAppPathInvalid},
-                 {kSubAppPath3, kSubAppPath3}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPathInvalid), SubAppsServiceResultCode::kFailure},
+       {GetURLFromPath(kSubAppPath3), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath},
+       {kSubAppPathInvalid, kSubAppPathInvalid},
+       {kSubAppPath3, kSubAppPath3}});
   EXPECT_EQ(2ul, GetAllSubAppIds(parent_app_id_).size());
 }
 
@@ -461,12 +472,13 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath2, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath3, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath},
-                 {kSubAppPath2, kSubAppPath2},
-                 {kSubAppPath3, kSubAppPath3}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath3), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath},
+       {kSubAppPath2, kSubAppPath2},
+       {kSubAppPath3, kSubAppPath3}});
 
   // Verify that sub-apps are installed.
   AppId sub_app_id_1 = GenerateAppIdFromPath(kSubAppPath);
@@ -506,9 +518,10 @@
   AppId sub_app_id_1 = GenerateAppIdFromPath(kSubAppPath);
   AppId sub_app_id_2 = GenerateAppIdFromPath(kSubAppPath2);
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}, {kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}, {kSubAppPath2, kSubAppPath2}});
 
   // Verify that 2 sub-apps are installed.
   EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(sub_app_id_1));
@@ -542,16 +555,18 @@
 
   // Add another sub-app to verify standalone app install/uninstall does not
   // affect normal sub-app uninstalls.
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
   AppId sub_app_id = GenerateAppIdFromPath(kSubAppPath);
   EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(sub_app_id));
 
   // Add standalone app as sub-app.
   const WebApp* standalone_app =
       provider().registrar_unsafe().GetAppById(standalone_app_id);
-  ExpectCallAdd({{kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath2, kSubAppPath2}});
 
   // Verify that it is now installed and registered as a sub-app.
   EXPECT_EQ(parent_app_id_, standalone_app->parent_app_id());
@@ -588,12 +603,13 @@
   EXPECT_EQ(std::vector<SubAppsServiceListResultEntryPtr>{},
             result->sub_apps_list);
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath2, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath3, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath},
-                 {kSubAppPath2, kSubAppPath2},
-                 {kSubAppPath3, kSubAppPath3}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath3), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath},
+       {kSubAppPath2, kSubAppPath2},
+       {kSubAppPath3, kSubAppPath3}});
 
   result = CallList();
 
@@ -627,8 +643,9 @@
   BindRemote();
 
   // Sub-app install.
-  ExpectCallAdd({{kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath2, kSubAppPath2}});
 
   std::vector<SubAppsServiceListResultEntryPtr> expected_result;
   expected_result.emplace_back(
@@ -660,8 +677,9 @@
   NavigateToParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
 
   AppId app_id = GenerateAppIdFromPath(kSubAppPath);
   EXPECT_EQ(1ul, GetAllSubAppIds(parent_app_id_).size());
@@ -680,18 +698,19 @@
   InstallParentApp();
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess},
-                 {kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}, {kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess},
+       {GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}, {kSubAppPath2, kSubAppPath2}});
 
   EXPECT_EQ(2ul, GetAllSubAppIds(parent_app_id_).size());
 
-  std::vector<std::pair<UnhashedAppId, SubAppsServiceResultCode>>
-      expected_result;
-  expected_result.emplace_back(kSubAppPath, SubAppsServiceResultCode::kSuccess);
-  expected_result.emplace_back(kSubAppPath2,
+  std::vector<std::pair<ManifestId, SubAppsServiceResultCode>> expected_result;
+  expected_result.emplace_back(GetURLFromPath(kSubAppPath),
                                SubAppsServiceResultCode::kSuccess);
-  expected_result.emplace_back(kSubAppPath3,
+  expected_result.emplace_back(GetURLFromPath(kSubAppPath2),
+                               SubAppsServiceResultCode::kSuccess);
+  expected_result.emplace_back(GetURLFromPath(kSubAppPath3),
                                SubAppsServiceResultCode::kFailure);
 
   EXPECT_THAT(RemoveResultsToList(
@@ -700,12 +719,12 @@
 
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
 
-  UnhashedAppId unhashed_sub_app_id_1 = GetURLFromPath(kSubAppPath).spec();
-  UnhashedAppId unhashed_sub_app_id_2 = GetURLFromPath(kSubAppPath2).spec();
-  EXPECT_FALSE(
-      provider().registrar_unsafe().IsInstalled(unhashed_sub_app_id_1));
-  EXPECT_FALSE(
-      provider().registrar_unsafe().IsInstalled(unhashed_sub_app_id_2));
+  ManifestId sub_app_id_1 = GetURLFromPath(kSubAppPath);
+  ManifestId sub_app_id_2 = GetURLFromPath(kSubAppPath2);
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
+      GenerateAppIdFromManifestId(sub_app_id_1)));
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
+      GenerateAppIdFromManifestId(sub_app_id_2)));
 }
 
 // Calling remove with an empty list doesn't crash.
@@ -716,8 +735,9 @@
 
   AppId app_id = GenerateAppIdFromPath(kSubAppPath);
 
-  ExpectCallAdd({{kSubAppPath, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath, kSubAppPath}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath, kSubAppPath}});
   EXPECT_EQ(1ul, GetAllSubAppIds(parent_app_id_).size());
   EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(app_id));
 
@@ -748,8 +768,9 @@
   NavigateToPath(kSubAppPath);
   BindRemote();
 
-  ExpectCallAdd({{kSubAppPath2, SubAppsServiceResultCode::kSuccess}},
-                {{kSubAppPath2, kSubAppPath2}});
+  ExpectCallAdd(
+      {{GetURLFromPath(kSubAppPath2), SubAppsServiceResultCode::kSuccess}},
+      {{kSubAppPath2, kSubAppPath2}});
 
   AppId second_parent_app = InstallPWAFromPath(kSubAppPath3);
   NavigateToPath(kSubAppPath3);
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 129f7bb..7c6e9d5 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -203,7 +203,8 @@
     base::HistogramTester tester;
     const GURL app_url = https_server()->GetURL(
         base::StringPrintf("/web_apps/basic.html?index=%d", index++));
-    auto web_app_info = std::make_unique<WebAppInstallInfo>();
+    auto web_app_info = std::make_unique<WebAppInstallInfo>(
+        GenerateManifestIdFromStartUrlOnly(app_url));
     web_app_info->start_url = app_url;
     web_app_info->scope = app_url;
     web_app_info->display_mode = display_mode;
@@ -286,10 +287,11 @@
     const SkColor theme_color = SkColorSetA(SK_ColorBLUE, 0xF0);
     blink::mojom::Manifest manifest;
     manifest.start_url = GURL(kExampleURL);
+    manifest.id = GenerateManifestIdFromStartUrlOnly(manifest.start_url);
     manifest.scope = GURL(kExampleURL);
     manifest.has_theme_color = true;
     manifest.theme_color = theme_color;
-    auto web_app_info = std::make_unique<WebAppInstallInfo>();
+    auto web_app_info = std::make_unique<WebAppInstallInfo>(manifest.id);
     web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
                                           web_app_info.get());
 
@@ -301,7 +303,8 @@
               app_browser->app_controller()->GetThemeColor());
   }
   {
-    auto web_app_info = std::make_unique<WebAppInstallInfo>();
+    auto web_app_info = std::make_unique<WebAppInstallInfo>(
+        GenerateManifestIdFromStartUrlOnly(GURL("http://example.org/2")));
     web_app_info->start_url = GURL("http://example.org/2");
     web_app_info->scope = GURL("http://example.org/");
     web_app_info->theme_color = absl::optional<SkColor>();
@@ -316,10 +319,11 @@
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, BackgroundColor) {
   blink::mojom::Manifest manifest;
   manifest.start_url = GURL(kExampleURL);
+  manifest.id = GenerateManifestIdFromStartUrlOnly(manifest.start_url);
   manifest.scope = GURL(kExampleURL);
   manifest.has_background_color = true;
   manifest.background_color = SkColorSetA(SK_ColorBLUE, 0xF0);
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(manifest.id);
   web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
                                         web_app_info.get());
   AppId app_id = InstallWebApp(std::move(web_app_info));
@@ -354,7 +358,8 @@
 // below.
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, BackgroundColorChange) {
   const GURL app_url = GetSecureAppURL();
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
   web_app_info->start_url = app_url;
   web_app_info->scope = app_url.GetWithoutFilename();
   web_app_info->theme_color = SK_ColorWHITE;
@@ -538,7 +543,8 @@
   ThemeServiceFactory::GetForProfile(browser()->profile())
       ->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
 
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(GURL(kExampleURL)));
   web_app_info->start_url = GURL(kExampleURL);
   AppId app_id = InstallWebApp(std::move(web_app_info));
 
@@ -1734,7 +1740,8 @@
 // app window.
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ReparentDisplayBrowserApp) {
   const GURL app_url = GetSecureAppURL();
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
   web_app_info->start_url = app_url;
   web_app_info->scope = app_url.GetWithoutFilename();
   web_app_info->display_mode = DisplayMode::kBrowser;
@@ -1809,7 +1816,8 @@
       https_server()->GetURL("app.com", "/web_apps/title_appname_prefix.html");
   const std::u16string app_title = u"A Web App";
 
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
   web_app_info->start_url = app_url;
   web_app_info->scope = app_url.GetWithoutFilename();
   web_app_info->title = app_title;
@@ -1833,7 +1841,8 @@
   // empty or simple title.
   const GURL app_url = https_server()->GetURL("app.site.test", "/empty.html");
   const std::u16string app_title = u"A Web App";
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
   web_app_info->start_url = app_url;
   web_app_info->scope = app_url.GetWithoutFilename();
   web_app_info->title = app_title;
@@ -1855,7 +1864,8 @@
   const GURL app_url = GetSecureAppURL();
   const std::u16string app_title = u"A Web App";
 
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
   web_app_info->start_url = app_url;
   web_app_info->scope = app_url.GetWithoutFilename();
   web_app_info->title = app_title;
@@ -1884,7 +1894,7 @@
       embedded_test_server()->GetURL("app.site.test", "/simple.html");
   const std::u16string app_title = u"A Web App";
 
-  auto web_app_info = std::make_unique<WebAppInstallInfo>();
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(app_url);
   web_app_info->start_url = app_url;
   web_app_info->title = app_title;
   const AppId app_id = InstallWebApp(std::move(web_app_info));
@@ -2175,9 +2185,7 @@
                 /*manifest_id=*/absl::nullopt,
                 provider->registrar_unsafe().GetAppStartUrl(app_id)),
             app_id);
-  EXPECT_EQ(app->start_url().spec().substr(
-                app->start_url().DeprecatedGetOriginAsURL().spec().size()),
-            app->manifest_id());
+  EXPECT_EQ(app->start_url(), app->manifest_id());
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ManifestId, ManifestIdSpecified) {
@@ -2190,8 +2198,7 @@
   auto* provider = WebAppProvider::GetForTest(profile());
   auto* app = provider->registrar_unsafe().GetAppById(app_id);
 
-  EXPECT_EQ(web_app::GenerateAppId(app->manifest_id(), app->start_url()),
-            app_id);
+  EXPECT_EQ(web_app::GenerateAppIdFromManifestId(app->manifest_id()), app_id);
   EXPECT_NE(
       web_app::GenerateAppId(/*manifest_id=*/absl::nullopt, app->start_url()),
       app_id);
diff --git a/chrome/browser/ui/webui/management/management_ui_browsertest.cc b/chrome/browser/ui/webui/management/management_ui_browsertest.cc
index 4e79f3a..1406b765 100644
--- a/chrome/browser/ui/webui/management/management_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_browsertest.cc
@@ -61,7 +61,8 @@
 };
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-IN_PROC_BROWSER_TEST_F(ManagementUITest, ManagementStateChange) {
+// TODO(crbug.com/1443363): flaky.
+IN_PROC_BROWSER_TEST_F(ManagementUITest, DISABLED_ManagementStateChange) {
   profile_policy_connector()->OverrideIsManagedForTesting(false);
   ASSERT_TRUE(
       ui_test_utils::NavigateToURL(browser(), GURL("chrome://management")));
diff --git a/chrome/browser/ui/webui/nacl_ui.cc b/chrome/browser/ui/webui/nacl_ui.cc
index ea8b7ca..0125ddc 100644
--- a/chrome/browser/ui/webui/nacl_ui.cc
+++ b/chrome/browser/ui/webui/nacl_ui.cc
@@ -19,6 +19,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/user_metrics.h"
 #include "base/path_service.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
@@ -187,10 +188,11 @@
 
 void NaClDomHandler::AddOperatingSystemInfo(base::Value::List* list) {
   // Obtain the Chrome version info.
-  AddPair(list, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
-          ASCIIToUTF16(
-              version_info::GetVersionNumber() + " (" +
-              chrome::GetChannelName(chrome::WithExtendedStable(true)) + ")"));
+  AddPair(
+      list, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
+      ASCIIToUTF16(base::StrCat(
+          {version_info::GetVersionNumber(), " (",
+           chrome::GetChannelName(chrome::WithExtendedStable(true)), ")"})));
 
   // OS version information.
   // TODO(jvoung): refactor this to share the extra windows labeling
diff --git a/chrome/browser/ui/webui/settings/OWNERS b/chrome/browser/ui/webui/settings/OWNERS
index b2e7e60..8c3434dc1 100644
--- a/chrome/browser/ui/webui/settings/OWNERS
+++ b/chrome/browser/ui/webui/settings/OWNERS
@@ -8,7 +8,7 @@
 per-file privacy_sandbox_handler*=file://components/privacy_sandbox/OWNERS
 per-file *site_settings*=msramek@chromium.org
 per-file *site_settings*=sauski@google.com
-per-file site_settings_permissions_handler*=rainhard@chromium.org,sideyilmaz@chromium.org
+per-file safety_hub_handler*=sideyilmaz@chromium.org,rainhard@chromium.org
 per-file safe_browsing_handler*=msramek@chromium.org
 per-file safe_browsing_handler*=sauski@google.com
 per-file safety_check_handler*=andzaytsev@google.com,rainhard@chromium.org,sideyilmaz@chromium.org
diff --git a/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc b/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
index 514c4d3..7b5d64addc 100644
--- a/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
@@ -128,9 +128,7 @@
 
 // Check that trying to change recovery with an invalidated auth session shows
 // the password prompt again.
-// TODO(crbug.com/1436858): Re-enable this test
-IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithFeature,
-                       DISABLED_DestroyedSession) {
+IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithFeature, DestroyedSession) {
   mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
       OpenLockScreenSettingsAndAuthenticate();
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
index 05c447d3..f4386175 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
@@ -319,3 +319,25 @@
 // Kerberos section.
 const string kKerberosSectionPath = "kerberos";
 const string kKerberosAccountsV2SubpagePath = "kerberos/kerberosAccounts";
+
+// The properties should match the top-level section paths defined above
+// Keep alphabetized
+struct OsPageAvailability {
+  bool apps;
+  bool bluetooth;
+  bool crostini;
+  bool dateTime;
+  bool device;
+  bool files;
+  bool internet;
+  bool kerberos;
+  bool multidevice;
+  bool osAccessibility;
+  bool osLanguages;
+  bool osPeople;
+  bool osPrinting;
+  bool osPrivacy;
+  bool osReset;
+  bool osSearch;
+  bool personalization;
+};
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc b/chrome/browser/ui/webui/settings/safety_hub_handler.cc
similarity index 78%
rename from chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
rename to chrome/browser/ui/webui/settings/safety_hub_handler.cc
index cfe16345..3e893d9 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_hub_handler.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 "chrome/browser/ui/webui/settings/site_settings_permissions_handler.h"
+#include "chrome/browser/ui/webui/settings/safety_hub_handler.h"
 
 #include "base/check.h"
 #include "base/json/values_util.h"
@@ -63,11 +63,10 @@
 }
 }  // namespace
 
-SiteSettingsPermissionsHandler::SiteSettingsPermissionsHandler(Profile* profile)
-    : profile_(profile) {}
-SiteSettingsPermissionsHandler::~SiteSettingsPermissionsHandler() = default;
+SafetyHubHandler::SafetyHubHandler(Profile* profile) : profile_(profile) {}
+SafetyHubHandler::~SafetyHubHandler() = default;
 
-void SiteSettingsPermissionsHandler::HandleGetRevokedUnusedSitePermissionsList(
+void SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList(
     const base::Value::List& args) {
   AllowJavascript();
 
@@ -79,7 +78,7 @@
   ResolveJavascriptCallback(callback_id, base::Value(std::move(result)));
 }
 
-void SiteSettingsPermissionsHandler::HandleAllowPermissionsAgainForUnusedSite(
+void SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite(
     const base::Value::List& args) {
   CHECK_EQ(1U, args.size());
   CHECK(args[0].is_string());
@@ -94,9 +93,8 @@
   SendUnusedSitePermissionsReviewList();
 }
 
-void SiteSettingsPermissionsHandler::
-    HandleUndoAllowPermissionsAgainForUnusedSite(
-        const base::Value::List& args) {
+void SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite(
+    const base::Value::List& args) {
   CHECK_EQ(1U, args.size());
   CHECK(args[0].is_dict());
 
@@ -109,9 +107,8 @@
   SendUnusedSitePermissionsReviewList();
 }
 
-void SiteSettingsPermissionsHandler::
-    HandleAcknowledgeRevokedUnusedSitePermissionsList(
-        const base::Value::List& args) {
+void SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList(
+    const base::Value::List& args) {
   permissions::UnusedSitePermissionsService* service =
       UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
 
@@ -119,9 +116,8 @@
   SendUnusedSitePermissionsReviewList();
 }
 
-void SiteSettingsPermissionsHandler::
-    HandleUndoAcknowledgeRevokedUnusedSitePermissionsList(
-        const base::Value::List& args) {
+void SafetyHubHandler::HandleUndoAcknowledgeRevokedUnusedSitePermissionsList(
+    const base::Value::List& args) {
   CHECK_EQ(1U, args.size());
   CHECK(args[0].is_list());
 
@@ -141,8 +137,7 @@
   SendUnusedSitePermissionsReviewList();
 }
 
-base::Value::List
-SiteSettingsPermissionsHandler::PopulateUnusedSitePermissionsData() {
+base::Value::List SafetyHubHandler::PopulateUnusedSitePermissionsData() {
   base::Value::List result;
   if (!base::FeatureList::IsEnabled(
           content_settings::features::kSafetyCheckUnusedSitePermissions)) {
@@ -187,38 +182,38 @@
   return result;
 }
 
-void SiteSettingsPermissionsHandler::RegisterMessages() {
+void SafetyHubHandler::RegisterMessages() {
   // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and
   // won't release ownership until destruction.
   web_ui()->RegisterMessageCallback(
       "getRevokedUnusedSitePermissionsList",
-      base::BindRepeating(&SiteSettingsPermissionsHandler::
-                              HandleGetRevokedUnusedSitePermissionsList,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList,
+          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "allowPermissionsAgainForUnusedSite",
-      base::BindRepeating(&SiteSettingsPermissionsHandler::
-                              HandleAllowPermissionsAgainForUnusedSite,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite,
+          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "undoAllowPermissionsAgainForUnusedSite",
-      base::BindRepeating(&SiteSettingsPermissionsHandler::
-                              HandleUndoAllowPermissionsAgainForUnusedSite,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite,
+          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "acknowledgeRevokedUnusedSitePermissionsList",
-      base::BindRepeating(&SiteSettingsPermissionsHandler::
-                              HandleAcknowledgeRevokedUnusedSitePermissionsList,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList,
+          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "undoAcknowledgeRevokedUnusedSitePermissionsList",
       base::BindRepeating(
-          &SiteSettingsPermissionsHandler::
+          &SafetyHubHandler::
               HandleUndoAcknowledgeRevokedUnusedSitePermissionsList,
           base::Unretained(this)));
 }
 
-void SiteSettingsPermissionsHandler::SendUnusedSitePermissionsReviewList() {
+void SafetyHubHandler::SendUnusedSitePermissionsReviewList() {
   // Notify observers that the unused site permission review list could have
   // changed. Note that the list is not guaranteed to have changed. In places
   // where determining whether the list has changed is cause for performance
@@ -227,6 +222,6 @@
                     PopulateUnusedSitePermissionsData());
 }
 
-void SiteSettingsPermissionsHandler::OnJavascriptAllowed() {}
+void SafetyHubHandler::OnJavascriptAllowed() {}
 
-void SiteSettingsPermissionsHandler::OnJavascriptDisallowed() {}
+void SafetyHubHandler::OnJavascriptDisallowed() {}
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h b/chrome/browser/ui/webui/settings/safety_hub_handler.h
similarity index 79%
rename from chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
rename to chrome/browser/ui/webui/settings/safety_hub_handler.h
index e4cfe91d..a4455f94 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
+++ b/chrome/browser/ui/webui/settings/safety_hub_handler.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 CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_HUB_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_HUB_HANDLER_H_
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
@@ -18,19 +18,19 @@
  * settings page.
  */
 
-class SiteSettingsPermissionsHandler : public settings::SettingsPageUIHandler {
+class SafetyHubHandler : public settings::SettingsPageUIHandler {
  public:
-  explicit SiteSettingsPermissionsHandler(Profile* profile);
+  explicit SafetyHubHandler(Profile* profile);
 
-  ~SiteSettingsPermissionsHandler() override;
+  ~SafetyHubHandler() override;
 
  private:
-  friend class SiteSettingsPermissionsHandlerTest;
-  FRIEND_TEST_ALL_PREFIXES(SiteSettingsPermissionsHandlerTest,
+  friend class SafetyHubHandlerTest;
+  FRIEND_TEST_ALL_PREFIXES(SafetyHubHandlerTest,
                            PopulateUnusedSitePermissionsData);
-  FRIEND_TEST_ALL_PREFIXES(SiteSettingsPermissionsHandlerTest,
+  FRIEND_TEST_ALL_PREFIXES(SafetyHubHandlerTest,
                            HandleAllowPermissionsAgainForUnusedSite);
-  FRIEND_TEST_ALL_PREFIXES(SiteSettingsPermissionsHandlerTest,
+  FRIEND_TEST_ALL_PREFIXES(SafetyHubHandlerTest,
                            HandleAcknowledgeRevokedUnusedSitePermissionsList);
 
   // SettingsPageUIHandler implementation.
@@ -75,4 +75,4 @@
   const raw_ptr<Profile> profile_;
 };
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SAFETY_HUB_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc b/chrome/browser/ui/webui/settings/safety_hub_handler_unittest.cc
similarity index 91%
rename from chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
rename to chrome/browser/ui/webui/settings/safety_hub_handler_unittest.cc
index b134e56..3f645cc 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/safety_hub_handler_unittest.cc
@@ -10,8 +10,8 @@
 #include "base/time/clock.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/ui/webui/settings/safety_hub_handler.h"
 #include "chrome/browser/ui/webui/settings/site_settings_helper.h"
-#include "chrome/browser/ui/webui/settings/site_settings_permissions_handler.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
@@ -31,9 +31,9 @@
 constexpr ContentSettingsType kUnusedPermission =
     ContentSettingsType::GEOLOCATION;
 
-class SiteSettingsPermissionsHandlerTest : public testing::Test {
+class SafetyHubHandlerTest : public testing::Test {
  public:
-  SiteSettingsPermissionsHandlerTest() {
+  SafetyHubHandlerTest() {
     feature_list_.InitAndEnableFeature(
         content_settings::features::kSafetyCheckUnusedSitePermissions);
   }
@@ -54,7 +54,7 @@
     hcsm_ = HostContentSettingsMapFactory::GetForProfile(profile());
     hcsm_->SetClockForTesting(&clock_);
 
-    handler_ = std::make_unique<SiteSettingsPermissionsHandler>(profile());
+    handler_ = std::make_unique<SafetyHubHandler>(profile());
     handler()->set_web_ui(web_ui());
     handler()->AllowJavascript();
 
@@ -103,21 +103,21 @@
 
   TestingProfile* profile() { return profile_.get(); }
   content::TestWebUI* web_ui() { return &web_ui_; }
-  SiteSettingsPermissionsHandler* handler() { return handler_.get(); }
+  SafetyHubHandler* handler() { return handler_.get(); }
   HostContentSettingsMap* hcsm() { return hcsm_.get(); }
   base::SimpleTestClock* clock() { return &clock_; }
 
  private:
   base::test::ScopedFeatureList feature_list_;
   content::BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<SiteSettingsPermissionsHandler> handler_;
+  std::unique_ptr<SafetyHubHandler> handler_;
   std::unique_ptr<TestingProfile> profile_;
   content::TestWebUI web_ui_;
   scoped_refptr<HostContentSettingsMap> hcsm_;
   base::SimpleTestClock clock_;
 };
 
-TEST_F(SiteSettingsPermissionsHandlerTest, PopulateUnusedSitePermissionsData) {
+TEST_F(SafetyHubHandlerTest, PopulateUnusedSitePermissionsData) {
   // Add GEOLOCATION setting for url but do not add to revoked list.
   const content_settings::ContentSettingConstraints constraint{
       .track_last_visit_for_autoexpiration = true};
@@ -135,8 +135,7 @@
                 site_settings::kOrigin)));
 }
 
-TEST_F(SiteSettingsPermissionsHandlerTest,
-       HandleAllowPermissionsAgainForUnusedSite) {
+TEST_F(SafetyHubHandlerTest, HandleAllowPermissionsAgainForUnusedSite) {
   base::Value::List initial_unused_site_permissions =
       handler()->PopulateUnusedSitePermissionsData();
   ExpectRevokedPermission();
@@ -164,7 +163,7 @@
   ExpectRevokedPermission();
 }
 
-TEST_F(SiteSettingsPermissionsHandlerTest,
+TEST_F(SafetyHubHandlerTest,
        HandleAcknowledgeRevokedUnusedSitePermissionsList) {
   const auto& revoked_permissions_before =
       handler()->PopulateUnusedSitePermissionsData();
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 4fd1d0a..feda6cc 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -53,6 +53,7 @@
 #include "chrome/browser/ui/webui/settings/protocol_handlers_handler.h"
 #include "chrome/browser/ui/webui/settings/reset_settings_handler.h"
 #include "chrome/browser/ui/webui/settings/safety_check_handler.h"
+#include "chrome/browser/ui/webui/settings/safety_hub_handler.h"
 #include "chrome/browser/ui/webui/settings/search_engines_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_localized_strings_provider.h"
@@ -63,7 +64,6 @@
 #include "chrome/browser/ui/webui/settings/settings_startup_pages_handler.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/settings/site_settings_handler.h"
-#include "chrome/browser/ui/webui/settings/site_settings_permissions_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
@@ -208,8 +208,7 @@
   AddSettingsPageUIHandler(
       std::make_unique<ClearBrowsingDataHandler>(web_ui, profile));
   AddSettingsPageUIHandler(std::make_unique<SafetyCheckHandler>());
-  AddSettingsPageUIHandler(
-      std::make_unique<SiteSettingsPermissionsHandler>(profile));
+  AddSettingsPageUIHandler(std::make_unique<SafetyHubHandler>(profile));
   AddSettingsPageUIHandler(std::make_unique<DownloadsHandler>(profile));
   AddSettingsPageUIHandler(std::make_unique<ExtensionControlHandler>());
   AddSettingsPageUIHandler(std::make_unique<FontHandler>(profile));
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
index 0605a71e..b8ca8b3 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
@@ -34,6 +34,7 @@
 #include "ui/base/ui_base_features.h"
 #include "ui/base/webui/web_ui_util.h"
 #include "ui/views/style/platform_style.h"
+#include "ui/webui/color_change_listener/color_change_handler.h"
 
 ReadingListUI::ReadingListUI(content::WebUI* web_ui)
     : ui::MojoBubbleWebUIController(web_ui),
@@ -102,6 +103,13 @@
 }
 
 void ReadingListUI::BindInterface(
+    mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
+        pending_receiver) {
+  color_provider_handler_ = std::make_unique<ui::ColorChangeHandler>(
+      web_ui()->GetWebContents(), std::move(pending_receiver));
+}
+
+void ReadingListUI::BindInterface(
     mojo::PendingReceiver<help_bubble::mojom::HelpBubbleHandlerFactory>
         pending_receiver) {
   if (help_bubble_handler_factory_receiver_.is_bound())
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.h b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.h
index 49d9e1b..86594249 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.h
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.h
@@ -14,10 +14,15 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ui/webui/mojo_bubble_web_ui_controller.h"
+#include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
 #include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom.h"
 
 class ReadingListPageHandler;
 
+namespace ui {
+class ColorChangeHandler;
+}
+
 class ReadingListUI : public ui::MojoBubbleWebUIController,
                       public reading_list::mojom::PageHandlerFactory,
                       public help_bubble::mojom::HelpBubbleHandlerFactory {
@@ -27,6 +32,10 @@
   ReadingListUI& operator=(const ReadingListUI&) = delete;
   ~ReadingListUI() override;
 
+  void BindInterface(
+      mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
+          pending_receiver);
+
   // Instantiates the implementor of the mojom::PageHandlerFactory mojo
   // interface passing the pending receiver that will be internally bound.
   void BindInterface(
@@ -54,6 +63,7 @@
   mojo::Receiver<reading_list::mojom::PageHandlerFactory>
       page_factory_receiver_{this};
 
+  std::unique_ptr<ui::ColorChangeHandler> color_provider_handler_;
   std::unique_ptr<user_education::HelpBubbleHandler> help_bubble_handler_;
   mojo::Receiver<help_bubble::mojom::HelpBubbleHandlerFactory>
       help_bubble_handler_factory_receiver_{this};
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
index e3c44a2..4ad8177 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.cc
@@ -56,8 +56,7 @@
 }  // namespace
 
 DiceWebSigninInterceptHandler::DiceWebSigninInterceptHandler(
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(int)> show_widget_with_height_callback,
     base::OnceCallback<void(SigninInterceptionUserChoice)> completion_callback)
     : bubble_parameters_(bubble_parameters),
@@ -250,7 +249,7 @@
 
 std::string DiceWebSigninInterceptHandler::GetHeaderText() {
   if (bubble_parameters_.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
     return intercepted_account().given_name;
   }
 
@@ -258,7 +257,7 @@
     return std::string();
 
   if (bubble_parameters_.interception_type ==
-          DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise &&
+          WebSigninInterceptor::SigninInterceptionType::kEnterprise &&
       IsManaged(intercepted_account())) {
     return intercepted_account().hosted_domain;
   }
@@ -268,7 +267,7 @@
 
 std::string DiceWebSigninInterceptHandler::GetBodyTitle() {
   if (bubble_parameters_.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
     return l10n_util::GetStringUTF8(
         IDS_SIGNIN_DICE_WEB_INTERCEPT_SWITCH_BUBBLE_TITLE);
   }
@@ -279,13 +278,18 @@
 
 std::string DiceWebSigninInterceptHandler::GetBodyText() {
   if (bubble_parameters_.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
     return l10n_util::GetStringUTF8(
         IDS_SIGNIN_DICE_WEB_INTERCEPT_SWITCH_BUBBLE_DESC);
   }
 
   switch (bubble_parameters_.interception_type) {
-    case DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise:
+    case WebSigninInterceptor::SigninInterceptionType::kEnterprise:
+      if (intercepted_account().IsEmpty()) {
+        return l10n_util::GetStringUTF8(
+            IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_BY_TOKEN);
+      }
+
       return ShouldShowManagedDeviceVersion()
                  ? l10n_util::GetStringFUTF8(
                        IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC_MANAGED_DEVICE,
@@ -293,7 +297,7 @@
                  : l10n_util::GetStringFUTF8(
                        IDS_SIGNIN_DICE_WEB_INTERCEPT_ENTERPRISE_BUBBLE_DESC,
                        base::UTF8ToUTF16(primary_account().email));
-    case DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser:
+    case WebSigninInterceptor::SigninInterceptionType::kMultiUser:
       return ShouldShowManagedDeviceVersion()
                  ? l10n_util::GetStringFUTF8(
                        IDS_SIGNIN_DICE_WEB_INTERCEPT_CONSUMER_BUBBLE_DESC_MANAGED_DEVICE,
@@ -302,12 +306,12 @@
                  : l10n_util::GetStringFUTF8(
                        IDS_SIGNIN_DICE_WEB_INTERCEPT_CONSUMER_BUBBLE_DESC,
                        base::UTF8ToUTF16(primary_account().given_name));
-    case DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
       // Already handled.
-    case DiceWebSigninInterceptor::SigninInterceptionType::
+    case WebSigninInterceptor::SigninInterceptionType::
         kEnterpriseAcceptManagement:
-    case DiceWebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
-    case DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
+    case WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
+    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
       NOTREACHED() << "This interception type is not handled by a bubble";
       return std::string();
   }
@@ -315,7 +319,7 @@
 
 std::string DiceWebSigninInterceptHandler::GetConfirmButtonLabel() {
   if (bubble_parameters_.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
     return l10n_util::GetStringUTF8(
         IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CONFIRM_SWITCH_BUTTON_LABEL);
   }
@@ -327,7 +331,7 @@
 std::string DiceWebSigninInterceptHandler::GetCancelButtonLabel() {
   return l10n_util::GetStringUTF8(
       bubble_parameters_.interception_type ==
-              DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch
+              WebSigninInterceptor::SigninInterceptionType::kProfileSwitch
           ? IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CANCEL_SWITCH_BUTTON_LABEL
           : IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CANCEL_BUTTON_LABEL);
 }
@@ -338,6 +342,13 @@
           GURL(chrome::kSigninInterceptManagedDisclaimerLearnMoreURL),
           g_browser_process->GetApplicationLocale())
           .spec();
+
+  if (intercepted_account().IsEmpty()) {
+    return l10n_util::GetStringFUTF8(
+        IDS_SIGNIN_DICE_WEB_INTERCEPT_MANAGED_DISCLAIMER,
+        base::ASCIIToUTF16(learn_more_url));
+  }
+
   std::string manager_domain = intercepted_account().IsManaged()
                                    ? intercepted_account().hosted_domain
                                    : std::string();
@@ -357,8 +368,9 @@
 
 bool DiceWebSigninInterceptHandler::GetShouldUseV2Design() {
   if (bubble_parameters_.interception_type ==
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch)
+      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
     return false;
+  }
 
   return base::FeatureList::IsEnabled(kSigninInterceptBubbleV2);
 }
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
index 810eca9..3b88586 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler.h
@@ -21,8 +21,7 @@
                                       public signin::IdentityManager::Observer {
  public:
   DiceWebSigninInterceptHandler(
-      const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-          bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(int)> show_widget_with_height_callback,
       base::OnceCallback<void(SigninInterceptionUserChoice)>
           completion_callback);
@@ -73,7 +72,7 @@
   base::ScopedObservation<signin::IdentityManager,
                           signin::IdentityManager::Observer>
       identity_observation_{this};
-  DiceWebSigninInterceptor::Delegate::BubbleParameters bubble_parameters_;
+  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters_;
 
   base::OnceCallback<void(int)> show_widget_with_height_callback_;
   base::OnceCallback<void(SigninInterceptionUserChoice)> completion_callback_;
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
index 5340148..7bf3cd3 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_handler_unittest.cc
@@ -45,7 +45,7 @@
 using ExpectedStringGenerator = base::RepeatingCallback<BubbleStrings()>;
 
 struct TestParam {
-  DiceWebSigninInterceptor::SigninInterceptionType interception_type;
+  WebSigninInterceptor::SigninInterceptionType interception_type;
   policy::EnterpriseManagementAuthority management_authority;
   ExpectedStringGenerator expected_strings;
   ExpectedStringGenerator expected_strings_v2;
@@ -102,7 +102,7 @@
 // Permutations of supported bubbles.
 const TestParam kTestParams[] = {
     {
-        DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+        WebSigninInterceptor::SigninInterceptionType::kMultiUser,
         policy::EnterpriseManagementAuthority::NONE,
         /*expected_strings=*/base::BindRepeating([]() {
           return BubbleStrings{
@@ -125,7 +125,7 @@
         /*expected_strings_v2=*/common_v2_strings_generator,
     },
     {
-        DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+        WebSigninInterceptor::SigninInterceptionType::kMultiUser,
         policy::EnterpriseManagementAuthority::CLOUD_DOMAIN,
         /*expected_strings=*/base::BindRepeating([]() {
           return BubbleStrings{
@@ -167,7 +167,7 @@
         }),
     },
     {
-        DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+        WebSigninInterceptor::SigninInterceptionType::kEnterprise,
         policy::EnterpriseManagementAuthority::NONE,
         /*expected_strings=*/base::BindRepeating([]() {
           return BubbleStrings{
@@ -207,7 +207,7 @@
         }),
     },
     {
-        DiceWebSigninInterceptor::SigninInterceptionType::kEnterprise,
+        WebSigninInterceptor::SigninInterceptionType::kEnterprise,
         policy::EnterpriseManagementAuthority::CLOUD_DOMAIN,
         /*expected_strings=*/base::BindRepeating([]() {
           return BubbleStrings{
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
index 3a9a26cb..193cecd 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
@@ -27,7 +27,7 @@
 
 // Helper to create parameters used for testing, when loading the intercept
 // bubble directly with the `debug` query param set.
-DiceWebSigninInterceptor::Delegate::BubbleParameters
+WebSigninInterceptor::Delegate::BubbleParameters
 CreateSampleBubbleParameters() {
   // Looks like the transparent checkerboard.
   std::string small_png =
@@ -51,8 +51,8 @@
   primary_account.picture_url = small_png;
   primary_account.hosted_domain = kNoHostedDomainFound;
 
-  return DiceWebSigninInterceptor::Delegate::BubbleParameters(
-      DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
+  return WebSigninInterceptor::Delegate::BubbleParameters(
+      WebSigninInterceptor::SigninInterceptionType::kMultiUser,
       intercepted_account, primary_account, SK_ColorMAGENTA);
 }
 
@@ -105,8 +105,7 @@
 DiceWebSigninInterceptUI::~DiceWebSigninInterceptUI() = default;
 
 void DiceWebSigninInterceptUI::Initialize(
-    const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-        bubble_parameters,
+    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
     base::OnceCallback<void(int)> show_widget_with_height_callback,
     base::OnceCallback<void(SigninInterceptionUserChoice)>
         completion_callback) {
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.h b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.h
index 03f0fd0..cd8cfb0 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.h
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.h
@@ -8,7 +8,7 @@
 #include "content/public/browser/web_ui_controller.h"
 
 #include "base/functional/callback.h"
-#include "chrome/browser/signin/dice_web_signin_interceptor.h"
+#include "chrome/browser/signin/web_signin_interceptor.h"
 
 namespace content {
 class WebUI;
@@ -24,8 +24,7 @@
 
   // Initializes the DiceWebSigninInterceptUI.
   void Initialize(
-      const DiceWebSigninInterceptor::Delegate::BubbleParameters&
-          bubble_parameters,
+      const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(int)> show_widget_with_height_callback,
       base::OnceCallback<void(SigninInterceptionUserChoice)>
           completion_callback);
diff --git a/chrome/browser/ui/window_sizer/window_sizer_chromeos_unittest.cc b/chrome/browser/ui/window_sizer/window_sizer_chromeos_unittest.cc
index 75ac7d02..7311541 100644
--- a/chrome/browser/ui/window_sizer/window_sizer_chromeos_unittest.cc
+++ b/chrome/browser/ui/window_sizer/window_sizer_chromeos_unittest.cc
@@ -747,7 +747,7 @@
 // in that this uses real ash shell implementations + StateProvider
 // rather than mocks.
 TEST_F(WindowSizerChromeOSTest, DefaultBoundsInTargetDisplay) {
-  UpdateDisplay("500x500,600x600");
+  UpdateDisplay("500x400,600x500");
 
   {
     // By default windows are placed on the primary display.
diff --git a/chrome/browser/update_client/chrome_update_query_params_delegate.cc b/chrome/browser/update_client/chrome_update_query_params_delegate.cc
index 4f48863a..3947ede3 100644
--- a/chrome/browser/update_client/chrome_update_query_params_delegate.cc
+++ b/chrome/browser/update_client/chrome_update_query_params_delegate.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/update_client/chrome_update_query_params_delegate.h"
 
 #include "base/lazy_instance.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/strcat.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/channel_info.h"
 #include "components/version_info/version_info.h"
@@ -30,10 +30,10 @@
 }
 
 std::string ChromeUpdateQueryParamsDelegate::GetExtraParams() {
-  return base::StringPrintf(
-      "&prodchannel=%s&prodversion=%s&lang=%s",
-      chrome::GetChannelName(chrome::WithExtendedStable(true)).c_str(),
-      version_info::GetVersionNumber().c_str(), GetLang());
+  return base::StrCat({"&prodchannel=",
+                       chrome::GetChannelName(chrome::WithExtendedStable(true)),
+                       "&prodversion=", version_info::GetVersionNumber(),
+                       "&lang=", GetLang()});
 }
 
 // static
diff --git a/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc b/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
index 83faf8e4..e55009694 100644
--- a/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
+++ b/chrome/browser/update_client/chrome_update_query_params_delegate_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <string>
 
-#include "base/strings/stringprintf.h"
+#include "base/strings/strcat.h"
 #include "base/system/sys_info.h"
 #include "chrome/browser/update_client/chrome_update_query_params_delegate.h"
 #include "chrome/common/channel_info.h"
@@ -12,8 +12,6 @@
 #include "components/version_info/version_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::StringPrintf;
-
 namespace {
 
 bool Contains(const std::string& source, const std::string& target) {
@@ -27,29 +25,27 @@
 
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("os=%s", update_client::UpdateQueryParams::GetOS())));
+      base::StrCat({"os=", update_client::UpdateQueryParams::GetOS()})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("arch=%s", update_client::UpdateQueryParams::GetArch())));
+      base::StrCat({"arch=", update_client::UpdateQueryParams::GetArch()})));
+  EXPECT_TRUE(Contains(
+      params, base::StrCat({"os_arch=",
+                            base::SysInfo().OperatingSystemArchitecture()})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("os_arch=%s",
-                   base::SysInfo().OperatingSystemArchitecture().c_str())));
-  EXPECT_TRUE(Contains(
-      params, StringPrintf(
-                  "prod=%s",
-                  update_client::UpdateQueryParams::GetProdIdString(prod_id))));
+      base::StrCat({"prod=", update_client::UpdateQueryParams::GetProdIdString(
+                                 prod_id)})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf(
-          "prodchannel=%s",
-          chrome::GetChannelName(chrome::WithExtendedStable(true)).c_str())));
-  EXPECT_TRUE(
-      Contains(params, StringPrintf("prodversion=%s",
-                                    version_info::GetVersionNumber().c_str())));
+      base::StrCat({"prodchannel=", chrome::GetChannelName(
+                                        chrome::WithExtendedStable(true))})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("lang=%s", ChromeUpdateQueryParamsDelegate::GetLang())));
+      base::StrCat({"prodversion=", version_info::GetVersionNumber()})));
+  EXPECT_TRUE(Contains(
+      params,
+      base::StrCat({"lang=", ChromeUpdateQueryParamsDelegate::GetLang()})));
 }
 
 TEST(ChromeUpdateQueryParamsDelegateTest, GetParams) {
diff --git a/chrome/browser/web_applications/commands/externally_managed_install_command.cc b/chrome/browser/web_applications/commands/externally_managed_install_command.cc
index 5fc1897..45dcae5e 100644
--- a/chrome/browser/web_applications/commands/externally_managed_install_command.cc
+++ b/chrome/browser/web_applications/commands/externally_managed_install_command.cc
@@ -177,7 +177,7 @@
         web_contents_->GetLastCommittedURL(), *web_app_info_);
   }
 
-  app_id_ = GenerateAppId(web_app_info_->manifest_id, web_app_info_->start_url);
+  app_id_ = GenerateAppIdFromManifestId(web_app_info_->manifest_id);
 
   // If the manifest specified icons, don't use the page icons.
   const bool skip_page_favicons = opt_manifest && !opt_manifest->icons.empty();
diff --git a/chrome/browser/web_applications/commands/externally_managed_install_command_unittest.cc b/chrome/browser/web_applications/commands/externally_managed_install_command_unittest.cc
index c17941cd..d8affe0 100644
--- a/chrome/browser/web_applications/commands/externally_managed_install_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/externally_managed_install_command_unittest.cc
@@ -83,6 +83,7 @@
     manifest->name = u"Example App";
     manifest->short_name = u"App";
     manifest->start_url = kWebAppUrl;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(kWebAppUrl);
     manifest->display = blink::mojom::DisplayMode::kStandalone;
     return manifest;
   }
diff --git a/chrome/browser/web_applications/commands/fetch_installability_for_chrome_management_unittest.cc b/chrome/browser/web_applications/commands/fetch_installability_for_chrome_management_unittest.cc
index 321ac6334..a7f3ae6 100644
--- a/chrome/browser/web_applications/commands/fetch_installability_for_chrome_management_unittest.cc
+++ b/chrome/browser/web_applications/commands/fetch_installability_for_chrome_management_unittest.cc
@@ -48,6 +48,7 @@
   blink::mojom::ManifestPtr CreateManifest() {
     auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = kWebAppUrl;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(kWebAppUrl);
     manifest->scope = kWebAppScope;
     manifest->short_name = base::ASCIIToUTF16(kWebAppName);
     return manifest;
diff --git a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
index 5488fc7..cfa1853e 100644
--- a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
+++ b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/browser/web_applications/web_contents/web_app_data_retriever.h"
@@ -190,7 +191,6 @@
         base::BindOnce(&FetchManifestAndInstallCommand::OnGetWebAppInstallInfo,
                        weak_ptr_factory_.GetWeakPtr()));
   } else {
-    web_app_info_ = std::make_unique<WebAppInstallInfo>();
     FetchManifest();
   }
 }
@@ -275,6 +275,9 @@
     return;
   }
   if (opt_manifest) {
+    if (!web_app_info_) {
+      web_app_info_ = std::make_unique<WebAppInstallInfo>(opt_manifest->id);
+    }
     UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url,
                                  web_app_info_.get());
     LogInstallInfo();
@@ -298,7 +301,7 @@
   const bool skip_page_favicons =
       opt_manifest_ && !opt_manifest_->icons.empty();
 
-  app_id_ = GenerateAppId(web_app_info_->manifest_id, web_app_info_->start_url);
+  app_id_ = GenerateAppIdFromManifestId(web_app_info_->manifest_id);
 
   app_lock_description_ =
       command_manager()->lock_manager().UpgradeAndAcquireLock(
@@ -575,10 +578,7 @@
 }
 
 void FetchManifestAndInstallCommand::LogInstallInfo() {
-  debug_log_.Set("manifest_id",
-                 web_app_info_->manifest_id.has_value()
-                     ? base::Value(web_app_info_->manifest_id.value())
-                     : base::Value());
+  debug_log_.Set("manifest_id", web_app_info_->manifest_id.spec());
   debug_log_.Set("start_url", web_app_info_->start_url.spec());
   debug_log_.Set("name", web_app_info_->title);
 }
diff --git a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_unittest.cc b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_unittest.cc
index 6735b4b..19ee9f38 100644
--- a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_unittest.cc
@@ -136,6 +136,7 @@
     manifest->name = u"foo";
     manifest->short_name = u"bar";
     manifest->start_url = kWebAppUrl;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(kWebAppUrl);
     manifest->display = blink::mojom::DisplayMode::kStandalone;
     return manifest;
   }
diff --git a/chrome/browser/web_applications/commands/install_from_info_command.cc b/chrome/browser/web_applications/commands/install_from_info_command.cc
index 1052f94..d60819e0 100644
--- a/chrome/browser/web_applications/commands/install_from_info_command.cc
+++ b/chrome/browser/web_applications/commands/install_from_info_command.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/check_is_test.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/bind.h"
 #include "base/memory/weak_ptr.h"
@@ -33,10 +34,12 @@
     OnceInstallCallback install_callback)
     : WebAppCommandTemplate<AppLock>("InstallFromInfoCommand"),
       profile_(profile),
-      lock_description_(std::make_unique<AppLockDescription>(
-          GenerateAppId(install_info->manifest_id, install_info->start_url))),
-      app_id_(
-          GenerateAppId(install_info->manifest_id, install_info->start_url)),
+      manifest_id_(
+          install_info->manifest_id.is_empty()
+              ? GenerateManifestIdFromStartUrlOnly(install_info->start_url)
+              : install_info->manifest_id),
+      app_id_(GenerateAppIdFromManifestId(manifest_id_)),
+      lock_description_(std::make_unique<AppLockDescription>(app_id_)),
       install_info_(std::move(install_info)),
       overwrite_existing_manifest_fields_(overwrite_existing_manifest_fields),
       install_surface_(install_surface),
@@ -47,6 +50,12 @@
              bool _) { std::move(install_callback).Run(app_id, code); },
           std::move(install_callback))) {
   PopulateInitialDebugInfo();
+  if (install_info_->manifest_id.is_empty()) {
+    // TODO(b/280862254): After the manifest id constructor is required, this
+    // can be removed.
+    install_info_->manifest_id = manifest_id_;
+  }
+  CHECK(install_info_->manifest_id.is_valid());
 }
 
 InstallFromInfoCommand::InstallFromInfoCommand(
@@ -58,10 +67,12 @@
     const WebAppInstallParams& install_params)
     : WebAppCommandTemplate<AppLock>("InstallFromInfoCommand"),
       profile_(profile),
-      lock_description_(std::make_unique<AppLockDescription>(
-          GenerateAppId(install_info->manifest_id, install_info->start_url))),
-      app_id_(
-          GenerateAppId(install_info->manifest_id, install_info->start_url)),
+      manifest_id_(
+          install_info->manifest_id.is_empty()
+              ? GenerateManifestIdFromStartUrlOnly(install_info->start_url)
+              : install_info->manifest_id),
+      app_id_(GenerateAppIdFromManifestId(manifest_id_)),
+      lock_description_(std::make_unique<AppLockDescription>(app_id_)),
       install_info_(std::move(install_info)),
       overwrite_existing_manifest_fields_(overwrite_existing_manifest_fields),
       install_surface_(install_surface),
@@ -72,6 +83,12 @@
              bool _) { std::move(install_callback).Run(app_id, code); },
           std::move(install_callback))),
       install_params_(install_params) {
+  if (install_info_->manifest_id.is_empty()) {
+    // TODO(b/280862254): After the manifest id constructor is required, this
+    // can be removed.
+    install_info_->manifest_id = manifest_id_;
+  }
+  CHECK(install_info_->manifest_id.is_valid());
   if (!install_params.locally_installed) {
     DCHECK(!install_params.add_to_applications_menu);
     DCHECK(!install_params.add_to_desktop);
@@ -91,16 +108,24 @@
     const std::vector<AppId>& apps_or_extensions_to_uninstall)
     : WebAppCommandTemplate<AppLock>("InstallFromInfoCommand"),
       profile_(profile),
-      lock_description_(std::make_unique<AppLockDescription>(
-          GenerateAppId(install_info->manifest_id, install_info->start_url))),
-      app_id_(
-          GenerateAppId(install_info->manifest_id, install_info->start_url)),
+      manifest_id_(
+          install_info->manifest_id.is_empty()
+              ? GenerateManifestIdFromStartUrlOnly(install_info->start_url)
+              : install_info->manifest_id),
+      app_id_(GenerateAppIdFromManifestId(manifest_id_)),
+      lock_description_(std::make_unique<AppLockDescription>(app_id_)),
       install_info_(std::move(install_info)),
       overwrite_existing_manifest_fields_(overwrite_existing_manifest_fields),
       install_surface_(install_surface),
       install_callback_(std::move(install_callback)),
       install_params_(install_params),
       apps_or_extensions_to_uninstall_(apps_or_extensions_to_uninstall) {
+  if (install_info_->manifest_id.is_empty()) {
+    // TODO(b/280862254): After the manifest id constructor is required, this
+    // can be removed.
+    install_info_->manifest_id = manifest_id_;
+  }
+  CHECK(install_info_->manifest_id.is_valid());
   if (!install_params.locally_installed) {
     DCHECK(!install_params.add_to_applications_menu);
     DCHECK(!install_params.add_to_desktop);
@@ -150,6 +175,7 @@
 
 void InstallFromInfoCommand::PopulateInitialDebugInfo() {
   debug_value_.Set("app_id", app_id_);
+  debug_value_.Set("manifest_id", manifest_id_.spec());
   debug_value_.Set("start_url", install_info_->start_url.spec());
   debug_value_.Set("overwrite_existing_manifest_fields",
                    overwrite_existing_manifest_fields_);
diff --git a/chrome/browser/web_applications/commands/install_from_info_command.h b/chrome/browser/web_applications/commands/install_from_info_command.h
index a2a7967..77f32b0 100644
--- a/chrome/browser/web_applications/commands/install_from_info_command.h
+++ b/chrome/browser/web_applications/commands/install_from_info_command.h
@@ -95,11 +95,12 @@
                                     bool did_uninstall_and_replace);
 
   const raw_ptr<Profile> profile_;
+  ManifestId manifest_id_;
+  AppId app_id_;
 
   std::unique_ptr<AppLockDescription> lock_description_;
   std::unique_ptr<AppLock> lock_;
 
-  AppId app_id_;
   std::unique_ptr<WebAppInstallInfo> install_info_;
   bool overwrite_existing_manifest_fields_;
   webapps::WebappInstallSource install_surface_;
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command.cc b/chrome/browser/web_applications/commands/install_from_sync_command.cc
index 875c1f8..4a356e0 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command.cc
+++ b/chrome/browser/web_applications/commands/install_from_sync_command.cc
@@ -54,8 +54,8 @@
 InstallFromSyncCommand::Params::~Params() = default;
 
 InstallFromSyncCommand::Params::Params(
-    AppId app_id,
-    const absl::optional<std::string>& manifest_id,
+    const AppId& app_id,
+    const ManifestId& manifest_id,
     const GURL& start_url,
     const std::string& title,
     const GURL& scope,
@@ -69,7 +69,11 @@
       scope(scope),
       theme_color(theme_color),
       user_display_mode(user_display_mode),
-      icons(icons) {}
+      icons(icons) {
+  CHECK(!app_id.empty());
+  CHECK(manifest_id.is_valid());
+  CHECK(!manifest_id.is_empty());
+}
 
 InstallFromSyncCommand::Params::Params(const Params&) = default;
 
@@ -94,8 +98,8 @@
   DCHECK(AreAppsLocallyInstalledBySync());
 #endif
   DCHECK(params_.start_url.is_valid());
-  fallback_install_info_ = std::make_unique<WebAppInstallInfo>();
-  fallback_install_info_->manifest_id = params_.manifest_id;
+  fallback_install_info_ =
+      std::make_unique<WebAppInstallInfo>(params_.manifest_id);
   fallback_install_info_->start_url = params_.start_url;
   fallback_install_info_->title = base::UTF8ToUTF16(params_.title);
   fallback_install_info_->user_display_mode = params_.user_display_mode;
@@ -103,7 +107,7 @@
   fallback_install_info_->theme_color = params_.theme_color;
   fallback_install_info_->manifest_icons = params_.icons;
   debug_value_.Set("app_id", params_.app_id);
-  debug_value_.Set("manifest_id", params_.manifest_id.value_or("<unset>"));
+  debug_value_.Set("manifest_id", params_.manifest_id.spec());
   debug_value_.Set("title", params_.title);
   debug_value_.Set("user_display_mode",
                    params_.user_display_mode
@@ -220,7 +224,7 @@
 
   // Ensure that the manifest linked is the right one.
   AppId generated_app_id =
-      GenerateAppId(install_info_->manifest_id, install_info_->start_url);
+      GenerateAppIdFromManifestId(install_info_->manifest_id);
   if (params_.app_id != generated_app_id) {
     // Add the error to the log.
     base::Value::Dict expected_id_error;
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command.h b/chrome/browser/web_applications/commands/install_from_sync_command.h
index 39709fd7..c61d19d8 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command.h
+++ b/chrome/browser/web_applications/commands/install_from_sync_command.h
@@ -41,8 +41,8 @@
     Params() = delete;
     ~Params();
     Params(const Params&);
-    Params(AppId app_id,
-           const absl::optional<std::string>& manifest_id,
+    Params(const AppId& app_id,
+           const ManifestId& manifest_id,
            const GURL& start_url,
            const std::string& title,
            const GURL& scope,
@@ -50,7 +50,7 @@
            const absl::optional<mojom::UserDisplayMode>& user_display_mode,
            const std::vector<apps::IconInfo>& icons);
     const AppId app_id;
-    const absl::optional<std::string> manifest_id;
+    const ManifestId manifest_id;
     const GURL start_url;
     const std::string title;
     const GURL scope;
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command_browsertest.cc b/chrome/browser/web_applications/commands/install_from_sync_command_browsertest.cc
index 172b63d3..7d4518b2 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command_browsertest.cc
+++ b/chrome/browser/web_applications/commands/install_from_sync_command_browsertest.cc
@@ -40,9 +40,10 @@
   auto* provider = WebAppProvider::GetForTest(profile());
   base::RunLoop loop;
   InstallFromSyncCommand::Params params = InstallFromSyncCommand::Params(
-      id, absl::nullopt, test_url, "Test Title",
-      https_server()->GetURL("/banners/"), absl::nullopt,
-      mojom::UserDisplayMode::kStandalone,
+      id, GenerateManifestIdFromStartUrlOnly(test_url), /*start_url=*/test_url,
+      "Test Title",
+      /*scope=*/https_server()->GetURL("/banners/"),
+      /*theme_color=*/absl::nullopt, mojom::UserDisplayMode::kStandalone,
       {apps::IconInfo(https_server()->GetURL("/banners/launcher-icon-2x.png"),
                       96)});
   provider->command_manager().ScheduleCommand(
@@ -80,9 +81,10 @@
   base::RunLoop loop;
   {
     InstallFromSyncCommand::Params params = InstallFromSyncCommand::Params(
-        id, absl::nullopt, test_url, "Test Title",
-        https_server()->GetURL("/banners/"), absl::nullopt,
-        mojom::UserDisplayMode::kStandalone,
+        id, GenerateManifestIdFromStartUrlOnly(test_url),
+        /*start_url=*/test_url, "Test Title",
+        /*scope=*/https_server()->GetURL("/banners/"),
+        /*theme_color=*/absl::nullopt, mojom::UserDisplayMode::kStandalone,
         {apps::IconInfo(https_server()->GetURL("/banners/launcher-icon-2x.png"),
                         96)});
     provider->command_manager().ScheduleCommand(
@@ -97,9 +99,10 @@
   }
   {
     InstallFromSyncCommand::Params params = InstallFromSyncCommand::Params(
-        other_id, absl::nullopt, other_test_url, "Test Title",
-        https_server()->GetURL("/banners/"), absl::nullopt,
-        mojom::UserDisplayMode::kStandalone,
+        other_id, GenerateManifestIdFromStartUrlOnly(other_test_url),
+        /*start_url=*/other_test_url, "Test Title",
+        /*scope=*/https_server()->GetURL("/banners/"),
+        /*theme_color=*/absl::nullopt, mojom::UserDisplayMode::kStandalone,
         {apps::IconInfo(https_server()->GetURL("/banners/launcher-icon-2x.png"),
                         96)});
     provider->command_manager().ScheduleCommand(
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command_unittest.cc b/chrome/browser/web_applications/commands/install_from_sync_command_unittest.cc
index 1a295179..bdcdb5b 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/install_from_sync_command_unittest.cc
@@ -113,7 +113,7 @@
 
   InstallFromSyncCommand::Params CreateParams(AppId app_id, GURL url) {
     return InstallFromSyncCommand::Params(
-        app_id, /*manifest_id=*/absl::nullopt, url, kFallbackTitle,
+        app_id, GenerateManifestIdFromStartUrlOnly(url), url, kFallbackTitle,
         url.GetWithoutFilename(), /*theme_color=*/absl::nullopt,
         mojom::UserDisplayMode::kStandalone, /*icons=*/
         {apps::IconInfo(kFallbackIconUrl, kIconSize)});
@@ -218,6 +218,7 @@
     blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
     manifest->name = kManifestName;
     manifest->start_url = url;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(url);
     if (icons) {
       blink::Manifest::ImageResource primary_icon;
       primary_icon.type = u"image/png";
@@ -450,7 +451,7 @@
       .WillOnce(base::test::RunOnceCallback<1>(CreateSiteInstallInfo()));
 
   auto manifest = CreateManifest(true);
-  manifest->id = u"other_path/index.html";
+  manifest->id = kWebAppManifestStartUrl.Resolve(u"other_path/index.html");
 
   EXPECT_CALL(*data_retriever, CheckInstallabilityAndRetrieveManifest(
                                    testing::_, true,
diff --git a/chrome/browser/web_applications/commands/install_placeholder_command.cc b/chrome/browser/web_applications/commands/install_placeholder_command.cc
index fd9312b..d90ca413 100644
--- a/chrome/browser/web_applications/commands/install_placeholder_command.cc
+++ b/chrome/browser/web_applications/commands/install_placeholder_command.cc
@@ -38,8 +38,9 @@
     std::unique_ptr<WebAppDataRetriever> data_retriever)
     : WebAppCommandTemplate<AppLock>("InstallPlaceholderCommand"),
       profile_(profile),
-      app_id_(GenerateAppId(/*manifest_id=*/absl::nullopt,
-                            install_options.install_url)),
+      // For placeholder installs, the install_url is treated as the start_url.
+      app_id_(GenerateAppIdFromManifestId(
+          GenerateManifestIdFromStartUrlOnly(install_options.install_url))),
       lock_description_(std::make_unique<AppLockDescription>(app_id_)),
       install_options_(install_options),
       callback_(std::move(callback)),
@@ -129,8 +130,9 @@
 void InstallPlaceholderCommand::FinalizeInstall(
     absl::optional<std::reference_wrapper<const std::vector<SkBitmap>>>
         bitmaps) {
-  WebAppInstallInfo web_app_info;
-
+  // For placeholder installs, the install_url is treated as the start_url.
+  WebAppInstallInfo web_app_info(
+      GenerateManifestIdFromStartUrlOnly(install_options_.install_url));
   web_app_info.title =
       install_options_.override_name
           ? base::UTF8ToUTF16(install_options_.override_name.value())
diff --git a/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc b/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
index 8b6c576..91fa369 100644
--- a/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
+++ b/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
@@ -137,7 +137,7 @@
   }
 
   debug_value_.Set("manifest_parsed", true);
-  web_app_info_ = std::make_unique<WebAppInstallInfo>();
+  web_app_info_ = std::make_unique<WebAppInstallInfo>(manifest->id);
   web_app_info_->user_display_mode = mojom::UserDisplayMode::kStandalone;
 
   UpdateWebAppInfoFromManifest(*manifest, manifest_url_, web_app_info_.get());
@@ -181,8 +181,7 @@
 
   PopulateOtherIcons(web_app_info_.get(), icons_map);
 
-  AppId app_id =
-      GenerateAppId(web_app_info_->manifest_id, web_app_info_->start_url);
+  AppId app_id = GenerateAppIdFromManifestId(web_app_info_->manifest_id);
 
   if (app_id != expected_id_) {
     Abort(CommandResult::kFailure,
diff --git a/chrome/browser/web_applications/commands/manifest_update_check_command.cc b/chrome/browser/web_applications/commands/manifest_update_check_command.cc
index 8493a319..d13ee75 100644
--- a/chrome/browser/web_applications/commands/manifest_update_check_command.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_check_command.cc
@@ -144,11 +144,13 @@
     return;
   }
   DCHECK(opt_manifest);
+  CHECK(!new_install_info_);
 
-  UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url, &new_install_info_);
+  new_install_info_ = std::make_unique<WebAppInstallInfo>(
+      CreateWebAppInfoFromManifest(*opt_manifest, manifest_url));
+  CHECK(new_install_info_->manifest_id.is_valid());
 
-  if (app_id_ != GenerateAppId(new_install_info_.manifest_id,
-                               new_install_info_.start_url)) {
+  if (app_id_ != GenerateAppIdFromManifestId(new_install_info_->manifest_id)) {
     CompleteCommandAndSelfDestruct(ManifestUpdateCheckResult::kAppIdMismatch);
     return;
   }
@@ -166,8 +168,9 @@
     return;
   }
 
+  CHECK(new_install_info_);
   base::flat_set<GURL> icon_urls =
-      GetValidIconUrlsToDownload(new_install_info_);
+      GetValidIconUrlsToDownload(*new_install_info_);
 
   IconDownloaderOptions options = {.skip_page_favicons = true,
                                    .fail_all_if_any_fail = true};
@@ -193,8 +196,8 @@
     return;
   }
 
-  PopulateOtherIcons(&new_install_info_, icons_map);
-  PopulateProductIcons(&new_install_info_, &icons_map);
+  PopulateOtherIcons(new_install_info_.get(), icons_map);
+  PopulateProductIcons(new_install_info_.get(), &icons_map);
 
   std::move(next_step_callback).Run();
 }
@@ -209,12 +212,12 @@
     return;
   }
 
-  GURL web_app_identity = GURL(GenerateAppIdUnhashed(
-      new_install_info_.manifest_id, new_install_info_.start_url));
-  ScopeExtensions new_scope_extensions = new_install_info_.scope_extensions;
+  CHECK(new_install_info_);
+  CHECK(new_install_info_->manifest_id.is_valid());
+  ScopeExtensions new_scope_extensions = new_install_info_->scope_extensions;
 
   lock_->origin_association_manager().GetWebAppOriginAssociations(
-      web_app_identity, std::move(new_scope_extensions),
+      new_install_info_->manifest_id, std::move(new_scope_extensions),
       std::move(next_step_callback));
 }
 
@@ -229,7 +232,7 @@
     return;
   }
 
-  new_install_info_.validated_scope_extensions =
+  new_install_info_->validated_scope_extensions =
       absl::make_optional(std::move(validated_scope_extensions));
   std::move(next_step_callback).Run();
 }
@@ -313,9 +316,10 @@
   const WebApp* web_app = lock_->registrar().GetAppById(app_id_);
   DCHECK(web_app);
 
+  CHECK(new_install_info_);
   manifest_data_changes_ = GetManifestDataChanges(
       GetWebApp(), &existing_app_icon_bitmaps_,
-      &existing_shortcuts_menu_icon_bitmaps_, new_install_info_);
+      &existing_shortcuts_menu_icon_bitmaps_, *new_install_info_);
 
   std::move(next_step_callback).Run();
 }
@@ -452,7 +456,7 @@
       /*icon_change=*/
       manifest_data_changes_.app_icon_identity_change.has_value(),
       /*old_title=*/base::UTF8ToUTF16(GetWebApp().untranslated_name()),
-      /*new_title=*/new_install_info_.title,
+      /*new_title=*/new_install_info_->title,
       /*old_icon=*/*before_icon,
       /*new_icon=*/*after_icon, web_contents_.get(),
       base::BindOnce(
@@ -494,7 +498,7 @@
     // Revert to WebApp::untranslated_name() instead of
     // WebAppRegistrar::GetAppShortName() because that's the field
     // WebAppInstallInfo::title gets written to (see SetWebAppManifestFields()).
-    new_install_info_.title =
+    new_install_info_->title =
         base::UTF8ToUTF16(GetWebApp().untranslated_name());
     manifest_data_changes_.app_name_changed = false;
   }
@@ -503,9 +507,9 @@
           IdentityUpdateDecision::kRevert &&
       manifest_data_changes_.app_icon_identity_change) {
     const WebApp& web_app = GetWebApp();
-    new_install_info_.manifest_icons = web_app.manifest_icons();
-    new_install_info_.icon_bitmaps = existing_app_icon_bitmaps_;
-    new_install_info_.is_generated_icon = web_app.is_generated_icon();
+    new_install_info_->manifest_icons = web_app.manifest_icons();
+    new_install_info_->icon_bitmaps = existing_app_icon_bitmaps_;
+    new_install_info_->is_generated_icon = web_app.is_generated_icon();
     manifest_data_changes_.app_icon_identity_change.reset();
     manifest_data_changes_.any_app_icon_changed = false;
   }
@@ -561,7 +565,7 @@
       base::BindOnce(std::move(completed_callback_), check_result,
                      check_result == ManifestUpdateCheckResult::kAppUpdateNeeded
                          ? absl::make_optional<WebAppInstallInfo>(
-                               std::move(new_install_info_))
+                               std::move(*new_install_info_))
                          : absl::nullopt));
 }
 
diff --git a/chrome/browser/web_applications/commands/manifest_update_check_command.h b/chrome/browser/web_applications/commands/manifest_update_check_command.h
index a70380a..c6d8b55 100644
--- a/chrome/browser/web_applications/commands/manifest_update_check_command.h
+++ b/chrome/browser/web_applications/commands/manifest_update_check_command.h
@@ -149,7 +149,7 @@
 
   // Temporary variables stored here while the update check progresses
   // asynchronously.
-  WebAppInstallInfo new_install_info_;
+  std::unique_ptr<WebAppInstallInfo> new_install_info_;
   IconBitmaps existing_app_icon_bitmaps_;
   ShortcutsMenuIconBitmaps existing_shortcuts_menu_icon_bitmaps_;
   ManifestDataChanges manifest_data_changes_;
diff --git a/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc b/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
index dd404cab..1158ba13 100644
--- a/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/web_applications/test/web_app_test.h"
 #include "chrome/browser/web_applications/web_app_callback_app_identity.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -390,6 +391,7 @@
   blink::mojom::ManifestPtr GetManifestFromInfo(const WebAppInstallInfo& info) {
     auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = info.start_url;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(info.start_url);
     manifest->scope = info.scope;
     manifest->display = info.display_mode;
     manifest->name = info.title;
diff --git a/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc b/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
index 26be1e1d..e691b969 100644
--- a/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
@@ -16,6 +16,7 @@
 #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/web_app_command_manager.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "components/keep_alive_registry/keep_alive_types.h"
@@ -75,6 +76,7 @@
   AppId InstallWebApp() {
     auto web_app_info = std::make_unique<WebAppInstallInfo>();
     web_app_info->start_url = app_url();
+    web_app_info->manifest_id = GenerateManifestIdFromStartUrlOnly(app_url());
     web_app_info->scope = app_url().GetWithoutFilename();
     web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
     web_app_info->title = u"Foo Bar";
@@ -84,6 +86,7 @@
   WebAppInstallInfo GetNewInstallInfoWithTitle(std::u16string new_title) {
     WebAppInstallInfo info;
     info.start_url = app_url();
+    info.manifest_id = GenerateManifestIdFromStartUrlOnly(app_url());
     info.scope = app_url().GetWithoutFilename();
     info.user_display_mode = mojom::UserDisplayMode::kStandalone;
     info.title = new_title;
diff --git a/chrome/browser/web_applications/commands/sub_app_install_command.cc b/chrome/browser/web_applications/commands/sub_app_install_command.cc
index 1a2f935..6b9c757 100644
--- a/chrome/browser/web_applications/commands/sub_app_install_command.cc
+++ b/chrome/browser/web_applications/commands/sub_app_install_command.cc
@@ -87,10 +87,10 @@
 
 std::vector<AppId> CreateAppIdsForLock(
     const AppId& parent_app_id,
-    const std::vector<std::pair<UnhashedAppId, GURL>>& sub_apps) {
+    const std::vector<std::pair<ManifestId, GURL>>& sub_apps) {
   std::vector<AppId> app_ids_vector = {parent_app_id};
   for (const auto& data : sub_apps) {
-    app_ids_vector.push_back(GenerateAppIdFromUnhashed(data.first));
+    app_ids_vector.push_back(GenerateAppIdFromManifestId(data.first));
   }
   return app_ids_vector;
 }
@@ -99,7 +99,7 @@
 
 SubAppInstallCommand::SubAppInstallCommand(
     const AppId& parent_app_id,
-    std::vector<std::pair<UnhashedAppId, GURL>> sub_apps,
+    std::vector<std::pair<ManifestId, GURL>> sub_apps,
     SubAppInstallResultCallback install_callback,
     Profile* profile,
     std::unique_ptr<WebAppUrlLoader> url_loader,
@@ -130,7 +130,7 @@
   base::Value::List pending_installs;
   for (const auto& installs_remaining : requested_installs_) {
     base::Value::Dict install_data;
-    install_data.Set("unhashed_app_id", installs_remaining.first);
+    install_data.Set("manifest_id", installs_remaining.first.spec());
     install_data.Set("install_url", installs_remaining.second.spec());
     pending_installs.Append(base::Value(std::move(install_data)));
   }
@@ -194,15 +194,15 @@
 
 void SubAppInstallCommand::StartNextInstall() {
   DCHECK(!requested_installs_.empty());
-  std::pair<UnhashedAppId, GURL> install_info =
+  std::pair<ManifestId, GURL> install_info =
       std::move(requested_installs_.back());
-  const UnhashedAppId& unhashed_app_id = install_info.first;
+  const ManifestId& manifest_id = install_info.first;
   GURL install_url = install_info.second;
   requested_installs_.pop_back();
 
   DCHECK(AreWebAppsUserInstallable(profile_));
   if (IsWebContentsDestroyed()) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kWebContentsDestroyed);
     return;
   }
@@ -212,11 +212,11 @@
       WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
       base::BindOnce(
           &SubAppInstallCommand::OnWebAppUrlLoadedGetWebAppInstallInfo,
-          weak_ptr_factory_.GetWeakPtr(), unhashed_app_id, install_url));
+          weak_ptr_factory_.GetWeakPtr(), manifest_id, install_url));
 }
 
 void SubAppInstallCommand::OnWebAppUrlLoadedGetWebAppInstallInfo(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     const GURL& url_to_load,
     WebAppUrlLoader::Result result) {
   if (result != WebAppUrlLoader::Result::kUrlLoaded) {
@@ -225,19 +225,19 @@
   }
 
   if (result == WebAppUrlLoader::Result::kRedirectedUrlLoaded) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kInstallURLRedirected);
     return;
   }
 
   if (result == WebAppUrlLoader::Result::kFailedPageTookTooLong) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kInstallURLLoadTimeOut);
     return;
   }
 
   if (result != WebAppUrlLoader::Result::kUrlLoaded) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kInstallURLLoadFailed);
     return;
   }
@@ -245,21 +245,21 @@
   data_retriever_->GetWebAppInstallInfo(
       &lock_->shared_web_contents(),
       base::BindOnce(&SubAppInstallCommand::OnGetWebAppInstallInfo,
-                     weak_ptr_factory_.GetWeakPtr(), unhashed_app_id));
+                     weak_ptr_factory_.GetWeakPtr(), manifest_id));
 }
 
 void SubAppInstallCommand::OnGetWebAppInstallInfo(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     std::unique_ptr<WebAppInstallInfo> install_info) {
   if (!install_info) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kGetWebAppInstallInfoFailed);
     return;
   }
   install_info->parent_app_id = parent_app_id_;
 
-  DCHECK(base::Contains(pending_installs_map_, unhashed_app_id));
-  const GURL& install_url = pending_installs_map_[unhashed_app_id];
+  DCHECK(base::Contains(pending_installs_map_, manifest_id));
+  const GURL& install_url = pending_installs_map_[manifest_id];
   // Set start_url to fallback_start_url as web_contents may have been
   // redirected. Will be overridden by manifest values if present.
   if (install_url.is_valid()) {
@@ -271,12 +271,12 @@
   data_retriever_->CheckInstallabilityAndRetrieveManifest(
       &lock_->shared_web_contents(), /*bypass_service_worker_check=*/false,
       base::BindOnce(&SubAppInstallCommand::OnDidPerformInstallableCheck,
-                     weak_ptr_factory_.GetWeakPtr(), unhashed_app_id,
+                     weak_ptr_factory_.GetWeakPtr(), manifest_id,
                      std::move(install_info)));
 }
 
 void SubAppInstallCommand::OnDidPerformInstallableCheck(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     std::unique_ptr<WebAppInstallInfo> web_app_info,
     blink::mojom::ManifestPtr opt_manifest,
     const GURL& manifest_url,
@@ -286,7 +286,7 @@
   if (!valid_manifest_for_web_app) {
     LOG(WARNING) << "Did not install " << web_app_info->start_url.spec()
                  << " because it didn't have a manifest for web app";
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kNotValidManifestForWebApp);
     return;
   }
@@ -296,21 +296,20 @@
                                  web_app_info.get());
   }
 
-  AppId app_id =
-      GenerateAppId(web_app_info->manifest_id, web_app_info->start_url);
+  AppId app_id = GenerateAppIdFromManifestId(web_app_info->manifest_id);
 
-  const AppId expected_app_id = GenerateAppIdFromUnhashed(unhashed_app_id);
+  const AppId expected_app_id = GenerateAppIdFromManifestId(manifest_id);
   if (app_id != expected_app_id) {
     log_entry_.LogExpectedAppIdError("OnDidPerformInstallableCheck",
                                      web_app_info->start_url.spec(), app_id,
                                      expected_app_id);
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kExpectedAppIdCheckFailed);
     return;
   }
 
   if (lock_->registrar().WasInstalledBySubApp(app_id)) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kSuccessAlreadyInstalled);
     return;
   }
@@ -322,12 +321,12 @@
   data_retriever_->GetIcons(
       &lock_->shared_web_contents(), std::move(icon_urls), skip_page_favicons,
       base::BindOnce(&SubAppInstallCommand::OnIconsRetrievedShowDialog,
-                     weak_ptr_factory_.GetWeakPtr(), unhashed_app_id,
+                     weak_ptr_factory_.GetWeakPtr(), manifest_id,
                      std::move(web_app_info)));
 }
 
 void SubAppInstallCommand::OnIconsRetrievedShowDialog(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     std::unique_ptr<WebAppInstallInfo> web_app_info,
     IconsDownloadedResult result,
     IconsMap icons_map,
@@ -340,20 +339,20 @@
                                       icons_http_results);
 
   acceptance_callbacks_.emplace_back(
-      unhashed_app_id, std::move(web_app_info),
+      manifest_id, std::move(web_app_info),
       base::BindOnce(&SubAppInstallCommand::OnDialogCompleted,
-                     weak_ptr_factory_.GetWeakPtr(), unhashed_app_id));
+                     weak_ptr_factory_.GetWeakPtr(), manifest_id));
   num_pending_dialog_callbacks_--;
   DCHECK_GE(num_pending_dialog_callbacks_, 0u);
   MaybeShowDialog();
 }
 
 void SubAppInstallCommand::OnDialogCompleted(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     bool user_accepted,
     std::unique_ptr<WebAppInstallInfo> web_app_info) {
   if (!user_accepted) {
-    MaybeFinishInstall(unhashed_app_id,
+    MaybeFinishInstall(manifest_id,
                        webapps::InstallResultCode::kUserInstallDeclined);
     return;
   }
@@ -363,35 +362,33 @@
   lock_->install_finalizer().FinalizeInstall(
       *web_app_info, GetFinalizerOptionsForSubApps(parent_app_id_),
       base::BindOnce(&SubAppInstallCommand::OnInstallFinalized,
-                     weak_ptr_factory_.GetWeakPtr(), unhashed_app_id,
+                     weak_ptr_factory_.GetWeakPtr(), manifest_id,
                      web_app_info->start_url));
 }
 
-void SubAppInstallCommand::OnInstallFinalized(
-    const UnhashedAppId& unhashed_app_id,
-    const GURL& start_url,
-    const AppId& app_id,
-    webapps::InstallResultCode code,
-    OsHooksErrors os_hooks_errors) {
+void SubAppInstallCommand::OnInstallFinalized(const ManifestId& manifest_id,
+                                              const GURL& start_url,
+                                              const AppId& app_id,
+                                              webapps::InstallResultCode code,
+                                              OsHooksErrors os_hooks_errors) {
   if (code != webapps::InstallResultCode::kSuccessNewInstall) {
-    MaybeFinishInstall(unhashed_app_id, code);
+    MaybeFinishInstall(manifest_id, code);
     return;
   }
 
   RecordWebAppInstallationTimestamp(profile_->GetPrefs(), app_id,
                                     webapps::WebappInstallSource::SUB_APP);
-  MaybeFinishInstall(unhashed_app_id,
+  MaybeFinishInstall(manifest_id,
                      webapps::InstallResultCode::kSuccessNewInstall);
 }
 
-void SubAppInstallCommand::MaybeFinishInstall(
-    const UnhashedAppId& unhashed_app_id,
-    webapps::InstallResultCode code) {
+void SubAppInstallCommand::MaybeFinishInstall(const ManifestId& manifest_id,
+                                              webapps::InstallResultCode code) {
   // Verifying that other asynchronous calls have not already installed this
   // app and thus removed it from the pending installs map.
-  DCHECK(base::Contains(pending_installs_map_, unhashed_app_id));
+  DCHECK(base::Contains(pending_installs_map_, manifest_id));
   webapps::InstallableMetrics::TrackInstallResult(webapps::IsSuccess(code));
-  AddResultAndRemoveFromPendingInstalls(unhashed_app_id, code);
+  AddResultAndRemoveFromPendingInstalls(manifest_id, code);
   // In case an installation returns with a failure before running the dialog
   // callback.
   if (state_ == State::kPendingDialogCallbacks &&
@@ -421,7 +418,7 @@
 
   // TODO(https://crbug.com/1313109): Replace the placeholder blanket user
   // acceptance below with a permissions dialog shown to the user.
-  for (auto& [unhashed_app_id, web_app_info, acceptance_callback] :
+  for (auto& [manifest_id, web_app_info, acceptance_callback] :
        acceptance_callbacks_) {
     if (dialog_not_accepted_for_testing_) {
       std::move(acceptance_callback).Run(false, std::move(web_app_info));
@@ -448,15 +445,15 @@
 }
 
 void SubAppInstallCommand::AddResultAndRemoveFromPendingInstalls(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     webapps::InstallResultCode result) {
   auto mojo_result = InstallResultCodeToMojo(result);
-  std::pair result_pair(unhashed_app_id, mojo_result);
-  AddResultToDebugData(unhashed_app_id, pending_installs_map_[unhashed_app_id],
-                       GenerateAppIdFromUnhashed(unhashed_app_id), result,
+  std::pair result_pair(manifest_id, mojo_result);
+  AddResultToDebugData(manifest_id, pending_installs_map_[manifest_id],
+                       GenerateAppIdFromManifestId(manifest_id), result,
                        mojo_result);
   results_.emplace_back(result_pair);
-  pending_installs_map_.erase(unhashed_app_id);
+  pending_installs_map_.erase(manifest_id);
 }
 
 bool SubAppInstallCommand::IsWebContentsDestroyed() {
@@ -464,13 +461,13 @@
 }
 
 void SubAppInstallCommand::AddResultToDebugData(
-    const UnhashedAppId& unhashed_app_id,
+    const ManifestId& manifest_id,
     const GURL& install_url,
     const AppId& installed_app_id,
     webapps::InstallResultCode detailed_code,
     const blink::mojom::SubAppsServiceResultCode& result_code) {
   base::Value::Dict install_info;
-  install_info.Set("unhashed_app_id", unhashed_app_id);
+  install_info.Set("manifest_id", manifest_id.spec());
   install_info.Set("install_url", install_url.spec());
   install_info.Set("detailed_result_code", base::ToString(detailed_code));
   install_info.Set("result_code", base::ToString(result_code));
diff --git a/chrome/browser/web_applications/commands/sub_app_install_command.h b/chrome/browser/web_applications/commands/sub_app_install_command.h
index 22e6f06d5..6f2ca09 100644
--- a/chrome/browser/web_applications/commands/sub_app_install_command.h
+++ b/chrome/browser/web_applications/commands/sub_app_install_command.h
@@ -39,14 +39,14 @@
 class WebAppDataRetriever;
 
 using AppInstallResults =
-    std::vector<std::pair<AppId, blink::mojom::SubAppsServiceResultCode>>;
+    std::vector<std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode>>;
 using SubAppInstallResultCallback = base::OnceCallback<void(AppInstallResults)>;
 
 class SubAppInstallCommand
     : public WebAppCommandTemplate<SharedWebContentsWithAppLock> {
  public:
   SubAppInstallCommand(const AppId& parent_app_id,
-                       std::vector<std::pair<UnhashedAppId, GURL>> sub_apps,
+                       std::vector<std::pair<ManifestId, GURL>> sub_apps,
                        SubAppInstallResultCallback install_callback,
                        Profile* profile,
                        std::unique_ptr<WebAppUrlLoader> url_loader,
@@ -75,45 +75,43 @@
 
   // Functions to perform install flow for each sub app.
   void StartNextInstall();
-  void OnWebAppUrlLoadedGetWebAppInstallInfo(
-      const UnhashedAppId& unhashed_app_id,
-      const GURL& url_to_load,
-      WebAppUrlLoader::Result result);
-  void OnGetWebAppInstallInfo(const UnhashedAppId& unhashed_app_id,
+  void OnWebAppUrlLoadedGetWebAppInstallInfo(const ManifestId& manifest_id,
+                                             const GURL& url_to_load,
+                                             WebAppUrlLoader::Result result);
+  void OnGetWebAppInstallInfo(const ManifestId& manifest_id,
                               std::unique_ptr<WebAppInstallInfo> install_info);
   void OnDidPerformInstallableCheck(
-      const UnhashedAppId& unhashed_app_id,
+      const ManifestId& manifest_id,
       std::unique_ptr<WebAppInstallInfo> web_app_info,
       blink::mojom::ManifestPtr opt_manifest,
       const GURL& manifest_url,
       bool valid_manifest_for_web_app,
       webapps::InstallableStatusCode error_code);
   void OnIconsRetrievedShowDialog(
-      const UnhashedAppId& unhashed_app_id,
+      const ManifestId& manifest_id,
       std::unique_ptr<WebAppInstallInfo> web_app_info,
       IconsDownloadedResult result,
       IconsMap icons_map,
       DownloadedIconsHttpResults icons_http_results);
-  void OnDialogCompleted(const UnhashedAppId& unhashed_app_id,
+  void OnDialogCompleted(const ManifestId& manifest_id,
                          bool user_accepted,
                          std::unique_ptr<WebAppInstallInfo> web_app_info);
-  void OnInstallFinalized(const UnhashedAppId& unhashed_app_id,
+  void OnInstallFinalized(const ManifestId& manifest_id,
                           const GURL& start_url,
                           const AppId& app_id,
                           webapps::InstallResultCode code,
                           OsHooksErrors os_hooks_errors);
-  void MaybeFinishInstall(const UnhashedAppId& app_id,
+  void MaybeFinishInstall(const ManifestId& app_id,
                           webapps::InstallResultCode code);
 
   // Functions to manage all sub apps installations.
   void MaybeShowDialog();
   void MaybeFinishCommand();
-  void AddResultAndRemoveFromPendingInstalls(
-      const UnhashedAppId& unhashed_app_id,
-      webapps::InstallResultCode result);
+  void AddResultAndRemoveFromPendingInstalls(const ManifestId& manifest_id,
+                                             webapps::InstallResultCode result);
   bool IsWebContentsDestroyed();
   void AddResultToDebugData(
-      const UnhashedAppId& unhashed_app_id,
+      const ManifestId& manifest_id,
       const GURL& url,
       const AppId& installed_app_id,
       webapps::InstallResultCode detailed_code,
@@ -123,20 +121,20 @@
   std::unique_ptr<SharedWebContentsWithAppLock> lock_;
 
   const AppId parent_app_id_;
-  std::vector<std::pair<UnhashedAppId, GURL>> requested_installs_;
+  std::vector<std::pair<ManifestId, GURL>> requested_installs_;
   SubAppInstallResultCallback install_callback_;
 
   raw_ptr<Profile> profile_;
   std::unique_ptr<WebAppUrlLoader> url_loader_;
   std::unique_ptr<WebAppDataRetriever> data_retriever_;
 
-  base::flat_map<UnhashedAppId, GURL> pending_installs_map_;
+  base::flat_map<ManifestId, GURL> pending_installs_map_;
   size_t num_pending_dialog_callbacks_ = 0;
   AppInstallResults results_;
   InstallErrorLogEntry log_entry_;
   base::Value::Dict debug_install_results_;
   std::vector<
-      std::tuple<UnhashedAppId,
+      std::tuple<ManifestId,
                  std::unique_ptr<WebAppInstallInfo>,
                  base::OnceCallback<void(bool user_accepted,
                                          std::unique_ptr<WebAppInstallInfo>)>>>
diff --git a/chrome/browser/web_applications/commands/sub_app_install_command_unittest.cc b/chrome/browser/web_applications/commands/sub_app_install_command_unittest.cc
index 6788d3d..6394958 100644
--- a/chrome/browser/web_applications/commands/sub_app_install_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/sub_app_install_command_unittest.cc
@@ -45,7 +45,7 @@
  protected:
   std::unique_ptr<SubAppInstallCommand> CreateCommand(
       AppId& parent_app_id,
-      std::vector<std::pair<UnhashedAppId, GURL>> sub_app_data,
+      std::vector<std::pair<ManifestId, GURL>> sub_app_data,
       SubAppInstallResultCallback callback,
       std::unique_ptr<WebAppUrlLoader> url_loader,
       std::unique_ptr<WebAppDataRetriever> data_retriever) {
@@ -56,7 +56,7 @@
 
   AppInstallResults InstallSubAppAndWait(
       AppId& parent_app_id,
-      std::pair<UnhashedAppId, GURL> sub_app_data,
+      std::pair<ManifestId, GURL> sub_app_data,
       std::unique_ptr<WebAppDataRetriever> data_retriever,
       bool dialog_not_accepted = false,
       WebAppUrlLoader::Result url_load_result =
@@ -86,6 +86,7 @@
   AppId InstallParentApp() {
     auto web_app_info = std::make_unique<WebAppInstallInfo>();
     web_app_info->start_url = parent_url_;
+    web_app_info->manifest_id = GenerateManifestIdFromStartUrlOnly(parent_url_);
     web_app_info->scope = parent_url_.GetWithoutFilename();
     web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
     web_app_info->title = u"Web App";
@@ -96,6 +97,7 @@
       const GURL& url) {
     auto web_app_info = std::make_unique<WebAppInstallInfo>();
     web_app_info->start_url = url;
+    web_app_info->manifest_id = GenerateManifestIdFromStartUrlOnly(url);
     web_app_info->scope = url.GetWithoutFilename();
     web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
     web_app_info->title = u"Sub App";
@@ -138,18 +140,18 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
   AppInstallResults command_result = InstallSubAppAndWait(
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()));
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kSuccess);
 
   // Verify command works fine, single sub_app is installed.
@@ -163,11 +165,11 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded,
@@ -176,7 +178,7 @@
   // Install first app as sub_app.
   AppInstallResults command_result = InstallSubAppAndWait(
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()));
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kSuccess);
   EXPECT_EQ(1u, command_result.size());
   EXPECT_EQ(expected_result, command_result[0]);
@@ -186,7 +188,7 @@
   // Reinstalling the same app as a sub_app should return a kSuccess.
   command_result = InstallSubAppAndWait(
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()));
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode>
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode>
       expected_result_installed(
           unhashed_sub_app_id,
           blink::mojom::SubAppsServiceResultCode::kSuccess);
@@ -201,11 +203,11 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
@@ -213,7 +215,7 @@
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()),
       /*dialog_not_accepted=*/true);
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kFailure);
 
   // Verify command works and returns a kFailure.
@@ -227,18 +229,18 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data("http://abc.com/", sub_app_url());
+  std::pair<ManifestId, GURL> data("http://abc.com/", sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
   AppInstallResults command_result = InstallSubAppAndWait(
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()));
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       "http://abc.com/", blink::mojom::SubAppsServiceResultCode::kFailure);
 
   // Verify command works and returns a kFailure.
@@ -249,19 +251,19 @@
 }
 
 TEST_F(SubAppInstallCommandTest, InstallFailsIfNoParentApp) {
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
   AppId parent_app_id = "random_app";
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
   AppInstallResults command_result = InstallSubAppAndWait(
       parent_app_id, data, GetDataRetrieverWithInfoAndManifest(sub_app_url()));
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kFailure);
 
   // Verify command works and returns a kFailure.
@@ -275,11 +277,11 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
@@ -288,7 +290,7 @@
       /*dialog_not_accepted=*/false,
       /*url_load_result=*/WebAppUrlLoader::Result::kRedirectedUrlLoaded);
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kFailure);
 
   // Verify command works and returns a kFailure.
@@ -302,11 +304,11 @@
   AppId parent_app_id = InstallParentApp();
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id).size());
 
-  UnhashedAppId unhashed_sub_app_id =
-      GenerateAppIdUnhashed(/*manifest_id=*/absl::nullopt, sub_app_url());
-  AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_sub_app_id);
+  ManifestId unhashed_sub_app_id =
+      GenerateManifestIdFromStartUrlOnly(sub_app_url());
+  AppId sub_app_id = GenerateAppIdFromManifestId(unhashed_sub_app_id);
 
-  std::pair<UnhashedAppId, GURL> data(unhashed_sub_app_id, sub_app_url());
+  std::pair<ManifestId, GURL> data(unhashed_sub_app_id, sub_app_url());
 
   command_manager_url_loader().AddPrepareForLoadResults(
       {WebAppUrlLoader::Result::kUrlLoaded});
@@ -315,7 +317,7 @@
                            GetDataRetrieverWithInfoAndManifest(
                                sub_app_url(), /*disable_web_app_info=*/true));
 
-  std::pair<AppId, blink::mojom::SubAppsServiceResultCode> expected_result(
+  std::pair<ManifestId, blink::mojom::SubAppsServiceResultCode> expected_result(
       unhashed_sub_app_id, blink::mojom::SubAppsServiceResultCode::kFailure);
 
   // Verify command works and returns a kFailure.
diff --git a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
index 95cefbc..171c914 100644
--- a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
@@ -35,6 +35,7 @@
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -365,6 +366,7 @@
 
     auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = options.install_url;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(options.install_url);
     manifest->name = u"Manifest Name";
 
     if (!mock_empty_web_app_info)
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.cc b/chrome/browser/web_applications/externally_managed_app_manager.cc
index fd22025..3dfd90a2 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager.cc
@@ -21,6 +21,7 @@
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/externally_managed_app_install_task.h"
 #include "chrome/browser/web_applications/externally_managed_app_registration_task.h"
 #include "chrome/browser/web_applications/locks/all_apps_lock.h"
@@ -175,7 +176,20 @@
       "ExternallyManagedAppManager::SynchronizeInstalledApps",
       std::make_unique<AllAppsLockDescription>(),
       base::BindOnce(
-          &ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired,
+          [](base::WeakPtr<ExternallyManagedAppManager> weak_this,
+             std::vector<ExternalInstallOptions> desired_apps_install_options,
+             ExternalInstallSource install_source, SynchronizeCallback callback,
+             AllAppsLock& lock) {
+            // To support the `base::Value` return value, this has to be a
+            // lambda instead of directly binding. This is because return values
+            // are not allowed when binding to a WeakPtr.
+            if (!weak_this) {
+              return base::Value();
+            }
+            return weak_this->SynchronizeInstalledAppsOnLockAcquired(
+                std::move(desired_apps_install_options), install_source,
+                std::move(callback), lock);
+          },
           weak_ptr_factory_.GetWeakPtr(),
           std::move(desired_apps_install_options), install_source,
           std::move(callback)));
@@ -475,11 +489,18 @@
   return is_in_shutdown_ || profile()->ShutdownStarted();
 }
 
-void ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired(
+base::Value ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired(
     std::vector<ExternalInstallOptions> desired_apps_install_options,
     ExternalInstallSource install_source,
     SynchronizeCallback callback,
     AllAppsLock& lock) {
+  base::Value::Dict debug_info;
+  debug_info.Set("install_source", base::ToString(install_source));
+  base::Value::List* desired_installs =
+      debug_info.EnsureList("desired_apps_install_options");
+  for (const ExternalInstallOptions& option : desired_apps_install_options) {
+    desired_installs->Append(option.install_url.spec());
+  }
   std::vector<GURL> installed_urls;
   for (const auto& apps_it :
        lock.registrar().GetExternallyInstalledApps(install_source)) {
@@ -499,15 +520,23 @@
 
   std::sort(installed_urls.begin(), installed_urls.end());
 
+  base::Value::List* desired_urls_debug = debug_info.EnsureList("desired_urls");
   std::vector<GURL> desired_urls;
   desired_urls.reserve(desired_apps_install_options.size());
-  for (const auto& info : desired_apps_install_options)
+  for (const auto& info : desired_apps_install_options) {
     desired_urls.push_back(info.install_url);
+    desired_urls_debug->Append(info.install_url.spec());
+  }
 
   std::sort(desired_urls.begin(), desired_urls.end());
 
   std::vector<GURL> urls_to_remove =
       base::STLSetDifference<std::vector<GURL>>(installed_urls, desired_urls);
+  base::Value::List* urls_to_remove_debug =
+      debug_info.EnsureList("urls_to_remove");
+  for (const GURL& url_to_remove : urls_to_remove) {
+    urls_to_remove_debug->Append(url_to_remove.spec());
+  }
 
 #if BUILDFLAG(IS_CHROMEOS)
   // This check ensures that on Chrome OS, the messages app is not uninstalled
@@ -533,7 +562,7 @@
         FROM_HERE,
         base::BindOnce(std::move(callback), std::map<GURL, InstallResult>(),
                        std::map<GURL, bool>()));
-    return;
+    return base::Value(std::move(debug_info));
   }
 
   // Add the callback to a map and call once all installs/uninstalls finish.
@@ -553,6 +582,7 @@
             &ExternallyManagedAppManager::UninstallForSynchronizeCallback,
             weak_ptr_factory_.GetWeakPtr(), install_source));
   }
+  return base::Value(std::move(debug_info));
 }
 
 void ExternallyManagedAppManager::SetRegistrationCallbackForTesting(
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.h b/chrome/browser/web_applications/externally_managed_app_manager.h
index 98168c2..b15daef7 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager.h
@@ -21,6 +21,10 @@
 class GURL;
 class Profile;
 
+namespace base {
+class Value;
+}
+
 namespace webapps {
 enum class InstallResultCode;
 }
@@ -212,7 +216,7 @@
     std::map<GURL, bool> uninstall_results;
   };
 
-  void SynchronizeInstalledAppsOnLockAcquired(
+  base::Value SynchronizeInstalledAppsOnLockAcquired(
       std::vector<ExternalInstallOptions> desired_apps_install_options,
       ExternalInstallSource install_source,
       SynchronizeCallback callback,
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc b/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
index 7a6473a..34f0afb 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
@@ -455,8 +455,8 @@
     install_page_state.url_load_result = WebAppUrlLoaderResult::kUrlLoaded;
     install_page_state.redirection_url = absl::nullopt;
 
-    install_page_state.page_install_info =
-        std::make_unique<WebAppInstallInfo>();
+    install_page_state.page_install_info = std::make_unique<WebAppInstallInfo>(
+        GenerateManifestIdFromStartUrlOnly(start_url));
     install_page_state.page_install_info->title = u"Basic app title";
 
     install_page_state.manifest_url = manifest_url;
@@ -466,6 +466,9 @@
     install_page_state.opt_manifest->scope =
         url::Origin::Create(start_url).GetURL();
     install_page_state.opt_manifest->start_url = start_url;
+    install_page_state.opt_manifest->id =
+        GenerateManifestIdFromStartUrlOnly(start_url);
+
     install_page_state.opt_manifest->display =
         blink::mojom::DisplayMode::kStandalone;
     install_page_state.opt_manifest->short_name = u"Basic app name";
@@ -667,7 +670,8 @@
         web_contents_manager().GetOrCreatePageState(kInstallUrl);
     install_page_state.opt_manifest->short_name = u"Test user app";
 
-    auto install_info = std::make_unique<WebAppInstallInfo>();
+    auto install_info = std::make_unique<WebAppInstallInfo>(
+        GenerateManifestIdFromStartUrlOnly(kStartUrl));
     install_info->start_url = kStartUrl;
     install_info->title = u"Test user app";
     absl::optional<AppId> user_app_id =
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.cc
index b389b47..2bafdbb 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.cc
@@ -69,14 +69,6 @@
   return result == WebAppUrlLoader::Result::kUrlLoaded;
 }
 
-absl::optional<std::string> UTF16ToUTF8(base::StringPiece16 src) {
-  std::string dest;
-  if (!base::UTF16ToUTF8(src.data(), src.length(), &dest)) {
-    return absl::nullopt;
-  }
-  return dest;
-}
-
 }  // namespace
 
 InstallIsolatedWebAppCommand::InstallIsolatedWebAppCommand(
@@ -277,23 +269,18 @@
 InstallIsolatedWebAppCommand::CreateInstallInfoFromManifest(
     const blink::mojom::Manifest& manifest,
     const GURL& manifest_url) {
-  WebAppInstallInfo info;
+  if (!manifest.id.is_valid()) {
+    return base::unexpected(
+        "Manifest `id` is not present or invalid. manifest_url: " +
+        manifest_url.possibly_invalid_spec());
+  }
+
+  WebAppInstallInfo info(manifest.id);
   UpdateWebAppInfoFromManifest(manifest, manifest_url, &info);
 
-  if (!manifest.id.has_value()) {
-    return base::unexpected("Manifest `id` is not present. manifest_url: " +
-                            manifest_url.possibly_invalid_spec());
-  }
+  std::string encoded_id = manifest.id.path();
 
-  // In other installations the best-effort encoding is fine, but for isolated
-  // apps we have the opportunity to report this error.
-  absl::optional<std::string> encoded_id = UTF16ToUTF8(*manifest.id);
-  if (!encoded_id.has_value()) {
-    return base::unexpected(
-        "Failed to convert manifest `id` from UTF16 to UTF8.");
-  }
-
-  if (!encoded_id->empty()) {
+  if (encoded_id != "/") {
     // Recommend to use "/" for manifest id and not empty manifest id because
     // the manifest parser does additional work on resolving manifest id taking
     // `start_url` into account. (See https://w3c.github.io/manifest/#id-member
@@ -304,7 +291,7 @@
     // to identify Isolated Web Apps by origin because there is always only 1
     // app per origin.
     return base::unexpected(
-        R"(Manifest `id` must be "/". Resolved manifest id: )" + *encoded_id);
+        R"(Manifest `id` must be "/". Resolved manifest id: )" + encoded_id);
   }
 
   url::Origin origin = url_info_.origin();
@@ -321,7 +308,6 @@
          manifest_url.possibly_invalid_spec()}));
   }
 
-  info.manifest_id = "";
   info.user_display_mode = mojom::UserDisplayMode::kStandalone;
 
   return info;
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command_unittest.cc
index c7b0685..cf888a0d 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command_unittest.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -127,7 +128,7 @@
 
 blink::mojom::ManifestPtr CreateDefaultManifest(const GURL& application_url) {
   auto manifest = blink::mojom::Manifest::New();
-  manifest->id = u"";
+  manifest->id = application_url.DeprecatedGetOriginAsURL();
   manifest->scope = application_url.Resolve("/");
   manifest->start_url = application_url.Resolve("/testing-start-url.html");
   manifest->display = DisplayMode::kStandalone;
@@ -686,40 +687,10 @@
     InstallIsolatedWebAppCommandTest;
 
 TEST_F(InstallIsolatedWebAppCommandManifestTest,
-       InstallationFailsWhenManifestHasNoId) {
-  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
-  blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest(url_info.origin().GetURL());
-  manifest->id = absl::nullopt;
-
-  EXPECT_THAT(
-      ExecuteCommandWithManifest(url_info, manifest.Clone()),
-      IsInstallationError(HasSubstr(
-          "Manifest `id` is not present. manifest_url: " +
-          CreateDefaultManifestURL(url_info.origin().GetURL()).spec())));
-
-  EXPECT_THAT(web_app_registrar().GetAppById(url_info.app_id()), IsNull());
-}
-
-TEST_F(InstallIsolatedWebAppCommandManifestTest,
-       FailsWhenManifestIdHasInvalidUTF8Character) {
-  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
-  blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest(url_info.origin().GetURL());
-  char16_t invalid_utf8_chars = {0xD801};
-  manifest->id = std::u16string{invalid_utf8_chars};
-
-  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
-              IsInstallationError(HasSubstr(
-                  "Failed to convert manifest `id` from UTF16 to UTF8")));
-}
-
-TEST_F(InstallIsolatedWebAppCommandManifestTest,
        PassesManifestIdToFinalizerWhenManifestIdIsEmpty) {
   IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
       CreateDefaultManifest(url_info.origin().GetURL());
-  manifest->id = u"";
 
   EXPECT_TRUE(
       ExecuteCommandWithManifest(url_info, manifest.Clone()).has_value());
@@ -732,7 +703,7 @@
   IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
       CreateDefaultManifest(url_info.origin().GetURL());
-  manifest->id = u"test-manifest-id";
+  manifest->id = url_info.origin().GetURL().Resolve("/test-manifest-id");
 
   EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationError(HasSubstr(R"(Manifest `id` must be "/")")));
@@ -1138,7 +1109,7 @@
   IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
       CreateDefaultManifest(url_info.origin().GetURL());
-  manifest->id = u"test manifest id";
+  manifest->id = url_info.origin().GetURL().Resolve("/test manifest id");
 
   base::HistogramTester histogram_tester;
 
diff --git a/chrome/browser/web_applications/manifest_update_manager.cc b/chrome/browser/web_applications/manifest_update_manager.cc
index e970b4f..de4c37bd 100644
--- a/chrome/browser/web_applications/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/manifest_update_manager.cc
@@ -229,6 +229,13 @@
     return;
   }
 
+  if (registrar_->IsIsolated(*app_id)) {
+    // Manifests of Isolated Web Apps are only updated when a new version of the
+    // app is installed.
+    NotifyResult(url, *app_id, ManifestUpdateResult::kAppIsIsolatedWebApp);
+    return;
+  }
+
   if (base::Contains(update_stages_, *app_id)) {
     return;
   }
diff --git a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
index e98eb23..6bd51123 100644
--- a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/manifest_update_manager.h"
 
 #include <ios>
@@ -44,10 +43,13 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_browser_controller.h"
+#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
@@ -268,7 +270,7 @@
   }
 
   void OnResult(const GURL& url, ManifestUpdateResult result) {
-    if (url != *url_) {
+    if (url != url_) {
       SetCallback();
       return;
     }
@@ -277,7 +279,7 @@
   }
 
  private:
-  const raw_ref<const GURL> url_;
+  const GURL url_;
   base::RunLoop run_loop_;
   absl::optional<ManifestUpdateResult> result_;
 };
@@ -2268,6 +2270,34 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+class ManifestUpdateManagerIsolatedWebAppBrowserTest
+    : public IsolatedWebAppBrowserTestHarness {
+ public:
+  ManifestUpdateManagerIsolatedWebAppBrowserTest() {
+    isolated_web_app_dev_server_ =
+        CreateAndStartServer(FILE_PATH_LITERAL("web_apps/simple_isolated_app"));
+  }
+
+ protected:
+  std::unique_ptr<net::EmbeddedTestServer> isolated_web_app_dev_server_;
+  base::HistogramTester histogram_tester_;
+};
+
+IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerIsolatedWebAppBrowserTest,
+                       CheckUpdateSkipped) {
+  IsolatedWebAppUrlInfo url_info_ = InstallDevModeProxyIsolatedWebApp(
+      isolated_web_app_dev_server_->GetOrigin());
+
+  UpdateCheckResultAwaiter awaiter(
+      url_info_.origin().GetURL().Resolve("/index.html"));
+  EXPECT_TRUE(OpenApp(url_info_.app_id()));
+  EXPECT_EQ(std::move(awaiter).AwaitNextResult(),
+            ManifestUpdateResult::kAppIsIsolatedWebApp);
+
+  histogram_tester_.ExpectBucketCount(
+      kUpdateHistogramName, ManifestUpdateResult::kAppIsIsolatedWebApp, 1);
+}
+
 using ManifestUpdateManagerWebAppsBrowserTest =
     ManifestUpdateManagerBrowserTest;
 
@@ -4024,12 +4054,8 @@
   AppId app_id = InstallWebApp();
 
   // manifest_id should default to start_url when it's not provided in manifest.
-  EXPECT_EQ(GetProvider()
-                .registrar_unsafe()
-                .GetAppById(app_id)
-                ->manifest_id()
-                .value(),
-            "start");
+  EXPECT_EQ(GetProvider().registrar_unsafe().GetAppById(app_id)->manifest_id(),
+            http_server_.GetURL("/start"));
 
   constexpr char kManifestTemplate2[] = R"(
     {
@@ -4052,43 +4078,6 @@
                                       ManifestUpdateResult::kAppUpToDate, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_ManifestId,
-                       AllowManifestIdUpdateWhenAppIdIsNotChanged) {
-  constexpr char kManifestTemplate[] = R"(
-    {
-      "name": "Test app name",
-      "id": "$1",
-      "start_url": "/start",
-      "scope": "/",
-      "display": "standalone",
-      "icons": $2
-    }
-  )";
-  OverrideManifest(kManifestTemplate, {"start", kInstallableIconList});
-  AppId app_id = InstallWebApp();
-  // Manually set manifest_id to null. manifest_id can be null when the app is
-  // sync installed from older versions of Chromium.
-  {
-    ScopedRegistryUpdate update(&GetProvider().sync_bridge_unsafe());
-    WebApp* app = update->UpdateApp(app_id);
-    app->SetManifestId(absl::nullopt);
-  }
-  EXPECT_FALSE(GetProvider()
-                   .registrar_unsafe()
-                   .GetAppById(app_id)
-                   ->manifest_id()
-                   .has_value());
-  // Reload page to trigger an manifest update that re-fetches the manifest with
-  // id specified to be same as the default start_url.
-  EXPECT_EQ(GetResultAfterPageLoad(GetAppURL()),
-            ManifestUpdateResult::kAppUpdated);
-  EXPECT_TRUE(GetProvider()
-                  .registrar_unsafe()
-                  .GetAppById(app_id)
-                  ->manifest_id()
-                  .has_value());
-}
-
 class ManifestUpdateManagerBrowserTest_ScopeExtensions
     : public ManifestUpdateManagerBrowserTest {
  public:
diff --git a/chrome/browser/web_applications/manifest_update_utils.cc b/chrome/browser/web_applications/manifest_update_utils.cc
index 45a2aa1..a73672b 100644
--- a/chrome/browser/web_applications/manifest_update_utils.cc
+++ b/chrome/browser/web_applications/manifest_update_utils.cc
@@ -52,6 +52,8 @@
       return os << "kSystemShutdown";
     case ManifestUpdateResult::kAppIdentityUpdateRejectedAndUninstalled:
       return os << "kAppIdentityUpdateRejectedAndUninstalled";
+    case ManifestUpdateResult::kAppIsIsolatedWebApp:
+      return os << "kAppIsIsolatedWebApp";
   }
 }
 
diff --git a/chrome/browser/web_applications/manifest_update_utils.h b/chrome/browser/web_applications/manifest_update_utils.h
index c1b9f93..8100e8a 100644
--- a/chrome/browser/web_applications/manifest_update_utils.h
+++ b/chrome/browser/web_applications/manifest_update_utils.h
@@ -39,7 +39,8 @@
   // kAppAssociationsUpdated = 14,
   kSystemShutdown = 15,
   kAppIdentityUpdateRejectedAndUninstalled = 16,
-  kMaxValue = kAppIdentityUpdateRejectedAndUninstalled,
+  kAppIsIsolatedWebApp = 17,
+  kMaxValue = kAppIsIsolatedWebApp,
 };
 
 std::ostream& operator<<(std::ostream& os, ManifestUpdateResult result);
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 36125bc..1ca594d 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -563,13 +563,13 @@
 
 RunOnOsLoginPolicy WebAppPolicyManager::GetUrlRunOnOsLoginPolicy(
     const AppId& app_id) const {
-  return GetUrlRunOnOsLoginPolicyByUnhashedAppId(
-      app_registrar_->GetComputedUnhashedAppId(app_id));
+  return GetUrlRunOnOsLoginPolicyByManifestId(
+      app_registrar_->GetComputedManifestId(app_id).spec());
 }
 
-RunOnOsLoginPolicy WebAppPolicyManager::GetUrlRunOnOsLoginPolicyByUnhashedAppId(
-    const std::string& unhashed_app_id) const {
-  auto it = settings_by_url_.find(unhashed_app_id);
+RunOnOsLoginPolicy WebAppPolicyManager::GetUrlRunOnOsLoginPolicyByManifestId(
+    const std::string& manifest_id) const {
+  auto it = settings_by_url_.find(manifest_id);
   if (it != settings_by_url_.end())
     return it->second.run_on_os_login_policy;
   return default_settings_->run_on_os_login_policy;
@@ -615,16 +615,12 @@
     return;
 
   // For policy-installed apps there are two ways for getting to the manifest:
-  // via the policy install URL, or via the manifest-specified start_url
+  // via the policy install URL, or via the manifest-specified identity
   // of an already installed app. Websites without a manifest will use the
   // policy-installed URL as start_url, so they are covered by the first case.
   // Second case first:
-  if (!manifest->start_url.is_empty()) {
-    const absl::optional<std::string> manifest_id =
-        manifest->id.has_value() ? absl::optional<std::string>(
-                                       base::UTF16ToUTF8(manifest->id.value()))
-                                 : absl::nullopt;
-    const AppId& app_id = GenerateAppId(manifest_id, manifest->start_url);
+  if (manifest->id.is_valid()) {
+    const AppId& app_id = GenerateAppIdFromManifestId(manifest->id);
     // List of policy-installed apps and their install URLs:
     base::flat_map<AppId, base::flat_set<GURL>> policy_installed_apps =
         app_registrar_->GetExternallyInstalledApps(
@@ -660,9 +656,8 @@
     return false;
   }
 
-  const std::string unhashed_id =
-      app_registrar_->GetComputedUnhashedAppId(app_id);
-  auto it = settings_by_url_.find(unhashed_id);
+  const ManifestId manifest_id = app_registrar_->GetComputedManifestId(app_id);
+  auto it = settings_by_url_.find(manifest_id.spec());
   if (it != settings_by_url_.end()) {
     return it->second.prevent_close;
   }
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 7af2a843..15571a6 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -155,8 +155,8 @@
 
   void OverrideManifest(const GURL& custom_values_key,
                         blink::mojom::ManifestPtr& manifest) const;
-  RunOnOsLoginPolicy GetUrlRunOnOsLoginPolicyByUnhashedAppId(
-      const std::string& unhashed_app_id) const;
+  RunOnOsLoginPolicy GetUrlRunOnOsLoginPolicyByManifestId(
+      const std::string& manifest_id) const;
 
   // Parses install options from a `base::Value::Dict`, which represents one
   // entry of the kWepAppInstallForceList. If the value contains a custom_name
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
index 29dd0e7..b08fd7db 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -243,6 +243,7 @@
   blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
   manifest->name = base::UTF8ToUTF16(std::string(kDefaultAppName));
   manifest->start_url = GURL(kStartUrl);
+  manifest->id = GenerateManifestIdFromStartUrlOnly(manifest->start_url);
   // Populate manifest with 2 icons:
   blink::Manifest::ImageResource icon;
   icon.src = GURL(kDefaultAppIconUrl1);
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
index 601ddbd..711bc7e 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_unittest.cc
@@ -450,9 +450,9 @@
 
   void InstallPwa(const std::string& url) {
     std::unique_ptr<WebAppInstallInfo> web_app_info =
-        std::make_unique<WebAppInstallInfo>();
+        std::make_unique<WebAppInstallInfo>(
+            GenerateManifestIdFromStartUrlOnly(GURL(url)));
     web_app_info->start_url = GURL(url);
-    web_app_info->manifest_id = "";
     web_app::test::InstallWebApp(profile(), std::move(web_app_info));
   }
 
@@ -515,15 +515,13 @@
     install_result_code_ = result_code;
   }
 
-  RunOnOsLoginPolicy GetUrlRunOnOsLoginPolicy(
-      const std::string& unhashed_app_id) {
-    return policy_manager().GetUrlRunOnOsLoginPolicyByUnhashedAppId(
-        unhashed_app_id);
+  RunOnOsLoginPolicy GetUrlRunOnOsLoginPolicy(const std::string& manifest_id) {
+    return policy_manager().GetUrlRunOnOsLoginPolicyByManifestId(manifest_id);
   }
 
-  bool IsPreventCloseEnabled(const std::string& unhashed_app_id) {
+  bool IsPreventCloseEnabled(const std::string& manifest_id) {
     return policy_manager().IsPreventCloseEnabled(
-        web_app::GenerateAppIdFromUnhashed(unhashed_app_id));
+        web_app::GenerateAppIdFromManifestId(GURL(manifest_id)));
   }
 
   void WaitForAppsToSynchronize() {
@@ -1423,7 +1421,7 @@
     }
   ])";
 
-  // Make sure that WebAppRegistrar::GetComputedUnhashedAppId does not fail.
+  // Make sure that WebAppRegistrar::GetComputedManifestId does not fail.
   InstallPwa(kWindowedUrl);
   InstallPwa(kTabbedUrl);
   InstallPwa(kNoContainerUrl);
diff --git a/chrome/browser/web_applications/test/fake_data_retriever.cc b/chrome/browser/web_applications/test/fake_data_retriever.cc
index 2e34ede..de5d1b0 100644
--- a/chrome/browser/web_applications/test/fake_data_retriever.cc
+++ b/chrome/browser/web_applications/test/fake_data_retriever.cc
@@ -10,6 +10,7 @@
 #include "base/functional/bind.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "components/webapps/browser/installable/installable_logging.h"
 #include "components/webapps/browser/installable/installable_params.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -110,6 +111,7 @@
 
   auto manifest = blink::mojom::Manifest::New();
   manifest->start_url = url;
+  manifest->id = GenerateManifestIdFromStartUrlOnly(manifest->start_url);
   manifest->scope = scope;
   manifest->display = DisplayMode::kStandalone;
   manifest->short_name = u"Manifest Name";
diff --git a/chrome/browser/web_applications/test/web_app_test_utils.cc b/chrome/browser/web_applications/test/web_app_test_utils.cc
index 68037bb1..3cffcd9 100644
--- a/chrome/browser/web_applications/test/web_app_test_utils.cc
+++ b/chrome/browser/web_applications/test/web_app_test_utils.cc
@@ -518,12 +518,20 @@
   RandomHelper random(seed);
 
   const std::string seed_str = base::NumberToString(seed);
-  absl::optional<std::string> manifest_id;
-  if (random.next_bool())
-    manifest_id = "manifest_id_" + seed_str;
+  absl::optional<std::string> relative_manifest_id;
+  if (random.next_bool()) {
+    std::string path = "manifest_id_" + seed_str;
+    if (random.next_bool()) {
+      path += "?query=test";
+    }
+    if (random.next_bool()) {
+      path += "#fragment";
+    }
+    relative_manifest_id = path;
+  }
   const GURL scope = base_url.Resolve("scope" + seed_str + "/");
   const GURL start_url = scope.Resolve("start" + seed_str);
-  const AppId app_id = GenerateAppId(manifest_id, start_url);
+  const AppId app_id = GenerateAppId(relative_manifest_id, start_url);
 
   const std::string name = "Name" + seed_str;
   const std::string description = "Description" + seed_str;
@@ -595,7 +603,10 @@
 
   app->SetName(name);
   app->SetDescription(description);
-  app->SetManifestId(manifest_id);
+  if (relative_manifest_id) {
+    app->SetManifestId(
+        GenerateManifestId(relative_manifest_id.value(), start_url));
+  }
   app->SetStartUrl(GURL(start_url));
   app->SetScope(GURL(scope));
   app->SetThemeColor(theme_color);
@@ -884,7 +895,7 @@
   // Depending on the installability criteria, different dialogs can be used.
   chrome::SetAutoAcceptWebAppDialogForTesting(true, true);
   chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
-  WebAppTestInstallObserver observer(browser->profile());
+  WebAppTestInstallWithOsHooksObserver observer(browser->profile());
   observer.BeginListening();
   CHECK(chrome::ExecuteCommand(browser, IDC_INSTALL_PWA));
   AppId app_id = observer.Wait();
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index eff179a..c3baf24 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -8,6 +8,7 @@
 #include <tuple>
 #include <utility>
 
+#include "base/check_is_test.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
 #include "base/functional/overloaded.h"
@@ -32,6 +33,7 @@
 #include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/color_utils.h"
+#include "url/origin.h"
 
 namespace web_app {
 
@@ -239,6 +241,20 @@
   }
 }
 
+ManifestId WebApp::manifest_id() const {
+  // Almost all production use-cases should have the manifest_id set, but in
+  // some test it is not. If the manifest id is not set, then fall back to the
+  // start_url, as per the algorithm in
+  // https://www.w3.org/TR/appmanifest/#id-member.
+  if (manifest_id_.is_empty()) {
+    CHECK_IS_TEST();
+    // This is why the function must return a value instead of a const ref, as
+    // this object would be temporary.
+    return GenerateManifestIdFromStartUrlOnly(start_url_);
+  }
+  return manifest_id_;
+}
+
 void WebApp::AddSource(WebAppManagement::Type source) {
   sources_[source] = true;
 }
@@ -323,7 +339,13 @@
 }
 
 void WebApp::SetStartUrl(const GURL& start_url) {
-  DCHECK(start_url.is_valid());
+  CHECK(start_url.is_valid());
+  if (manifest_id_.is_empty()) {
+    manifest_id_ = GenerateManifestIdFromStartUrlOnly(start_url);
+  }
+  CHECK(url::Origin::Create(manifest_id())
+            .IsSameOriginWith(url::Origin::Create(start_url)))
+      << manifest_id().spec() << " " << start_url.spec();
   start_url_ = start_url;
 }
 
@@ -525,7 +547,12 @@
   manifest_url_ = manifest_url;
 }
 
-void WebApp::SetManifestId(const absl::optional<std::string>& manifest_id) {
+void WebApp::SetManifestId(const ManifestId& manifest_id) {
+  CHECK(manifest_id.is_valid());
+  CHECK(start_url_.is_empty() ||
+        url::Origin::Create(start_url_)
+            .IsSameOriginWith(url::Origin::Create(manifest_id)))
+      << start_url_.spec() << " vs " << manifest_id.spec();
   manifest_id_ = manifest_id;
 }
 
@@ -954,8 +981,6 @@
 
   root.Set("launch_query_params", ConvertOptional(launch_query_params_));
 
-  root.Set("manifest_id", ConvertOptional(manifest_id_));
-
   root.Set("manifest_update_time", base::ToString(manifest_update_time_));
 
   root.Set("manifest_url", base::ToString(manifest_url_));
@@ -1023,7 +1048,7 @@
 
   root.Set("theme_color", ColorToString(theme_color_));
 
-  root.Set("unhashed_app_id", GenerateAppIdUnhashed(manifest_id_, start_url_));
+  root.Set("manifest_id", manifest_id_.spec());
 
   root.Set("url_handlers", ConvertDebugValueList(url_handlers_));
 
diff --git a/chrome/browser/web_applications/web_app.h b/chrome/browser/web_applications/web_app.h
index 86a3371..d7a0a510 100644
--- a/chrome/browser/web_applications/web_app.h
+++ b/chrome/browser/web_applications/web_app.h
@@ -260,9 +260,7 @@
 
   const GURL& manifest_url() const { return manifest_url_; }
 
-  const absl::optional<std::string>& manifest_id() const {
-    return manifest_id_;
-  }
+  ManifestId manifest_id() const;
 
   const absl::optional<LaunchHandler>& launch_handler() const {
     return launch_handler_;
@@ -425,7 +423,7 @@
   void SetSyncFallbackData(SyncFallbackData sync_fallback_data);
   void SetCaptureLinks(blink::mojom::CaptureLinks capture_links);
   void SetManifestUrl(const GURL& manifest_url);
-  void SetManifestId(const absl::optional<std::string>& manifest_id);
+  void SetManifestId(const ManifestId& manifest_id);
   void SetWindowControlsOverlayEnabled(bool enabled);
   void SetLaunchHandler(absl::optional<LaunchHandler> launch_handler);
   void SetParentAppId(const absl::optional<AppId>& parent_app_id);
@@ -542,7 +540,7 @@
       blink::mojom::CaptureLinks::kUndefined;
   ClientData client_data_;
   GURL manifest_url_;
-  absl::optional<std::string> manifest_id_;
+  ManifestId manifest_id_;
   // The state of the user's approval of the app's use of the File Handler API.
   ApiApprovalState file_handler_approval_state_ =
       ApiApprovalState::kRequiresPrompt;
diff --git a/chrome/browser/web_applications/web_app_database.cc b/chrome/browser/web_applications/web_app_database.cc
index b546aca..803630c 100644
--- a/chrome/browser/web_applications/web_app_database.cc
+++ b/chrome/browser/web_applications/web_app_database.cc
@@ -396,9 +396,10 @@
 
   // Required fields:
   const GURL start_url = web_app.start_url();
-  DCHECK(!start_url.is_empty() && start_url.is_valid());
+  DCHECK(start_url.is_valid());
 
   DCHECK(!web_app.app_id().empty());
+  DCHECK(web_app.manifest_id().is_valid());
 
   // Set sync data to sync proto.
   *(local_data->mutable_sync_data()) = WebAppToSyncProto(web_app);
@@ -849,15 +850,18 @@
     return nullptr;
   }
 
-  absl::optional<std::string> manifest_id = absl::nullopt;
-  if (sync_data.has_manifest_id())
-    manifest_id = absl::optional<std::string>(sync_data.manifest_id());
+  ManifestId manifest_id;
+  if (sync_data.has_relative_manifest_id()) {
+    manifest_id =
+        GenerateManifestId(sync_data.relative_manifest_id(), start_url);
+  } else {
+    manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
+  }
 
-  const AppId app_id = GenerateAppId(manifest_id, start_url);
+  AppId app_id = GenerateAppIdFromManifestId(manifest_id);
 
   auto web_app = std::make_unique<WebApp>(app_id);
   web_app->SetStartUrl(start_url);
-
   web_app->SetManifestId(manifest_id);
 
   // Required fields:
@@ -1663,7 +1667,9 @@
   }
 
   if (web_app->app_id() != app_id) {
-    DLOG(ERROR) << "WebApps LevelDB error: app_id doesn't match storage key";
+    DLOG(ERROR) << "WebApps LevelDB error: app_id doesn't match storage key "
+                << app_id << " vs " << web_app->app_id() << ", from "
+                << web_app->manifest_id();
     return nullptr;
   }
 
diff --git a/chrome/browser/web_applications/web_app_database_unittest.cc b/chrome/browser/web_applications/web_app_database_unittest.cc
index 001706c..5451677 100644
--- a/chrome/browser/web_applications/web_app_database_unittest.cc
+++ b/chrome/browser/web_applications/web_app_database_unittest.cc
@@ -339,6 +339,7 @@
 
   // Required fields:
   app->SetStartUrl(start_url);
+  app->SetManifestId(GenerateManifestIdFromStartUrlOnly(start_url));
   app->SetName(name);
   app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
   app->SetIsLocallyInstalled(false);
@@ -389,7 +390,6 @@
   EXPECT_EQ(app->run_on_os_login_mode(), RunOnOsLoginMode::kNotRun);
   EXPECT_FALSE(app->run_on_os_login_os_integration_state().has_value());
   EXPECT_TRUE(app->manifest_url().is_empty());
-  EXPECT_FALSE(app->manifest_id().has_value());
   EXPECT_TRUE(app->permissions_policy().empty());
   EXPECT_FALSE(app->isolation_data().has_value());
   RegisterApp(std::move(app));
@@ -401,6 +401,8 @@
 
   // Required fields were serialized:
   EXPECT_EQ(app_id, app_copy->app_id());
+  EXPECT_EQ(GenerateManifestIdFromStartUrlOnly(start_url),
+            app_copy->manifest_id());
   EXPECT_EQ(start_url, app_copy->start_url());
   EXPECT_EQ(name, app_copy->untranslated_name());
   EXPECT_EQ(mojom::UserDisplayMode::kBrowser, app_copy->user_display_mode());
@@ -459,7 +461,6 @@
   EXPECT_EQ(app_copy->run_on_os_login_mode(), RunOnOsLoginMode::kNotRun);
   EXPECT_FALSE(app_copy->run_on_os_login_os_integration_state().has_value());
   EXPECT_TRUE(app_copy->manifest_url().is_empty());
-  EXPECT_FALSE(app_copy->manifest_id().has_value());
   EXPECT_TRUE(app_copy->permissions_policy().empty());
   EXPECT_FALSE(app_copy->tab_strip());
 }
@@ -615,7 +616,6 @@
 
 TEST_F(WebAppDatabaseProtoDataTest, DoesNotSetIsolationDataIfNotIsolated) {
   std::unique_ptr<WebApp> web_app = CreateMinimalWebApp();
-
   std::unique_ptr<WebApp> protoed_web_app = ToAndFromProto(*web_app);
   EXPECT_THAT(*web_app,
               AllOf(Eq(*protoed_web_app),
diff --git a/chrome/browser/web_applications/web_app_helpers.cc b/chrome/browser/web_applications/web_app_helpers.cc
index 1a1252b..99e6b810 100644
--- a/chrome/browser/web_applications/web_app_helpers.cc
+++ b/chrome/browser/web_applications/web_app_helpers.cc
@@ -51,65 +51,44 @@
   return app_name.substr(prefix.length());
 }
 
-AppId GenerateAppIdFromUnhashed(std::string unhashed_app_id) {
-  DCHECK_EQ(GURL(unhashed_app_id).spec(), unhashed_app_id);
+AppId GenerateAppIdFromManifestId(const ManifestId& manifest_id) {
   // The app ID is hashed twice: here and in GenerateId.
   // The double-hashing is for historical reasons and it needs to stay
   // this way for backwards compatibility. (Back then, a web app's input to the
   // hash needed to be formatted like an extension public key.)
   return crx_file::id_util::GenerateId(
-      crypto::SHA256HashString(unhashed_app_id));
+      crypto::SHA256HashString(manifest_id.spec()));
 }
 
-std::string GenerateAppIdUnhashed(
-    const absl::optional<std::string>& manifest_id,
-    const GURL& start_url) {
+AppId GenerateAppId(const absl::optional<std::string>& manifest_id_path,
+                    const GURL& start_url) {
+  if (!manifest_id_path) {
+    return GenerateAppIdFromManifestId(
+        GenerateManifestIdFromStartUrlOnly(start_url));
+  }
+  return GenerateAppIdFromManifestId(
+      GenerateManifestId(manifest_id_path.value(), start_url));
+}
+
+ManifestId GenerateManifestId(const std::string& manifest_id_path,
+                              const GURL& start_url) {
   // When manifest_id is specified, the app id is generated from
-  // <start_url_origin>/<manifest_id>.
+  // <start_url_origin>/<manifest_id_path>.
   // Note: start_url.DeprecatedGetOriginAsURL().spec() returns the origin ending
   // with slash.
-  if (manifest_id.has_value()) {
-    GURL app_id(start_url.DeprecatedGetOriginAsURL().spec() +
-                manifest_id.value());
-    DCHECK(app_id.is_valid())
-        << "start_url: " << start_url << ", manifest_id = " << *manifest_id;
-    return app_id.spec();
-  }
-  return start_url.spec();
-}
-
-AppId GenerateAppId(const absl::optional<std::string>& manifest_id,
-                    const GURL& start_url) {
-  return GenerateAppIdFromUnhashed(
-      GenerateAppIdUnhashed(manifest_id, start_url));
-}
-
-std::string GenerateAppIdUnhashedFromManifest(
-    const blink::mojom::Manifest& manifest) {
-  return GenerateAppIdUnhashed(
-      manifest.id.has_value()
-          ? absl::optional<std::string>(base::UTF16ToUTF8(manifest.id.value()))
-          : absl::nullopt,
-      manifest.start_url);
+  GURL app_id(start_url.DeprecatedGetOriginAsURL().spec() + manifest_id_path);
+  CHECK(app_id.is_valid()) << "start_url: " << start_url
+                           << ", manifest_id = " << manifest_id_path;
+  return app_id.GetWithoutRef();
 }
 
 AppId GenerateAppIdFromManifest(const blink::mojom::Manifest& manifest) {
-  return GenerateAppIdFromUnhashed(GenerateAppIdUnhashedFromManifest(manifest));
+  CHECK(manifest.id.is_valid());
+  return GenerateAppIdFromManifestId(manifest.id);
 }
 
-std::string GenerateRecommendedId(const GURL& start_url) {
-  if (!start_url.is_valid()) {
-    return base::EmptyString();
-  }
-
-  std::string full_url = start_url.spec();
-  std::string origin = start_url.DeprecatedGetOriginAsURL().spec();
-  DCHECK(!full_url.empty() && !origin.empty() &&
-         origin.size() <= full_url.size());
-  // Make recommended id starts with a leading slash so it's clear to developers
-  // that it's a root-relative url path. In reality it's always root-relative
-  // because the base_url is the origin.
-  return full_url.substr(origin.size() - 1);
+ManifestId GenerateManifestIdFromStartUrlOnly(const GURL& start_url) {
+  return start_url.GetWithoutRef();
 }
 
 bool IsValidWebAppUrl(const GURL& app_url) {
diff --git a/chrome/browser/web_applications/web_app_helpers.h b/chrome/browser/web_applications/web_app_helpers.h
index 5dbc93a..83b08aa 100644
--- a/chrome/browser/web_applications/web_app_helpers.h
+++ b/chrome/browser/web_applications/web_app_helpers.h
@@ -29,37 +29,46 @@
 // Extracts the application id from the app name.
 AppId GetAppIdFromApplicationName(const std::string& app_name);
 
-// Compute the App ID (such as "fedbieoalmbobgfjapopkghdmhgncnaa") or
-// App Key, from a web app's URL. Both are derived from a hash of the
-// URL, but are subsequently encoded differently, for historical reasons. The
-// ID is a Base-16 encoded (a=0, b=1, ..., p=15) subset of the hash, and is
-// used as a directory name, sometimes on case-insensitive file systems
-// (Windows). The Key is a Base-64 encoding of the hash.
+// Compute the AppId using the given start_url and optional manifest
+// id path, which is the path component of the manifest id defined by the spec.
+// This mimics what is given to the spec algorithm as the json manifest_id in
+// https://www.w3.org/TR/appmanifest/#id-member. The `manifest_id_path` can
+// include query arguments and/or fragments, although the fragment will be
+// removed. See the `AppId` type for more information.
 //
-// For PWAs (progressive web apps), the URL should be the Start URL, explicitly
-// listed in the manifest.
+// This should only be used if a `Manifest` object is not available.
 //
-// For non-PWA web apps, also known as "shortcuts", the URL is just the
-// bookmark URL.
-//
-// App ID and App Key match Extension ID and Extension Key for migration.
-
-// Generate App id using manifest_id, if null, use start_url instead.
-AppId GenerateAppId(const absl::optional<std::string>& manifest_id,
+// TODO(b/281881755): Change the optional parameter to required, and refactor
+// calls with absl::nullopt to `GenerateManifestIdFromStartUrlOnly`.
+AppId GenerateAppId(const absl::optional<std::string>& manifest_id_path,
                     const GURL& start_url);
-std::string GenerateAppIdUnhashed(
-    const absl::optional<std::string>& manifest_id,
-    const GURL& start_url);
-AppId GenerateAppIdFromUnhashed(std::string unhashed_app_id);
 
-std::string GenerateAppIdUnhashedFromManifest(
-    const blink::mojom::Manifest& manifest);
+// Returns a resolved manifest id given the relative `manifest_id_path`,
+// as per the spec algorithm at https://www.w3.org/TR/appmanifest/#id-member.
+// The `manifest_id_path` can include query arguments and/or fragments, although
+// the fragment will be removed. If there is no `manifest_id_path`, then
+// GenerateManifestIdFromStartUrlOnly can be used.
+//
+// This should only be used if a `Manifest` object is not available.
+ManifestId GenerateManifestId(const std::string& manifest_id_path,
+                              const GURL& start_url);
 
+// Generates the chrome-specific `AppId` from the spec-defined manifest id. See
+// the `AppId` type for more information.
+AppId GenerateAppIdFromManifestId(const ManifestId& manifest_id);
+
+// Generates the chrome-specific `AppId` from the spec-defined manifest. See the
+// `AppId` type for more information. This will CHECK-fail if the `id` field is
+// not present on the manifest.
 AppId GenerateAppIdFromManifest(const blink::mojom::Manifest& manifest);
 
-// Suggests recommended id to be specified to match with computed |app_id|
-// generated from start_url.
-std::string GenerateRecommendedId(const GURL& start_url);
+// Generates a manifest id by only the start_url, which matches the spec
+// algorithm in https://www.w3.org/TR/appmanifest/#id-member where the `id` json
+// member is not present or an empty string. To include an identifier path,
+// please use `GenerateManifestId`.
+//
+// This should only be used if a `Manifest` object is not available.
+ManifestId GenerateManifestIdFromStartUrlOnly(const GURL& start_url);
 
 // Returns whether the given |app_url| is a valid web app url.
 bool IsValidWebAppUrl(const GURL& app_url);
diff --git a/chrome/browser/web_applications/web_app_helpers_unittest.cc b/chrome/browser/web_applications/web_app_helpers_unittest.cc
index d0ebd054..732a6b2 100644
--- a/chrome/browser/web_applications/web_app_helpers_unittest.cc
+++ b/chrome/browser/web_applications/web_app_helpers_unittest.cc
@@ -32,12 +32,18 @@
                                "?utm_source=web_app_manifest")));
 }
 
-TEST(WebAppHelpers, GenerateRecommendedId) {
-  EXPECT_EQ("", GenerateRecommendedId(GURL()));
-  EXPECT_EQ("/", GenerateRecommendedId(GURL("https://example.com/")));
-  EXPECT_EQ("/", GenerateRecommendedId(GURL("https://example.com")));
-  EXPECT_EQ("/start?a=b",
-            GenerateRecommendedId(GURL("https://example.com/start?a=b")));
+TEST(WebAppHelpers, GenerateManifestIdFromStartUrlOnly) {
+  EXPECT_EQ(GURL(), GenerateManifestIdFromStartUrlOnly(GURL()));
+  EXPECT_EQ(GURL("https://example.com/"),
+            GenerateManifestIdFromStartUrlOnly(GURL("https://example.com/")));
+  EXPECT_EQ(GURL("https://example.com"),
+            GenerateManifestIdFromStartUrlOnly(GURL("https://example.com")));
+  EXPECT_EQ(GURL("https://example.com/start?a=b"),
+            GenerateManifestIdFromStartUrlOnly(
+                GURL("https://example.com/start?a=b")));
+  EXPECT_EQ(GURL("https://example.com/start"),
+            GenerateManifestIdFromStartUrlOnly(
+                GURL("https://example.com/start#fragment")));
 }
 
 TEST(WebAppHelpers, IsValidWebAppUrl) {
@@ -82,4 +88,26 @@
   // "/"" is excluded from encoding according to url spec.
   EXPECT_NE(GenerateAppId("a/b", start_url), GenerateAppId("a%2Fb", start_url));
 }
+
+TEST(WebAppHelpers, ManifestIdWithQueriesAndFragments) {
+  GURL start_url_long = GURL("https://example.com/start_url/long/path.html");
+  GURL url = GURL("https://example.com/test");
+  GURL url_with_query = GURL("https://example.com/test?id");
+  GURL url_with_fragment = GURL("https://example.com/test#id");
+  GURL url_with_query_and_fragment =
+      GURL("https://example.com/test?id#fragment");
+
+  EXPECT_EQ(url, GenerateManifestIdFromStartUrlOnly(url));
+  EXPECT_EQ(url, GenerateManifestIdFromStartUrlOnly(url_with_fragment));
+  EXPECT_EQ(url_with_query, GenerateManifestIdFromStartUrlOnly(url_with_query));
+  EXPECT_EQ(url_with_query,
+            GenerateManifestIdFromStartUrlOnly(url_with_query_and_fragment));
+
+  EXPECT_EQ(url, GenerateManifestId("test", start_url_long));
+  EXPECT_EQ(url, GenerateManifestId("test#id", start_url_long));
+  EXPECT_EQ(url_with_query, GenerateManifestId("test?id", start_url_long));
+  EXPECT_EQ(url_with_query,
+            GenerateManifestId("test?id#fragment", start_url_long));
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_id.h b/chrome/browser/web_applications/web_app_id.h
index 9185c715..c8220afb 100644
--- a/chrome/browser/web_applications/web_app_id.h
+++ b/chrome/browser/web_applications/web_app_id.h
@@ -7,15 +7,26 @@
 
 #include <string>
 
+class GURL;
+
 namespace web_app {
 
-// App ID matches Extension ID.
+// An example AppId id is "fedbieoalmbobgfjapopkghdmhgncnaa", and is derived
+// from the web app's ManifestId (see below).
+// This id starts with a URL which is then:
+// - hashed using SHA256,
+// - hashed using SHA256 again,
+// - hex encoded into the characters 0-f,
+// - transformed to only use alpha characters between a-p (inclusive).
+// This algorithm was designed for historical reasons and needs to stay this way
+// for backwards compatibility.
 using AppId = std::string;
 
-// Unhashed version of App ID. This can be hashed using
-// GenerateAppIdFromUnhashed(unhashed_app_id), see
-// chrome/browser/web_applications/web_app_helpers.h.
-using UnhashedAppId = std::string;
+// This is computed from the manifest's `start_url` and `id` members:
+// https://www.w3.org/TR/appmanifest/#id-member. This can be hashed using
+// GenerateAppIdFromManifestId in
+// chrome/browser/web_applications/web_app_helpers.h to produce an AppId above.
+using ManifestId = GURL;
 
 }  // namespace web_app
 
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 28b4932..edd6b4a 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/barrier_callback.h"
+#include "base/check_is_test.h"
 #include "base/containers/contains.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/bind.h"
@@ -33,6 +34,7 @@
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
+#include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
@@ -49,6 +51,7 @@
 #include "components/webapps/browser/uninstall_result_code.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_data.h"
@@ -130,10 +133,18 @@
     return;
   }
 
-  AppId app_id =
-      GenerateAppId(web_app_info.manifest_id, web_app_info.start_url);
-  std::string app_id_unhashed =
-      GenerateAppIdUnhashed(web_app_info.manifest_id, web_app_info.start_url);
+  ManifestId manifest_id = web_app_info.manifest_id;
+  if (manifest_id.is_valid()) {
+    CHECK(url::Origin::Create(manifest_id)
+              .IsSameOriginWith(url::Origin::Create(web_app_info.start_url)));
+  } else {
+    // TODO(b/280862254): After the manifest id constructor is required, this
+    // can be removed.
+    CHECK_IS_TEST();
+    manifest_id = GenerateManifestIdFromStartUrlOnly(web_app_info.start_url);
+  }
+
+  AppId app_id = GenerateAppIdFromManifestId(manifest_id);
   OnDidGetWebAppOriginAssociations origin_association_validated_callback =
       base::BindOnce(&WebAppInstallFinalizer::OnOriginAssociationValidated,
                      weak_ptr_factory_.GetWeakPtr(), web_app_info.Clone(),
@@ -147,7 +158,7 @@
   }
 
   origin_association_manager_->GetWebAppOriginAssociations(
-      GURL(app_id_unhashed), web_app_info.scope_extensions,
+      manifest_id, web_app_info.scope_extensions,
       std::move(origin_association_validated_callback));
 }
 
@@ -343,8 +354,18 @@
     InstallFinalizedCallback callback) {
   CHECK(started_);
 
-  const AppId app_id =
-      GenerateAppId(web_app_info.manifest_id, web_app_info.start_url);
+  ManifestId manifest_id = web_app_info.manifest_id;
+  if (manifest_id.is_valid()) {
+    CHECK(url::Origin::Create(manifest_id)
+              .IsSameOriginWith(url::Origin::Create(web_app_info.start_url)));
+  } else {
+    // TODO(b/280862254): After the manifest id constructor is required, this
+    // can be removed.
+    CHECK_IS_TEST();
+    manifest_id = GenerateManifestIdFromStartUrlOnly(web_app_info.start_url);
+  }
+
+  const AppId app_id = GenerateAppIdFromManifestId(manifest_id);
   const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id);
 
   if (!existing_web_app ||
diff --git a/chrome/browser/web_applications/web_app_install_info.cc b/chrome/browser/web_applications/web_app_install_info.cc
index 7464fb41..0c81b8c 100644
--- a/chrome/browser/web_applications/web_app_install_info.cc
+++ b/chrome/browser/web_applications/web_app_install_info.cc
@@ -6,6 +6,7 @@
 
 #include <sstream>
 
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "components/webapps/common/web_page_metadata.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/skia_util.h"
@@ -233,7 +234,8 @@
 WebAppInstallInfo WebAppInstallInfo::CreateInstallInfoForCreateShortcut(
     const GURL& document_url,
     const WebAppInstallInfo& other) {
-  WebAppInstallInfo create_shortcut_info;
+  WebAppInstallInfo create_shortcut_info(
+      web_app::GenerateManifestIdFromStartUrlOnly(document_url));
   create_shortcut_info.title = other.title;
   create_shortcut_info.description = other.description;
   create_shortcut_info.start_url = document_url;
@@ -256,6 +258,11 @@
 
 WebAppInstallInfo::WebAppInstallInfo() = default;
 
+WebAppInstallInfo::WebAppInstallInfo(const web_app::ManifestId& manifest_id)
+    : manifest_id(manifest_id) {
+  CHECK(manifest_id.is_valid());
+}
+
 WebAppInstallInfo::WebAppInstallInfo(const WebAppInstallInfo& other) = default;
 
 WebAppInstallInfo::WebAppInstallInfo(WebAppInstallInfo&&) = default;
@@ -264,7 +271,9 @@
 
 WebAppInstallInfo::WebAppInstallInfo(
     const webapps::mojom::WebPageMetadata& metadata)
-    : title(metadata.application_name),
+    : manifest_id(web_app::GenerateManifestIdFromStartUrlOnly(
+          metadata.application_url)),
+      title(metadata.application_name),
       description(metadata.description),
       start_url(metadata.application_url) {
   for (const auto& icon : metadata.icons) {
diff --git a/chrome/browser/web_applications/web_app_install_info.h b/chrome/browser/web_applications/web_app_install_info.h
index ff7ce9c..93cdc2e 100644
--- a/chrome/browser/web_applications/web_app_install_info.h
+++ b/chrome/browser/web_applications/web_app_install_info.h
@@ -185,8 +185,12 @@
       const GURL& document_url,
       const WebAppInstallInfo& other);
 
+  // TODO(b/280862254): Remove this constructor to force users to use specify
+  // the manifest_id.
   WebAppInstallInfo();
 
+  explicit WebAppInstallInfo(const web_app::ManifestId& manifest_id);
+
   // Deleted to prevent accidental copying. Use Clone() to deep copy explicitly.
   WebAppInstallInfo& operator=(const WebAppInstallInfo&) = delete;
 
@@ -200,7 +204,10 @@
   WebAppInstallInfo Clone() const;
 
   // Id specified in the manifest.
-  absl::optional<std::string> manifest_id;
+  // TODO(b/280862254): After the manifest id constructor is required, this can
+  // be guaranteed to be valid & non-empty.
+  // https://www.w3.org/TR/appmanifest/#id-member
+  web_app::ManifestId manifest_id;
 
   // Title of the application.
   std::u16string title;
diff --git a/chrome/browser/web_applications/web_app_install_params.h b/chrome/browser/web_applications/web_app_install_params.h
index c0dce263..d7ab25e6 100644
--- a/chrome/browser/web_applications/web_app_install_params.h
+++ b/chrome/browser/web_applications/web_app_install_params.h
@@ -65,11 +65,6 @@
   // URL to be used as start_url if manifest is unavailable.
   GURL fallback_start_url;
 
-  // Setting this field will force the webapp to have a manifest id, which
-  // will result in a different AppId than if it isn't set. Currently here
-  // to support forwards compatibility with future sync entities..
-  absl::optional<std::string> override_manifest_id;
-
   // App name to be used if manifest is unavailable.
   absl::optional<std::u16string> fallback_app_name;
 
diff --git a/chrome/browser/web_applications/web_app_install_utils.cc b/chrome/browser/web_applications/web_app_install_utils.cc
index 092f9018..b73842f 100644
--- a/chrome/browser/web_applications/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/web_app_install_utils.cc
@@ -43,6 +43,7 @@
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_chromeos_data.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_params.h"
@@ -627,9 +628,8 @@
   else if (manifest.short_name)
     web_app_info->title = *manifest.short_name;
 
-  if (manifest.id.has_value()) {
-    web_app_info->manifest_id =
-        absl::optional<std::string>(base::UTF16ToUTF8(manifest.id.value()));
+  if (manifest.id.is_valid()) {
+    web_app_info->manifest_id = manifest.id;
   }
 
   // Set the url based on the manifest value, if any.
@@ -759,6 +759,14 @@
   }
 }
 
+WebAppInstallInfo CreateWebAppInfoFromManifest(
+    const blink::mojom::Manifest& manifest,
+    const GURL& manifest_url) {
+  WebAppInstallInfo info(manifest.id);
+  UpdateWebAppInfoFromManifest(manifest, manifest_url, &info);
+  return info;
+}
+
 namespace {
 
 std::vector<GURL> GetAppIconUrls(const WebAppInstallInfo& web_app_info) {
@@ -1149,7 +1157,13 @@
   web_app.SetName(base::UTF16ToUTF8(web_app_info.title));
 
   web_app.SetStartUrl(web_app_info.start_url);
-  web_app.SetManifestId(web_app_info.manifest_id);
+
+  // TODO(b/280862254): CHECK that the manifest_id isn't empty after the empty
+  // constructor is removed. Currently, `SetStartUrl` sets a default manifest_id
+  // based on the start_url.
+  if (web_app_info.manifest_id.is_valid()) {
+    web_app.SetManifestId(web_app_info.manifest_id);
+  }
 
   web_app.SetDisplayMode(web_app_info.display_mode);
   web_app.SetDisplayModeOverride(web_app_info.display_override);
@@ -1274,9 +1288,6 @@
   if (install_params.user_display_mode.has_value())
     web_app_info.user_display_mode = install_params.user_display_mode;
 
-  if (install_params.override_manifest_id.has_value())
-    web_app_info.manifest_id = install_params.override_manifest_id;
-
   // If `additional_search_terms` was a manifest property, it would be
   // sanitized while parsing the manifest. Since it's not, we sanitize it
   // here.
diff --git a/chrome/browser/web_applications/web_app_install_utils.h b/chrome/browser/web_applications/web_app_install_utils.h
index ad1f63c9..900a0bb 100644
--- a/chrome/browser/web_applications/web_app_install_utils.h
+++ b/chrome/browser/web_applications/web_app_install_utils.h
@@ -58,6 +58,11 @@
                                   const GURL& manifest_url,
                                   WebAppInstallInfo* web_app_info);
 
+// Same as above, but returns a fresh WebAppInstallInfo.
+WebAppInstallInfo CreateWebAppInfoFromManifest(
+    const blink::mojom::Manifest& manifest,
+    const GURL& manifest_url);
+
 // Form a list of icons to download: Remove icons with invalid urls.
 base::flat_set<GURL> GetValidIconUrlsToDownload(
     const WebAppInstallInfo& web_app_info);
diff --git a/chrome/browser/web_applications/web_app_install_utils_unittest.cc b/chrome/browser/web_applications/web_app_install_utils_unittest.cc
index d04dd87..985f20a 100644
--- a/chrome/browser/web_applications/web_app_install_utils_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_utils_unittest.cc
@@ -651,6 +651,8 @@
 // each.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyShortcutIcons) {
   blink::mojom::Manifest manifest;
+  manifest.start_url = GURL("http://www.chromium.org/");
+  manifest.id = GURL("http://www.chromium.org/");
   const unsigned kNumShortcuts = 5;
 
   for (unsigned int i = 0; i < kNumShortcuts; ++i) {
@@ -668,9 +670,8 @@
 
     manifest.shortcuts.push_back(std::move(shortcut_item));
   }
-  WebAppInstallInfo web_app_info;
-  UpdateWebAppInfoFromManifest(
-      manifest, GURL("http://www.chromium.org/manifest.json"), &web_app_info);
+  WebAppInstallInfo web_app_info = CreateWebAppInfoFromManifest(
+      manifest, GURL("http://www.chromium.org/manifest.json"));
 
   std::vector<WebAppShortcutsMenuItemInfo::Icon> all_icons;
   for (const auto& shortcut : web_app_info.shortcuts_menu_item_infos) {
diff --git a/chrome/browser/web_applications/web_app_proto_utils.cc b/chrome/browser/web_applications/web_app_proto_utils.cc
index 24a1feb..4cb6695 100644
--- a/chrome/browser/web_applications/web_app_proto_utils.cc
+++ b/chrome/browser/web_applications/web_app_proto_utils.cc
@@ -162,8 +162,12 @@
   DCHECK(app.start_url().is_valid());
 
   sync_pb::WebAppSpecifics sync_proto;
-  if (app.manifest_id().has_value())
-    sync_proto.set_manifest_id(app.manifest_id().value());
+  // The relative id does not include the initial '/' character.
+  std::string relative_manifest_id_path = app.manifest_id().PathForRequest();
+  if (relative_manifest_id_path.starts_with("/")) {
+    relative_manifest_id_path = relative_manifest_id_path.substr(1);
+  }
+  sync_proto.set_relative_manifest_id(relative_manifest_id_path);
   sync_proto.set_start_url(app.start_url().spec());
   sync_proto.set_user_display_mode(
       ConvertUserDisplayModeToWebAppSpecificsUserDisplayMode(
diff --git a/chrome/browser/web_applications/web_app_registrar.cc b/chrome/browser/web_applications/web_app_registrar.cc
index 3df64e6..ce048141 100644
--- a/chrome/browser/web_applications/web_app_registrar.cc
+++ b/chrome/browser/web_applications/web_app_registrar.cc
@@ -546,12 +546,9 @@
   return GetAppDisplayMode(app_id);
 }
 
-std::string WebAppRegistrar::GetComputedUnhashedAppId(
-    const AppId& app_id) const {
+GURL WebAppRegistrar::GetComputedManifestId(const AppId& app_id) const {
   auto* web_app = GetAppById(app_id);
-  return web_app ? GenerateAppIdUnhashed(web_app->manifest_id(),
-                                         web_app->start_url())
-                 : std::string();
+  return web_app ? web_app->manifest_id() : GURL();
 }
 
 bool WebAppRegistrar::IsTabbedWindowModeEnabled(const AppId& app_id) const {
@@ -946,10 +943,9 @@
   return web_app ? web_app->start_url() : GURL::EmptyGURL();
 }
 
-absl::optional<std::string> WebAppRegistrar::GetAppManifestId(
-    const AppId& app_id) const {
+ManifestId WebAppRegistrar::GetAppManifestId(const AppId& app_id) const {
   auto* web_app = GetAppById(app_id);
-  return web_app ? web_app->manifest_id() : absl::nullopt;
+  return web_app ? web_app->manifest_id() : ManifestId();
 }
 
 const std::string* WebAppRegistrar::GetAppLaunchQueryParams(
diff --git a/chrome/browser/web_applications/web_app_registrar.h b/chrome/browser/web_applications/web_app_registrar.h
index 7b5094f..90b9fc9 100644
--- a/chrome/browser/web_applications/web_app_registrar.h
+++ b/chrome/browser/web_applications/web_app_registrar.h
@@ -199,7 +199,7 @@
   absl::optional<SkColor> GetAppDarkModeBackgroundColor(
       const AppId& app_id) const;
   const GURL& GetAppStartUrl(const AppId& app_id) const;
-  absl::optional<std::string> GetAppManifestId(const AppId& app_id) const;
+  ManifestId GetAppManifestId(const AppId& app_id) const;
   const std::string* GetAppLaunchQueryParams(const AppId& app_id) const;
   const apps::ShareTarget* GetAppShareTarget(const AppId& app_id) const;
   const apps::FileHandlers* GetAppFileHandlers(const AppId& app_id) const;
@@ -344,7 +344,7 @@
 
   // Computes and returns the unhashed app id from entries in the web app
   // manifest.
-  std::string GetComputedUnhashedAppId(const AppId& app_id) const;
+  GURL GetComputedManifestId(const AppId& app_id) const;
 
   // Returns whether the app should be opened in tabbed window mode.
   bool IsTabbedWindowModeEnabled(const AppId& app_id) const;
diff --git a/chrome/browser/web_applications/web_app_registry_update.cc b/chrome/browser/web_applications/web_app_registry_update.cc
index ab0d414..bb64786 100644
--- a/chrome/browser/web_applications/web_app_registry_update.cc
+++ b/chrome/browser/web_applications/web_app_registry_update.cc
@@ -32,6 +32,7 @@
 
 void WebAppRegistryUpdate::CreateApp(std::unique_ptr<WebApp> web_app) {
   DCHECK(update_data_);
+  CHECK(web_app->manifest_id().is_valid());
   DCHECK(!web_app->app_id().empty());
   DCHECK(!registrar_->GetAppById(web_app->app_id()));
   DCHECK(!base::Contains(update_data_->apps_to_create, web_app));
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.cc b/chrome/browser/web_applications/web_app_sync_bridge.cc
index 9fdf776b..3d34fea 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge.cc
@@ -71,25 +71,21 @@
 
   // app_id is a hash of start_url. Parse start_url first:
   const GURL start_url(sync_data.start_url());
-  if (start_url.is_empty() || !start_url.is_valid()) {
+  if (!start_url.is_valid()) {
     DLOG(ERROR) << "ApplySyncDataToApp: start_url parse error.";
     return;
   }
-  absl::optional<std::string> manifest_id = absl::nullopt;
-  if (sync_data.has_manifest_id())
-    manifest_id = absl::optional<std::string>(sync_data.manifest_id());
-
-  if (app->app_id() != GenerateAppId(manifest_id, start_url)) {
-    DLOG(ERROR) << "ApplySyncDataToApp: app_id doesn't match id generated "
-                   "from manifest id or start_url.";
-    return;
+  ManifestId manifest_id;
+  if (sync_data.has_relative_manifest_id()) {
+    manifest_id =
+        GenerateManifestId(sync_data.relative_manifest_id(), start_url);
+  } else {
+    manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
   }
 
-  if (!app->manifest_id().has_value()) {
-    app->SetManifestId(manifest_id);
-  } else if (app->manifest_id() != manifest_id) {
-    DLOG(ERROR) << "ApplySyncDataToApp: existing manifest_id doesn't match "
-                   "manifest_id.";
+  if (app->app_id() != GenerateAppIdFromManifestId(manifest_id)) {
+    DLOG(ERROR) << "ApplySyncDataToApp: app_id doesn't match id generated "
+                   "from manifest id or start_url.";
     return;
   }
 
@@ -101,6 +97,13 @@
     return;
   }
 
+  if (app->manifest_id() != manifest_id) {
+    DLOG(ERROR) << "ApplySyncDataToApp: existing manifest_id doesn't match "
+                   "manifest_id. "
+                << app->manifest_id().spec() << " vs " << manifest_id.spec();
+    return;
+  }
+
   // Always override user_display mode with a synced value.
   app->SetUserDisplayMode(
       CreateUserDisplayModeFromWebAppSpecificsUserDisplayMode(
@@ -385,11 +388,13 @@
   for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_create) {
     DCHECK(!registrar_->GetAppById(web_app->app_id()));
     DCHECK(!web_app->untranslated_name().empty());
+    DCHECK(web_app->manifest_id().is_valid());
   }
 
   for (const std::unique_ptr<WebApp>& web_app : update_data.apps_to_update) {
     DCHECK(registrar_->GetAppById(web_app->app_id()));
     DCHECK(!web_app->untranslated_name().empty());
+    DCHECK(web_app->manifest_id().is_valid());
   }
 
   for (const AppId& app_id : update_data.apps_to_delete)
@@ -585,6 +590,24 @@
     // storage.
     auto web_app = std::make_unique<WebApp>(app_id);
 
+    const GURL start_url(specifics.start_url());
+    if (!start_url.is_valid()) {
+      DLOG(ERROR) << "WebAppSyncBridge: start_url parse error.";
+      return;
+    }
+
+    // Set the manifest id first, as ApplySyncDataToApp verifies that the
+    // computed manifest ids match.
+    ManifestId manifest_id;
+    if (specifics.has_relative_manifest_id()) {
+      manifest_id =
+          GenerateManifestId(specifics.relative_manifest_id(), start_url);
+    } else {
+      manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
+    }
+
+    web_app->SetManifestId(manifest_id);
+
     // Request a followup sync-initiated install for this stub app to fetch
     // full local data and all the icons.
     web_app->SetIsFromSyncAndPendingInstallation(true);
@@ -771,10 +794,14 @@
     return std::string();
   }
 
-  absl::optional<std::string> manifest_id = absl::nullopt;
-  if (specifics.has_manifest_id())
-    manifest_id = absl::optional<std::string>(specifics.manifest_id());
-  return GenerateAppId(manifest_id, start_url);
+  ManifestId manifest_id;
+  if (specifics.has_relative_manifest_id()) {
+    manifest_id =
+        GenerateManifestId(specifics.relative_manifest_id(), start_url);
+  } else {
+    manifest_id = GenerateManifestIdFromStartUrlOnly(start_url);
+  }
+  return GenerateAppIdFromManifestId(manifest_id);
 }
 
 std::string WebAppSyncBridge::GetStorageKey(
diff --git a/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc b/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
index 30a916090..b37594c 100644
--- a/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
+++ b/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
@@ -15,6 +15,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "components/webapps/browser/installable/installable_data.h"
@@ -61,7 +62,8 @@
 
   // Makes a copy of WebContents fields right after Commit but before a mojo
   // request to the renderer process.
-  fallback_install_info_ = std::make_unique<WebAppInstallInfo>();
+  fallback_install_info_ = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(web_contents->GetLastCommittedURL()));
   fallback_install_info_->start_url = web_contents->GetLastCommittedURL();
   fallback_install_info_->title = web_contents->GetTitle();
   if (fallback_install_info_->title.empty()) {
diff --git a/chrome/browser/web_applications/web_contents/web_app_data_retriever_unittest.cc b/chrome/browser/web_applications/web_contents/web_app_data_retriever_unittest.cc
index 867973a..a588e1d7 100644
--- a/chrome/browser/web_applications/web_contents/web_app_data_retriever_unittest.cc
+++ b/chrome/browser/web_applications/web_contents/web_app_data_retriever_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
@@ -61,26 +62,27 @@
 
   // Set |web_app_info| to respond on |GetWebAppInstallInfo|.
   void SetWebAppInstallInfo(const WebAppInstallInfo& web_app_info) {
-    web_app_info_ = web_app_info.Clone();
+    web_app_info_ = std::make_unique<WebAppInstallInfo>(web_app_info.Clone());
   }
 
   void GetWebPageMetadata(GetWebPageMetadataCallback callback) override {
     webapps::mojom::WebPageMetadataPtr web_page_metadata(
         webapps::mojom::WebPageMetadata::New());
-    web_page_metadata->application_name = web_app_info_.title;
-    web_page_metadata->description = web_app_info_.description;
-    web_page_metadata->application_url = web_app_info_.start_url;
+    CHECK(web_app_info_);
+    web_page_metadata->application_name = web_app_info_->title;
+    web_page_metadata->description = web_app_info_->description;
+    web_page_metadata->application_url = web_app_info_->start_url;
 
     // Convert more fields as needed.
-    DCHECK(web_app_info_.manifest_icons.empty());
-    DCHECK(web_app_info_.mobile_capable ==
+    DCHECK(web_app_info_->manifest_icons.empty());
+    DCHECK(web_app_info_->mobile_capable ==
            WebAppInstallInfo::MOBILE_CAPABLE_UNSPECIFIED);
 
     std::move(callback).Run(std::move(web_page_metadata));
   }
 
  private:
-  WebAppInstallInfo web_app_info_;
+  std::unique_ptr<WebAppInstallInfo> web_app_info_;
 
   mojo::AssociatedReceiver<webapps::mojom::WebPageMetadataAgent> receiver_{
       this};
@@ -353,6 +355,10 @@
   const GURL kFooUrl("https://foo.example/bar");
   web_contents_tester()->NavigateAndCommit(kFooUrl.DeprecatedGetOriginAsURL());
 
+  // TODO(b/280862254): This will stop working once we remove the default
+  // constructor.
+  SetRendererWebAppInstallInfo(WebAppInstallInfo());
+
   base::RunLoop run_loop;
   WebAppDataRetriever retriever;
   retriever.GetWebAppInstallInfo(
@@ -380,6 +386,7 @@
     manifest->short_name = manifest_short_name;
     manifest->name = manifest_name;
     manifest->start_url = manifest_start_url;
+    manifest->id = GenerateManifestIdFromStartUrlOnly(manifest_start_url);
     manifest->scope = manifest_scope;
     manifest->has_theme_color = true;
     manifest->theme_color = manifest_theme_color;
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index ed9ed75..eb87c3e4 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1684151984-6bac68628cf911489bb3882986b45f6b21f13980.profdata
+chrome-chromeos-amd64-generic-main-1684166300-a1977d7b9884e164d260d6c33b9d4e205e4ddb06.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 4f8bd53..28df7f3d 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1684159034-e3a06fbfe2f3504cf315423580ef9554f7815b51.profdata
+chrome-mac-arm-main-1684166300-a02730dee233c17519199a930badb8fcdc65257f.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index f8d4335..36853e5 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1684151984-f93e22ad2fcdff0f21bd97758980699368c9d8ad.profdata
+chrome-win32-main-1684162775-28c5d35abf29bb167549419671a3018389b62011.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index f53ec4b..a72592b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1684151984-912815de30aead9faa59e396a602c1a60a3af297.profdata
+chrome-win64-main-1684162775-e47a1a8247b7440e00bd3acee6cdffd97ad7f8fc.profdata
diff --git a/chrome/common/printing/print_media_l10n.cc b/chrome/common/printing/print_media_l10n.cc
index c40c7d7c..ad0ccb8 100644
--- a/chrome/common/printing/print_media_l10n.cc
+++ b/chrome/common/printing/print_media_l10n.cc
@@ -8,809 +8,776 @@
 
 #include "base/containers/contains.h"
 #include "base/containers/fixed_flat_map.h"
+#include "base/i18n/number_formatting.h"
 #include "base/i18n/string_compare.h"
-#include "base/no_destructor.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/device_event_log/device_event_log.h"
 #include "components/strings/grit/components_strings.h"
 #include "printing/backend/print_backend_utils.h"
-#include "third_party/re2/src/re2/re2.h"
+#include "printing/units.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace printing {
 
 namespace {
 
-base::StringPiece StandardNameForSize(const std::string& vendor_id) {
-  // Mapping from dimensions to the standard IPP media name of the same size.
-  // This is nearly the inverse of `media_map` below except it doesn't have the
-  // entries marked as duplicate.
-  static constexpr auto kSizeMap =
-      base::MakeFixedFlatMapSorted<base::StringPiece, base::StringPiece>({
-          {"10.75x15.5in", "roc_8k_10.75x15.5in"},
-          {"1000x1414mm", "iso_b0_1000x1414mm"},
-          {"100x148mm", "jpn_hagaki_100x148mm"},
-          {"100x150mm", "om_small-photo_100x150mm"},
-          {"100x200mm", "om_wide-photo_100x200mm"},
-          {"102x165mm", "prc_1_102x165mm"},
-          {"102x176mm", "prc_2_102x176mm"},
-          {"1030x1456mm", "jis_b0_1030x1456mm"},
-          {"105x148mm", "iso_a6_105x148mm"},
-          {"105x235mm", "jpn_you4_105x235mm"},
-          {"10x11in", "na_10x11_10x11in"},
-          {"10x12in", "oe_photo-10r_10x12in"},
-          {"10x13in", "na_10x13_10x13in"},
-          {"10x14in", "na_10x14_10x14in"},
-          {"10x15in", "na_10x15_10x15in"},
-          {"110x208mm", "prc_4_110x208mm"},
-          {"110x220mm", "iso_dl_110x220mm"},
-          {"110x230mm", "om_italian_110x230mm"},
-          {"111.1x146mm", "jpn_chou2_111.1x146mm"},
-          {"114x162mm", "iso_c6_114x162mm"},
-          {"114x229mm", "iso_c6c5_114x229mm"},
-          {"1189x1682mm", "iso_2a0_1189x1682mm"},
-          {"1189x2523mm", "iso_a0x3_1189x2523mm"},
-          {"119x197mm", "jpn_kaku8_119x197mm"},
-          {"11x12in", "na_11x12_11x12in"},
-          {"11x14.875in", "na_fanfold-us_11x14.875in"},
-          {"11x14in", "na_edp_11x14in"},
-          {"11x15in", "na_11x15_11x15in"},
-          {"11x17in", "na_ledger_11x17in"},
-          {"120x235mm", "jpn_chou3_120x235mm"},
-          {"120x309mm", "prc_8_120x309mm"},
-          {"120x320mm", "prc_6_120x320mm"},
-          {"125x176mm", "iso_b6_125x176mm"},
-          {"125x324mm", "iso_b6c4_125x324mm"},
-          {"128x182mm", "jis_b6_128x182mm"},
-          {"12x14in", "na_eur-edp_12x14in"},
-          {"12x15in", "oe_photo-12r_12x15in"},
-          {"12x16in", "oe_12x16_12x16in"},
-          {"12x18in", "na_arch-b_12x18in"},
-          {"12x19.17in", "na_b-plus_12x19.17in"},
-          {"12x19in", "na_12x19_12x19in"},
-          {"130x180mm", "om_medium-photo_130x180mm"},
-          {"13x19in", "na_super-b_13x19in"},
-          {"142x205mm", "jpn_kaku7_142x205mm"},
-          {"146x215mm", "prc_16k_146x215mm"},
-          {"148x200mm", "jpn_oufuku_148x200mm"},
-          {"148x210mm", "iso_a5_148x210mm"},
-          {"14x17in", "oe_14x17_14x17in"},
-          {"14x18in", "oe_photo-14x18_14x18in"},
-          {"160x230mm", "prc_7_160x230mm"},
-          {"162x229mm", "iso_c5_162x229mm"},
-          {"16x20in", "oe_photo-16r_16x20in"},
-          {"174x235mm", "iso_a5-extra_174x235mm"},
-          {"176x250mm", "iso_b5_176x250mm"},
-          {"17x22in", "na_c_17x22in"},
-          {"17x24in", "oe_a2plus_17x24in"},
-          {"182x257mm", "jis_b5_182x257mm"},
-          {"184x260mm", "om_16k_184x260mm"},
-          {"18x22in", "oe_18x22_18x22in"},
-          {"18x24in", "na_arch-c_18x24in"},
-          {"190x240mm", "jpn_kaku5_190x240mm"},
-          {"195x270mm", "om_16k_195x270mm"},
-          {"197x267mm", "jpn_kaku4_197x267mm"},
-          {"198x275mm", "om_juuro-ku-kai_198x275mm"},
-          {"200x300mm", "om_large-photo_200x300mm"},
-          {"201x276mm", "iso_b5-extra_201x276mm"},
-          {"20x24in", "oe_photo-20r_20x24in"},
-          {"210x297mm", "iso_a4_210x297mm"},
-          {"210x330mm", "om_folio_210x330mm"},
-          {"215x305mm", "iso_ra4_215x305mm"},
-          {"215x315mm", "om_folio-sp_215x315mm"},
-          {"216x277mm", "jpn_kaku3_216x277mm"},
-          {"216x330mm", "jis_exec_216x330mm"},
-          {"220x220mm", "om_invite_220x220mm"},
-          {"225x297mm", "iso_a4-tab_225x297mm"},
-          {"225x320mm", "iso_sra4_225x320mm"},
-          {"229x324mm", "iso_c4_229x324mm"},
-          {"22x28in", "oe_photo-22x28_22x28in"},
-          {"22x29.5in", "oe_photo-22r_22x29.5in"},
-          {"22x34in", "na_d_22x34in"},
-          {"235.5x322.3mm", "iso_a4-extra_235.5x322.3mm"},
-          {"240x322.1mm", "jpn_kahu_240x322.1mm"},
-          {"240x332mm", "jpn_kaku2_240x332mm"},
-          {"24x30in", "oe_photo-24x30_24x30in"},
-          {"24x31.5in", "oe_photo-24r_24x31.5in"},
-          {"24x36in", "na_arch-d_24x36in"},
-          {"250x353mm", "iso_b4_250x353mm"},
-          {"257x364mm", "jis_b4_257x364mm"},
-          {"267x389mm", "om_pa-kai_267x389mm"},
-          {"26x37mm", "iso_a10_26x37mm"},
-          {"26x38in", "na_arch-e2_26x38in"},
-          {"270x382mm", "jpn_kaku1_270x382mm"},
-          {"275x395mm", "om_dai-pa-kai_275x395mm"},
-          {"27x39in", "na_arch-e3_27x39in"},
-          {"28x40in", "asme_f_28x40in"},
-          {"28x40mm", "iso_c10_28x40mm"},
-          {"297x1051mm", "iso_a4x5_297x1051mm"},
-          {"297x1261mm", "iso_a4x6_297x1261mm"},
-          {"297x1471mm", "iso_a4x7_297x1471mm"},
-          {"297x1682mm", "iso_a4x8_297x1682mm"},
-          {"297x1892mm", "iso_a4x9_297x1892mm"},
-          {"297x420mm", "iso_a3_297x420mm"},
-          {"297x630mm", "iso_a4x3_297x630mm"},
-          {"297x841mm", "iso_a4x4_297x841mm"},
-          {"2x3.5in", "oe_business-card_2x3.5in"},
-          {"3.5x5in", "oe_photo-l_3.5x5in"},
-          {"3.625x6.5in", "na_personal_3.625x6.5in"},
-          {"3.875x7.5in", "na_monarch_3.875x7.5in"},
-          {"3.875x8.875in", "na_number-9_3.875x8.875in"},
-          {"300x400mm", "om_photo-30x40_300x400mm"},
-          {"300x450mm", "om_photo-30x45_300x450mm"},
-          {"305x430mm", "iso_ra3_305x430mm"},
-          {"30x40in", "oe_photo-30r_30x40in"},
-          {"30x42in", "na_wide-format_30x42in"},
-          {"31x44mm", "iso_b10_31x44mm"},
-          {"320x450mm", "iso_sra3_320x450mm"},
-          {"322x445mm", "iso_a3-extra_322x445mm"},
-          {"324x458mm", "iso_c3_324x458mm"},
-          {"32x45mm", "jis_b10_32x45mm"},
-          {"34x44in", "na_e_34x44in"},
-          {"350x460mm", "om_photo-35x46_350x460mm"},
-          {"353x500mm", "iso_b3_353x500mm"},
-          {"364x515mm", "jis_b3_364x515mm"},
-          {"36x48in", "na_arch-e_36x48in"},
-          {"37x52mm", "iso_a9_37x52mm"},
-          {"3x5in", "na_index-3x5_3x5in"},
-          {"4.125x9.5in", "na_number-10_4.125x9.5in"},
-          {"4.375x5.75in", "na_a2_4.375x5.75in"},
-          {"4.5x10.375in", "na_number-11_4.5x10.375in"},
-          {"4.75x11in", "na_number-12_4.75x11in"},
-          {"400x600mm", "om_photo-40x60_400x600mm"},
-          {"40x57mm", "iso_c9_40x57mm"},
-          {"420x1189mm", "iso_a3x4_420x1189mm"},
-          {"420x1486mm", "iso_a3x5_420x1486mm"},
-          {"420x1783mm", "iso_a3x6_420x1783mm"},
-          {"420x2080mm", "iso_a3x7_420x2080mm"},
-          {"420x594mm", "iso_a2_420x594mm"},
-          {"420x891mm", "iso_a3x3_420x891mm"},
-          {"430x610mm", "iso_ra2_430x610mm"},
-          {"44x62mm", "iso_b9_44x62mm"},
-          {"44x68in", "na_f_44x68in"},
-          {"450x640mm", "iso_sra2_450x640mm"},
-          {"458x648mm", "iso_c2_458x648mm"},
-          {"45x64mm", "jis_b9_45x64mm"},
-          {"4x4in", "oe_square-photo_4x4in"},
-          {"4x6in", "na_index-4x6_4x6in"},
-          {"5.5x8.5in", "na_invoice_5.5x8.5in"},
-          {"500x707mm", "iso_b2_500x707mm"},
-          {"500x750mm", "om_photo-50x75_500x750mm"},
-          {"500x760mm", "om_photo-50x76_500x760mm"},
-          {"515x728mm", "jis_b2_515x728mm"},
-          {"52x74mm", "iso_a8_52x74mm"},
-          {"53.98x85.6mm", "iso_id-1_53.98x85.6mm"},
-          {"54x86mm", "om_card_54x86mm"},
-          {"55x85mm", "om_business-card_55x85mm"},
-          {"55x91mm", "om_business-card_55x91mm"},
-          {"57x81mm", "iso_c8_57x81mm"},
-          {"594x1261mm", "iso_a2x3_594x1261mm"},
-          {"594x1682mm", "iso_a2x4_594x1682mm"},
-          {"594x2102mm", "iso_a2x5_594x2102mm"},
-          {"594x841mm", "iso_a1_594x841mm"},
-          {"5x11.5in", "na_number-14_5x11.5in"},
-          {"5x5in", "oe_square-photo_5x5in"},
-          {"5x7in", "na_5x7_5x7in"},
-          {"5x8in", "na_index-5x8_5x8in"},
-          {"6.5x9.5in", "na_c5_6.5x9.5in"},
-          {"600x900mm", "om_photo-60x90_600x900mm"},
-          {"610x860mm", "iso_ra1_610x860mm"},
-          {"62x88mm", "iso_b8_62x88mm"},
-          {"640x900mm", "iso_sra1_640x900mm"},
-          {"648x917mm", "iso_c1_648x917mm"},
-          {"64x91mm", "jis_b8_64x91mm"},
-          {"6x8in", "na_index-4x6-ext_6x8in"},
-          {"6x9in", "na_6x9_6x9in"},
-          {"7.25x10.5in", "na_executive_7.25x10.5in"},
-          {"7.75x10.75in", "roc_16k_7.75x10.75in"},
-          {"707x1000mm", "iso_b1_707x1000mm"},
-          {"728x1030mm", "jis_b1_728x1030mm"},
-          {"74x105mm", "iso_a7_74x105mm"},
-          {"7x9in", "na_7x9_7x9in"},
-          {"8.5x10.83in", "na_quarto_8.5x10.83in"},
-          {"8.5x11in", "na_letter_8.5x11in"},
-          {"8.5x12.69in", "na_letter-plus_8.5x12.69in"},
-          {"8.5x12in", "na_fanfold-eur_8.5x12in"},
-          {"8.5x13.4in", "na_oficio_8.5x13.4in"},
-          {"8.5x13in", "na_foolscap_8.5x13in"},
-          {"8.5x14in", "na_legal_8.5x14in"},
-          {"8.94x14in", "na_super-a_8.94x14in"},
-          {"81x114mm", "iso_c7_81x114mm"},
-          {"81x162mm", "iso_c7c6_81x162mm"},
-          {"841x1189mm", "iso_a0_841x1189mm"},
-          {"841x1783mm", "iso_a1x3_841x1783mm"},
-          {"841x2378mm", "iso_a1x4_841x2378mm"},
-          {"860x1220mm", "iso_ra0_860x1220mm"},
-          {"88x125mm", "iso_b7_88x125mm"},
-          {"89x119mm", "om_dsc-photo_89x119mm"},
-          {"89x89mm", "om_square-photo_89x89mm"},
-          {"8x10in", "na_govt-letter_8x10in"},
-          {"8x12in", "oe_photo-s8r_8x12in"},
-          {"8x13in", "na_govt-legal_8x13in"},
-          {"9.5x12in", "na_letter-extra_9.5x12in"},
-          {"9.5x15in", "na_legal-extra_9.5x15in"},
-          {"900x1280mm", "iso_sra0_900x1280mm"},
-          {"90x205mm", "jpn_chou4_90x205mm"},
-          {"90x225mm", "jpn_chou40_90x225mm"},
-          {"917x1297mm", "iso_c0_917x1297mm"},
-          {"91x128mm", "jis_b7_91x128mm"},
-          {"97x151mm", "prc_32k_97x151mm"},
-          {"98x190mm", "jpn_you6_98x190mm"},
-          {"9x11in", "na_9x11_9x11in"},
-          {"9x12in", "na_arch-a_9x12in"},
+// Return the localized PWG name, display name, and sort group of a media name
+// specified by `size` if any is found - else return an empty string in the
+// named sizes group. The static map contained here is intended to reach all
+// translated media names - see print_media_resources.grd.
+MediaSizeInfo InfoForStandardSize(const gfx::Size& size) {
+  struct RegisteredMediaInfo {
+    base::StringPiece vendor_id;
+    int l10n_id;
+    MediaSizeGroup sort_group;
+  };
+
+  static constexpr auto kMediaMap = base::MakeFixedFlatMapSorted<
+      gfx::Size, RegisteredMediaInfo>(
+      {
+          {{2600, 3700},
+           {"iso_a10_26x37mm", PRINT_PREVIEW_MEDIA_ISO_A10_26X37MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{2800, 4000},
+           {"iso_c10_28x40mm", PRINT_PREVIEW_MEDIA_ISO_C10_28X40MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{3100, 4400},
+           {"iso_b10_31x44mm", PRINT_PREVIEW_MEDIA_ISO_B10_31X44MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{3200, 4500},
+           {"jis_b10_32x45mm", PRINT_PREVIEW_MEDIA_JIS_B10_32X45MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{3700, 5200},
+           {"iso_a9_37x52mm", PRINT_PREVIEW_MEDIA_ISO_A9_37X52MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{4000, 5700},
+           {"iso_c9_40x57mm", PRINT_PREVIEW_MEDIA_ISO_C9_40X57MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{4400, 6200},
+           {"iso_b9_44x62mm", PRINT_PREVIEW_MEDIA_ISO_B9_44X62MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{4500, 6400},
+           {"jis_b9_45x64mm", PRINT_PREVIEW_MEDIA_JIS_B9_45X64MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{5080, 8890},
+           {"oe_business-card_2x3.5in",
+            PRINT_PREVIEW_MEDIA_OE_BUSINESS_CARD_2X3_5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{5200, 7400},
+           {"iso_a8_52x74mm", PRINT_PREVIEW_MEDIA_ISO_A8_52X74MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{5398, 8560},
+           {"iso_id-1_53.98x85.6mm", PRINT_PREVIEW_MEDIA_ISO_ID_1_53_98X85_6MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{5400, 8600},
+           {"om_card_54x86mm", PRINT_PREVIEW_MEDIA_OM_CARD_54X86MM,
+            MediaSizeGroup::kSizeMm}},
+          {{5500, 8500},
+           {"om_business-card_55x85mm",
+            PRINT_PREVIEW_MEDIA_OM_BUSINESS_CARD_55X85MM,
+            MediaSizeGroup::kSizeMm}},
+          {{5500, 9100},
+           {"om_business-card_55x91mm",
+            PRINT_PREVIEW_MEDIA_OM_BUSINESS_CARD_55X91MM,
+            MediaSizeGroup::kSizeMm}},
+          {{5700, 8100},
+           {"iso_c8_57x81mm", PRINT_PREVIEW_MEDIA_ISO_C8_57X81MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{6200, 8800},
+           {"iso_b8_62x88mm", PRINT_PREVIEW_MEDIA_ISO_B8_62X88MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{6400, 9100},
+           {"jis_b8_64x91mm", PRINT_PREVIEW_MEDIA_JIS_B8_64X91MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{7400, 10500},
+           {"iso_a7_74x105mm", PRINT_PREVIEW_MEDIA_ISO_A7_74X105MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{7620, 12700},
+           {"na_index-3x5_3x5in", PRINT_PREVIEW_MEDIA_NA_INDEX_3X5_3X5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{8100, 11400},
+           {"iso_c7_81x114mm", PRINT_PREVIEW_MEDIA_ISO_C7_81X114MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{8100, 16200},
+           {"iso_c7c6_81x162mm", PRINT_PREVIEW_MEDIA_ISO_C7C6_81X162MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{8800, 12500},
+           {"iso_b7_88x125mm", PRINT_PREVIEW_MEDIA_ISO_B7_88X125MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{8890, 12700},
+           {"oe_photo-l_3.5x5in", PRINT_PREVIEW_MEDIA_OE_PHOTO_L_3_5X5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{8900, 8900},
+           {"om_square-photo_89x89mm",
+            PRINT_PREVIEW_MEDIA_OM_SQUARE_PHOTO_89X89MM,
+            MediaSizeGroup::kSizeMm}},
+          {{8900, 11900},
+           {"om_dsc-photo_89x119mm", PRINT_PREVIEW_MEDIA_OM_DSC_PHOTO_89X119MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9000, 20500},
+           {"jpn_chou4_90x205mm", PRINT_PREVIEW_MEDIA_JPN_CHOU4_90X205MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9000, 22500},
+           {"jpn_chou40_90x225mm", PRINT_PREVIEW_MEDIA_JPN_CHOU40_90X225MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9100, 12800},
+           {"jis_b7_91x128mm", PRINT_PREVIEW_MEDIA_JIS_B7_91X128MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9207, 16510},
+           {"na_personal_3.625x6.5in",
+            PRINT_PREVIEW_MEDIA_NA_PERSONAL_3_625X6_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{9700, 15100},
+           {"prc_32k_97x151mm", PRINT_PREVIEW_MEDIA_PRC_32K_97X151MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9800, 19000},
+           {"jpn_you6_98x190mm", PRINT_PREVIEW_MEDIA_JPN_YOU6_98X190MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{9842, 19050},
+           {"na_monarch_3.875x7.5in",
+            PRINT_PREVIEW_MEDIA_NA_MONARCH_3_875X7_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{9842, 22542},
+           {"na_number-9_3.875x8.875in",
+            PRINT_PREVIEW_MEDIA_NA_NUMBER_9_3_875X8_875IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{10000, 14800},
+           {"jpn_hagaki_100x148mm", PRINT_PREVIEW_MEDIA_JPN_HAGAKI_100X148MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{10000, 15000},
+           {"om_small-photo_100x150mm",
+            PRINT_PREVIEW_MEDIA_OM_SMALL_PHOTO_100X150MM,
+            MediaSizeGroup::kSizeMm}},
+          {{10000, 20000},
+           {"om_wide-photo_100x200mm",
+            PRINT_PREVIEW_MEDIA_OM_WIDE_PHOTO_100X200MM,
+            MediaSizeGroup::kSizeMm}},
+          {{10160, 10160},
+           {"oe_square-photo_4x4in", PRINT_PREVIEW_MEDIA_OE_SQUARE_PHOTO_4X4IN,
+            MediaSizeGroup::kSizeIn}},
+          {{10160, 15240},
+           {"na_index-4x6_4x6in", PRINT_PREVIEW_MEDIA_NA_INDEX_4X6_4X6IN,
+            MediaSizeGroup::kSizeIn}},
+          {{10200, 16500},
+           {"prc_1_102x165mm", PRINT_PREVIEW_MEDIA_PRC_1_102X165MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{10200, 17600},
+           {"prc_2_102x176mm", PRINT_PREVIEW_MEDIA_PRC_2_102X176MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{10477, 24130},
+           {"na_number-10_4.125x9.5in",
+            PRINT_PREVIEW_MEDIA_NA_NUMBER_10_4_125X9_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{10500, 14800},
+           {"iso_a6_105x148mm", PRINT_PREVIEW_MEDIA_ISO_A6_105X148MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{10500, 23500},
+           {"jpn_you4_105x235mm", PRINT_PREVIEW_MEDIA_JPN_YOU4_105X235MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11000, 20800},
+           {"prc_4_110x208mm", PRINT_PREVIEW_MEDIA_PRC_4_110X208MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11000, 22000},
+           {"iso_dl_110x220mm", PRINT_PREVIEW_MEDIA_ISO_DL_110X220MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11000, 23000},
+           {"om_italian_110x230mm", PRINT_PREVIEW_MEDIA_OM_ITALIAN_110X230MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11110, 14600},
+           {"jpn_chou2_111.1x146mm", PRINT_PREVIEW_MEDIA_JPN_CHOU2_111_1X146MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11112, 14605},
+           {"na_a2_4.375x5.75in", PRINT_PREVIEW_MEDIA_NA_A2_4_375X5_75IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{11400, 16200},
+           {"iso_c6_114x162mm", PRINT_PREVIEW_MEDIA_ISO_C6_114X162MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11400, 22900},
+           {"iso_c6c5_114x229mm", PRINT_PREVIEW_MEDIA_ISO_C6C5_114X229MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{11430, 26352},
+           {"na_number-11_4.5x10.375in",
+            PRINT_PREVIEW_MEDIA_NA_NUMBER_11_4_5X10_375IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{11900, 19700},
+           {"jpn_kaku8_119x197mm", PRINT_PREVIEW_MEDIA_JPN_KAKU8_119X197MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12000, 23500},
+           {"jpn_chou3_120x235mm", PRINT_PREVIEW_MEDIA_JPN_CHOU3_120X235MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12000, 30900},
+           {"prc_8_120x309mm", PRINT_PREVIEW_MEDIA_PRC_8_120X309MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12000, 32000},
+           {"prc_6_120x320mm", PRINT_PREVIEW_MEDIA_PRC_6_120X320MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12065, 27940},
+           {"na_number-12_4.75x11in",
+            PRINT_PREVIEW_MEDIA_NA_NUMBER_12_4_75X11IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{12500, 17600},
+           {"iso_b6_125x176mm", PRINT_PREVIEW_MEDIA_ISO_B6_125X176MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12500, 32400},
+           {"iso_b6c4_125x324mm", PRINT_PREVIEW_MEDIA_ISO_B6C4_125X324MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{12700, 12700},
+           {"oe_square-photo_5x5in", PRINT_PREVIEW_MEDIA_OE_SQUARE_PHOTO_5X5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{12700, 17780},
+           {"na_5x7_5x7in", PRINT_PREVIEW_MEDIA_NA_5X7_5X7IN,
+            MediaSizeGroup::kSizeIn}},
+          {{12700, 20320},
+           {"na_index-5x8_5x8in", PRINT_PREVIEW_MEDIA_NA_INDEX_5X8_5X8IN,
+            MediaSizeGroup::kSizeIn}},
+          {{12700, 29210},
+           {"na_number-14_5x11.5in", PRINT_PREVIEW_MEDIA_NA_NUMBER_14_5X11_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{12800, 18200},
+           {"jis_b6_128x182mm", PRINT_PREVIEW_MEDIA_JIS_B6_128X182MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{13000, 18000},
+           {"om_medium-photo_130x180mm",
+            PRINT_PREVIEW_MEDIA_OM_MEDIUM_PHOTO_130X180MM,
+            MediaSizeGroup::kSizeMm}},
+          {{13970, 21590},
+           {"na_invoice_5.5x8.5in", PRINT_PREVIEW_MEDIA_NA_INVOICE_5_5X8_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{14200, 20500},
+           {"jpn_kaku7_142x205mm", PRINT_PREVIEW_MEDIA_JPN_KAKU7_142X205MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{14600, 21500},
+           {"prc_16k_146x215mm", PRINT_PREVIEW_MEDIA_PRC_16K_146X215MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{14800, 20000},
+           {"jpn_oufuku_148x200mm", PRINT_PREVIEW_MEDIA_JPN_OUFUKU_148X200MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{14800, 21000},
+           {"iso_a5_148x210mm", PRINT_PREVIEW_MEDIA_ISO_A5_148X210MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{15240, 20320},
+           {"na_index-4x6-ext_6x8in",
+            PRINT_PREVIEW_MEDIA_NA_INDEX_4X6_EXT_6X8IN,
+            MediaSizeGroup::kSizeIn}},
+          {{15240, 22860},
+           {"na_6x9_6x9in", PRINT_PREVIEW_MEDIA_NA_6X9_6X9IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{16000, 23000},
+           {"prc_7_160x230mm", PRINT_PREVIEW_MEDIA_PRC_7_160X230MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{16200, 22900},
+           {"iso_c5_162x229mm", PRINT_PREVIEW_MEDIA_ISO_C5_162X229MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{16510, 24130},
+           {"na_c5_6.5x9.5in", PRINT_PREVIEW_MEDIA_NA_C5_6_5X9_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{17400, 23500},
+           {"iso_a5-extra_174x235mm",
+            PRINT_PREVIEW_MEDIA_ISO_A5_EXTRA_174X235MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{17600, 25000},
+           {"iso_b5_176x250mm", PRINT_PREVIEW_MEDIA_ISO_B5_176X250MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{17780, 22860},
+           {"na_7x9_7x9in", PRINT_PREVIEW_MEDIA_NA_7X9_7X9IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{18200, 25700},
+           {"jis_b5_182x257mm", PRINT_PREVIEW_MEDIA_JIS_B5_182X257MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{18400, 26000},
+           {"om_16k_184x260mm", PRINT_PREVIEW_MEDIA_OM_16K_184X260MM,
+            MediaSizeGroup::kSizeMm}},
+          {{18415, 26670},
+           {"na_executive_7.25x10.5in",
+            PRINT_PREVIEW_MEDIA_NA_EXECUTIVE_7_25X10_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{19000, 24000},
+           {"jpn_kaku5_190x240mm", PRINT_PREVIEW_MEDIA_JPN_KAKU5_190X240MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{19500, 27000},
+           {"om_16k_195x270mm", PRINT_PREVIEW_MEDIA_OM_16K_195X270MM,
+            MediaSizeGroup::kSizeMm}},
+          {{19685, 27305},
+           {"roc_16k_7.75x10.75in", PRINT_PREVIEW_MEDIA_ROC_16K_7_75X10_75IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{19700, 26700},
+           {"jpn_kaku4_197x267mm", PRINT_PREVIEW_MEDIA_JPN_KAKU4_197X267MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{19800, 27500},
+           {"om_juuro-ku-kai_198x275mm",
+            PRINT_PREVIEW_MEDIA_OM_JUURO_KU_KAI_198X275MM,
+            MediaSizeGroup::kSizeMm}},
+          {{20000, 30000},
+           {"om_large-photo_200x300mm",
+            PRINT_PREVIEW_MEDIA_OM_LARGE_PHOTO_200X300,
+            MediaSizeGroup::kSizeMm}},
+          {{20100, 27600},
+           {"iso_b5-extra_201x276mm",
+            PRINT_PREVIEW_MEDIA_ISO_B5_EXTRA_201X276MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{20320, 25400},
+           {"na_govt-letter_8x10in", PRINT_PREVIEW_MEDIA_NA_GOVT_LETTER_8X10IN,
+            MediaSizeGroup::kSizeIn}},
+          {{20320, 30480},
+           {"oe_photo-s8r_8x12in", PRINT_PREVIEW_MEDIA_OE_PHOTO_S8R_8X12IN,
+            MediaSizeGroup::kSizeIn}},
+          {{20320, 33020},
+           {"na_govt-legal_8x13in", PRINT_PREVIEW_MEDIA_NA_GOVT_LEGAL_8X13IN,
+            MediaSizeGroup::kSizeIn}},
+          {{21000, 29700},
+           {"iso_a4_210x297mm", PRINT_PREVIEW_MEDIA_ISO_A4_210X297MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{21000, 33000},
+           {"om_folio_210x330mm", PRINT_PREVIEW_MEDIA_OM_FOLIO_210X330MM,
+            MediaSizeGroup::kSizeMm}},
+          {{21500, 30500},
+           {"iso_ra4_215x305mm", PRINT_PREVIEW_MEDIA_ISO_RA4_215X305MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{21500, 31500},
+           {"om_folio-sp_215x315mm", PRINT_PREVIEW_MEDIA_OM_FOLIO_SP_215X315MM,
+            MediaSizeGroup::kSizeMm}},
+          {{21590, 27508},
+           {"na_quarto_8.5x10.83in", PRINT_PREVIEW_MEDIA_NA_QUARTO_8_5X10_83IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 27940},
+           {"na_letter_8.5x11in", PRINT_PREVIEW_MEDIA_NA_LETTER_8_5X11IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 30480},
+           {"na_fanfold-eur_8.5x12in",
+            PRINT_PREVIEW_MEDIA_NA_FANFOLD_EUR_8_5X12IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 32232},
+           {"na_letter-plus_8.5x12.69in",
+            PRINT_PREVIEW_MEDIA_NA_LETTER_PLUS_8_5X12_69IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 33020},
+           {"na_foolscap_8.5x13in", PRINT_PREVIEW_MEDIA_NA_FOOLSCAP_8_5X13IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 34036},
+           {"na_oficio_8.5x13.4in", PRINT_PREVIEW_MEDIA_NA_OFICIO_8_5X13_4IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21590, 35560},
+           {"na_legal_8.5x14in", PRINT_PREVIEW_MEDIA_NA_LEGAL_8_5X14IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{21600, 27700},
+           {"jpn_kaku3_216x277mm", PRINT_PREVIEW_MEDIA_JPN_KAKU3_216X277MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{21600, 33000},
+           {"jis_exec_216x330mm", PRINT_PREVIEW_MEDIA_JIS_EXEC_216X330MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{22000, 22000},
+           {"om_invite_220x220mm", PRINT_PREVIEW_MEDIA_OM_INVITE_220X220MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{22500, 29700},
+           {"iso_a4-tab_225x297mm", PRINT_PREVIEW_MEDIA_ISO_A4_TAB_225X297MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{22500, 32000},
+           {"iso_sra4_225x320mm", PRINT_PREVIEW_MEDIA_ISO_SRA4_225X320MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{22707, 35560},
+           {"na_super-a_8.94x14in", PRINT_PREVIEW_MEDIA_NA_SUPER_A_8_94X14IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{22860, 27940},
+           {"na_9x11_9x11in", PRINT_PREVIEW_MEDIA_NA_9X11_9X11IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{22860, 30480},
+           {"na_arch-a_9x12in", PRINT_PREVIEW_MEDIA_NA_ARCH_A_9X12IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{22900, 32400},
+           {"iso_c4_229x324mm", PRINT_PREVIEW_MEDIA_ISO_C4_229X324MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{23550, 32230},
+           {"iso_a4-extra_235.5x322.3mm",
+            PRINT_PREVIEW_MEDIA_ISO_A4_EXTRA_235_5X322_3MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{24000, 32210},
+           {"jpn_kahu_240x322.1mm", PRINT_PREVIEW_MEDIA_JPN_KAHU_240X322_1MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{24000, 33200},
+           {"jpn_kaku2_240x332mm", PRINT_PREVIEW_MEDIA_JPN_KAKU2_240X332MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{24130, 30480},
+           {"na_letter-extra_9.5x12in",
+            PRINT_PREVIEW_MEDIA_NA_LETTER_EXTRA_9_5X12IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{24130, 38100},
+           {"na_legal-extra_9.5x15in",
+            PRINT_PREVIEW_MEDIA_NA_LEGAL_EXTRA_9_5X15IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{25000, 35300},
+           {"iso_b4_250x353mm", PRINT_PREVIEW_MEDIA_ISO_B4_250X353MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{25400, 27940},
+           {"na_10x11_10x11in", PRINT_PREVIEW_MEDIA_NA_10X11_10X11IN,
+            MediaSizeGroup::kSizeIn}},
+          {{25400, 30480},
+           {"oe_photo-10r_10x12in", PRINT_PREVIEW_MEDIA_OE_PHOTO_10R_10X12IN,
+            MediaSizeGroup::kSizeIn}},
+          {{25400, 33020},
+           {"na_10x13_10x13in", PRINT_PREVIEW_MEDIA_NA_10X13_10X13IN,
+            MediaSizeGroup::kSizeIn}},
+          {{25400, 35560},
+           {"na_10x14_10x14in", PRINT_PREVIEW_MEDIA_NA_10X14_10X14IN,
+            MediaSizeGroup::kSizeIn}},
+          {{25400, 38100},
+           {"na_10x15_10x15in", PRINT_PREVIEW_MEDIA_NA_10X15_10X15IN,
+            MediaSizeGroup::kSizeIn}},
+          {{25700, 36400},
+           {"jis_b4_257x364mm", PRINT_PREVIEW_MEDIA_JIS_B4_257X364MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{26700, 38900},
+           {"om_pa-kai_267x389mm", PRINT_PREVIEW_MEDIA_OM_PA_KAI_267X389MM,
+            MediaSizeGroup::kSizeMm}},
+          {{27000, 38200},
+           {"jpn_kaku1_270x382mm", PRINT_PREVIEW_MEDIA_JPN_KAKU1_270X382MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{27305, 39370},
+           {"roc_8k_10.75x15.5in", PRINT_PREVIEW_MEDIA_ROC_8K_10_75X15_5IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{27500, 39500},
+           {"om_dai-pa-kai_275x395mm",
+            PRINT_PREVIEW_MEDIA_OM_DAI_PA_KAI_275X395MM,
+            MediaSizeGroup::kSizeMm}},
+          {{27940, 30480},
+           {"na_11x12_11x12in", PRINT_PREVIEW_MEDIA_NA_11X12_11X12IN,
+            MediaSizeGroup::kSizeIn}},
+          {{27940, 35560},
+           {"na_edp_11x14in", PRINT_PREVIEW_MEDIA_NA_EDP_11X14IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{27940, 37782},
+           {"na_fanfold-us_11x14.875in",
+            PRINT_PREVIEW_MEDIA_NA_FANFOLD_US_11X14_875IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{27940, 38100},
+           {"na_11x15_11x15in", PRINT_PREVIEW_MEDIA_NA_11X15_11X15IN,
+            MediaSizeGroup::kSizeIn}},
+          {{27940, 43180},
+           {"na_ledger_11x17in", PRINT_PREVIEW_MEDIA_NA_LEDGER_11X17IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 42000},
+           {"iso_a3_297x420mm", PRINT_PREVIEW_MEDIA_ISO_A3_297X420MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 63000},
+           {"iso_a4x3_297x630mm", PRINT_PREVIEW_MEDIA_ISO_A4X3_297X630MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 84100},
+           {"iso_a4x4_297x841mm", PRINT_PREVIEW_MEDIA_ISO_A4X4_297X841MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 105100},
+           {"iso_a4x5_297x1051mm", PRINT_PREVIEW_MEDIA_ISO_A4X5_297X1051MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 126100},
+           {"iso_a4x6_297x1261mm", PRINT_PREVIEW_MEDIA_ISO_A4X6_297X1261MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 147100},
+           {"iso_a4x7_297x1471mm", PRINT_PREVIEW_MEDIA_ISO_A4X7_297X1471MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 168200},
+           {"iso_a4x8_297x1682mm", PRINT_PREVIEW_MEDIA_ISO_A4X8_297X1682MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{29700, 189200},
+           {"iso_a4x9_297x1892mm", PRINT_PREVIEW_MEDIA_ISO_A4X9_297X1892MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{30000, 40000},
+           {"om_photo-30x40_300x400mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_30X40_300X400MM,
+            MediaSizeGroup::kSizeMm}},
+          {{30000, 45000},
+           {"om_photo-30x45_300x450mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_30X45_300X450MM,
+            MediaSizeGroup::kSizeMm}},
+          {{30480, 35560},
+           {"na_eur-edp_12x14in", PRINT_PREVIEW_MEDIA_NA_EUR_EDP_12X14IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{30480, 38100},
+           {"oe_photo-12r_12x15in", PRINT_PREVIEW_MEDIA_OE_PHOTO_12R_12X15IN,
+            MediaSizeGroup::kSizeIn}},
+          {{30480, 40640},
+           {"oe_12x16_12x16in", PRINT_PREVIEW_MEDIA_OE_12X16_12X16IN,
+            MediaSizeGroup::kSizeIn}},
+          {{30480, 45720},
+           {"na_arch-b_12x18in", PRINT_PREVIEW_MEDIA_NA_ARCH_B_12X18IN,
+            MediaSizeGroup::kSizeIn}},
+          {{30480, 48260},
+           {"na_12x19_12x19in", PRINT_PREVIEW_MEDIA_NA_12X19_12X19IN,
+            MediaSizeGroup::kSizeIn}},
+          {{30480, 48691},
+           {"na_b-plus_12x19.17in", PRINT_PREVIEW_MEDIA_NA_B_PLUS_12X19_17IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{30500, 43000},
+           {"iso_ra3_305x430mm", PRINT_PREVIEW_MEDIA_ISO_RA3_305X430MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{32000, 45000},
+           {"iso_sra3_320x450mm", PRINT_PREVIEW_MEDIA_ISO_SRA3_320X450MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{32200, 44500},
+           {"iso_a3-extra_322x445mm",
+            PRINT_PREVIEW_MEDIA_ISO_A3_EXTRA_322X445MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{32400, 45800},
+           {"iso_c3_324x458mm", PRINT_PREVIEW_MEDIA_ISO_C3_324X458MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{33020, 48260},
+           {"na_super-b_13x19in", PRINT_PREVIEW_MEDIA_NA_SUPER_B_13X19IN,
+            MediaSizeGroup::kSizeNamed}},
+          {{35000, 46000},
+           {"om_photo-35x46_350x460mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_35X46_350X460MM,
+            MediaSizeGroup::kSizeMm}},
+          {{35300, 50000},
+           {"iso_b3_353x500mm", PRINT_PREVIEW_MEDIA_ISO_B3_353X500MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{35560, 43180},
+           {"oe_14x17_14x17in", PRINT_PREVIEW_MEDIA_OE_14X17_14X17IN,
+            MediaSizeGroup::kSizeIn}},
+          {{35560, 45720},
+           {"oe_photo-14x18_14x18in",
+            PRINT_PREVIEW_MEDIA_OE_PHOTO_14X18_14X18IN,
+            MediaSizeGroup::kSizeIn}},
+          {{36400, 51500},
+           {"jis_b3_364x515mm", PRINT_PREVIEW_MEDIA_JIS_B3_364X515MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{40000, 60000},
+           {"om_photo-40x60_400x600mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_40X60_400X600MM,
+            MediaSizeGroup::kSizeMm}},
+          {{40640, 50800},
+           {"oe_photo-16r_16x20in", PRINT_PREVIEW_MEDIA_OE_PHOTO_16R_16X20IN,
+            MediaSizeGroup::kSizeIn}},
+          {{42000, 59400},
+           {"iso_a2_420x594mm", PRINT_PREVIEW_MEDIA_ISO_A2_420X594MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{42000, 89100},
+           {"iso_a3x3_420x891mm", PRINT_PREVIEW_MEDIA_ISO_A3X3_420X891MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{42000, 118900},
+           {"iso_a3x4_420x1189mm", PRINT_PREVIEW_MEDIA_ISO_A3X4_420X1189MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{42000, 148600},
+           {"iso_a3x5_420x1486mm", PRINT_PREVIEW_MEDIA_ISO_A3X5_420X1486MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{42000, 178300},
+           {"iso_a3x6_420x1783mm", PRINT_PREVIEW_MEDIA_ISO_A3X6_420X1783MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{42000, 208000},
+           {"iso_a3x7_420x2080mm", PRINT_PREVIEW_MEDIA_ISO_A3X7_420X2080MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{43000, 61000},
+           {"iso_ra2_430x610mm", PRINT_PREVIEW_MEDIA_ISO_RA2_430X610MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{43180, 55880},
+           {"na_c_17x22in", PRINT_PREVIEW_MEDIA_NA_C_17X22IN,
+            MediaSizeGroup::kSizeIn}},
+          {{43180, 60960},
+           {"oe_a2plus_17x24in", PRINT_PREVIEW_MEDIA_OE_A2PLUS_17X24IN,
+            MediaSizeGroup::kSizeIn}},
+          {{45000, 64000},
+           {"iso_sra2_450x640mm", PRINT_PREVIEW_MEDIA_ISO_SRA2_450X640MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{45720, 55880},
+           {"oe_18x22_18x22in", PRINT_PREVIEW_MEDIA_OE_18X22_18X22IN,
+            MediaSizeGroup::kSizeIn}},
+          {{45720, 60960},
+           {"na_arch-c_18x24in", PRINT_PREVIEW_MEDIA_NA_ARCH_C_18X24IN,
+            MediaSizeGroup::kSizeIn}},
+          {{45800, 64800},
+           {"iso_c2_458x648mm", PRINT_PREVIEW_MEDIA_ISO_C2_458X648MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{50000, 70700},
+           {"iso_b2_500x707mm", PRINT_PREVIEW_MEDIA_ISO_B2_500X707MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{50000, 75000},
+           {"om_photo-50x75_500x750mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_50X75_500X750MM,
+            MediaSizeGroup::kSizeMm}},
+          {{50000, 76000},
+           {"om_photo-50x76_500x760mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_50X76_500X760MM,
+            MediaSizeGroup::kSizeMm}},
+          {{50800, 60960},
+           {"oe_photo-20r_20x24in", PRINT_PREVIEW_MEDIA_OE_PHOTO_20R_20X24IN,
+            MediaSizeGroup::kSizeIn}},
+          {{51500, 72800},
+           {"jis_b2_515x728mm", PRINT_PREVIEW_MEDIA_JIS_B2_515X728MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{55880, 71120},
+           {"oe_photo-22x28_22x28in",
+            PRINT_PREVIEW_MEDIA_OE_PHOTO_22X28_22X28IN,
+            MediaSizeGroup::kSizeIn}},
+          {{55880, 74930},
+           {"oe_photo-22r_22x29.5in",
+            PRINT_PREVIEW_MEDIA_OE_PHOTO_22R_22X29_5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{55880, 86360},
+           {"na_d_22x34in", PRINT_PREVIEW_MEDIA_NA_D_22X34IN,
+            MediaSizeGroup::kSizeIn}},
+          {{59400, 84100},
+           {"iso_a1_594x841mm", PRINT_PREVIEW_MEDIA_ISO_A1_594X841MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{59400, 126100},
+           {"iso_a2x3_594x1261mm", PRINT_PREVIEW_MEDIA_ISO_A2X3_594X1261MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{59400, 168200},
+           {"iso_a2x4_594x1682mm", PRINT_PREVIEW_MEDIA_ISO_A2X4_594X1682MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{59400, 210200},
+           {"iso_a2x5_594x2102mm", PRINT_PREVIEW_MEDIA_ISO_A2X5_594X2102MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{60000, 90000},
+           {"om_photo-60x90_600x900mm",
+            PRINT_PREVIEW_MEDIA_OM_PHOTO_60X90_600X900MM,
+            MediaSizeGroup::kSizeMm}},
+          {{60960, 76200},
+           {"oe_photo-24x30_24x30in",
+            PRINT_PREVIEW_MEDIA_OE_PHOTO_24X30_24X30IN,
+            MediaSizeGroup::kSizeIn}},
+          {{60960, 80010},
+           {"oe_photo-24r_24x31.5in",
+            PRINT_PREVIEW_MEDIA_OE_PHOTO_24R_24X31_5IN,
+            MediaSizeGroup::kSizeIn}},
+          {{60960, 91440},
+           {"na_arch-d_24x36in", PRINT_PREVIEW_MEDIA_NA_ARCH_D_24X36IN,
+            MediaSizeGroup::kSizeIn}},
+          {{61000, 86000},
+           {"iso_ra1_610x860mm", PRINT_PREVIEW_MEDIA_ISO_RA1_610X860MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{64000, 90000},
+           {"iso_sra1_640x900mm", PRINT_PREVIEW_MEDIA_ISO_SRA1_640X900MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{64800, 91700},
+           {"iso_c1_648x917mm", PRINT_PREVIEW_MEDIA_ISO_C1_648X917MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{66040, 96520},
+           {"na_arch-e2_26x38in", PRINT_PREVIEW_MEDIA_NA_ARCH_E2_26X38IN,
+            MediaSizeGroup::kSizeIn}},
+          {{68580, 99060},
+           {"na_arch-e3_27x39in", PRINT_PREVIEW_MEDIA_NA_ARCH_E3_27X39IN,
+            MediaSizeGroup::kSizeIn}},
+          {{70700, 100000},
+           {"iso_b1_707x1000mm", PRINT_PREVIEW_MEDIA_ISO_B1_707X1000MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{71120, 101600},
+           {"asme_f_28x40in", PRINT_PREVIEW_MEDIA_ASME_F_28X40IN,
+            MediaSizeGroup::kSizeIn}},
+          {{72800, 103000},
+           {"jis_b1_728x1030mm", PRINT_PREVIEW_MEDIA_JIS_B1_728X1030MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{76200, 101600},
+           {"oe_photo-30r_30x40in", PRINT_PREVIEW_MEDIA_OE_PHOTO_30R_30X40IN,
+            MediaSizeGroup::kSizeIn}},
+          {{76200, 106680},
+           {"na_wide-format_30x42in",
+            PRINT_PREVIEW_MEDIA_NA_WIDE_FORMAT_30X42IN,
+            MediaSizeGroup::kSizeIn}},
+          {{84100, 118900},
+           {"iso_a0_841x1189mm", PRINT_PREVIEW_MEDIA_ISO_A0_841X1189MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{84100, 178300},
+           {"iso_a1x3_841x1783mm", PRINT_PREVIEW_MEDIA_ISO_A1X3_841X1783MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{84100, 237800},
+           {"iso_a1x4_841x2378mm", PRINT_PREVIEW_MEDIA_ISO_A1X4_841X2378MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{86000, 122000},
+           {"iso_ra0_860x1220mm", PRINT_PREVIEW_MEDIA_ISO_RA0_860X1220MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{86360, 111760},
+           {"na_e_34x44in", PRINT_PREVIEW_MEDIA_NA_E_34X44IN,
+            MediaSizeGroup::kSizeIn}},
+          {{90000, 128000},
+           {"iso_sra0_900x1280mm", PRINT_PREVIEW_MEDIA_ISO_SRA0_900X1280MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{91440, 121920},
+           {"na_arch-e_36x48in", PRINT_PREVIEW_MEDIA_NA_ARCH_E_36X48IN,
+            MediaSizeGroup::kSizeIn}},
+          {{91700, 129700},
+           {"iso_c0_917x1297mm", PRINT_PREVIEW_MEDIA_ISO_C0_917X1297MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{100000, 141400},
+           {"iso_b0_1000x1414mm", PRINT_PREVIEW_MEDIA_ISO_B0_1000X1414MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{103000, 145600},
+           {"jis_b0_1030x1456mm", PRINT_PREVIEW_MEDIA_JIS_B0_1030X1456MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{111760, 172720},
+           {"na_f_44x68in", PRINT_PREVIEW_MEDIA_NA_F_44X68IN,
+            MediaSizeGroup::kSizeIn}},
+          {{118900, 168200},
+           {"iso_2a0_1189x1682mm", PRINT_PREVIEW_MEDIA_ISO_2A0_1189X1682MM,
+            MediaSizeGroup::kSizeNamed}},
+          {{118900, 252300},
+           {"iso_a0x3_1189x2523mm", PRINT_PREVIEW_MEDIA_ISO_A0X3_1189X2523MM,
+            MediaSizeGroup::kSizeNamed}},
+      },
+      [](const gfx::Size& a, const gfx::Size& b) {
+        auto result = a.width() - b.width();
+        if (result == 0) {
+          result = a.height() - b.height();
+        }
+        return result < 0;
       });
 
-  // The standard sizes are separated by underscore with the dimensions in the
-  // last field.
-  std::vector<std::string> parts = base::SplitString(
-      vendor_id, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-  if (parts.size() != 3) {
-    return "";
-  }
-  auto* it = kSizeMap.find(parts.back());
-  return it != kSizeMap.end() ? it->second : "";
-}
-
-// Return the localized display name and sort group of a media name specified by
-// `vendor_id` if any is found - else return an empty string in the named sizes
-// group. The static map contained here is intended to reach all translated
-// media names - see print_media_resources.grd.
-MediaSizeInfo InfoForVendorId(const std::string& vendor_id) {
-  static constexpr auto kMediaMap = base::MakeFixedFlatMapSorted<
-      base::StringPiece, std::pair<int, MediaSizeGroup>>({
-      {"asme_f_28x40in",
-       {PRINT_PREVIEW_MEDIA_ASME_F_28X40IN, MediaSizeGroup::kSizeIn}},
-      {"iso_2a0_1189x1682mm",
-       {PRINT_PREVIEW_MEDIA_ISO_2A0_1189X1682MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a0_841x1189mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A0_841X1189MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a0x3_1189x2523mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A0X3_1189X2523MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a10_26x37mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A10_26X37MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a1_594x841mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A1_594X841MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a1x3_841x1783mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A1X3_841X1783MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a1x4_841x2378mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A1X4_841X2378MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a2_420x594mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A2_420X594MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a2x3_594x1261mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A2X3_594X1261MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a2x4_594x1682mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A2X4_594X1682MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a2x5_594x2102mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A2X5_594X2102MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3-extra_322x445mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3_EXTRA_322X445MM,
-        MediaSizeGroup::kSizeNamed}},
-      {"iso_a3_297x420mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3_297X420MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3x3_420x891mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3X3_420X891MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3x4_420x1189mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3X4_420X1189MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3x5_420x1486mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3X5_420X1486MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3x6_420x1783mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3X6_420X1783MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a3x7_420x2080mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A3X7_420X2080MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4-extra_235.5x322.3mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4_EXTRA_235_5X322_3MM,
-        MediaSizeGroup::kSizeNamed}},
-      {"iso_a4-tab_225x297mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4_TAB_225X297MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4_210x297mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4_210X297MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x3_297x630mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X3_297X630MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x4_297x841mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X4_297X841MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x5_297x1051mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X5_297X1051MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x6_297x1261mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X6_297X1261MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x7_297x1471mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X7_297X1471MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x8_297x1682mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X8_297X1682MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a4x9_297x1892mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A4X9_297X1892MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a5-extra_174x235mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A5_EXTRA_174X235MM,
-        MediaSizeGroup::kSizeNamed}},
-      {"iso_a5_148x210mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A5_148X210MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a6_105x148mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A6_105X148MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a7_74x105mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A7_74X105MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a8_52x74mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A8_52X74MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_a9_37x52mm",
-       {PRINT_PREVIEW_MEDIA_ISO_A9_37X52MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b0_1000x1414mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B0_1000X1414MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b10_31x44mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B10_31X44MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b1_707x1000mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B1_707X1000MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b2_500x707mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B2_500X707MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b3_353x500mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B3_353X500MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b4_250x353mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B4_250X353MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b5-extra_201x276mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B5_EXTRA_201X276MM,
-        MediaSizeGroup::kSizeNamed}},
-      {"iso_b5_176x250mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B5_176X250MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b6_125x176mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B6_125X176MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b6c4_125x324mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B6C4_125X324MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b7_88x125mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B7_88X125MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b8_62x88mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B8_62X88MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_b9_44x62mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B9_44X62MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c0_917x1297mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C0_917X1297MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c10_28x40mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C10_28X40MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c1_648x917mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C1_648X917MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c2_458x648mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C2_458X648MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c3_324x458mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C3_324X458MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c4_229x324mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C4_229X324MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c5_162x229mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C5_162X229MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c6_114x162mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C6_114X162MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c6c5_114x229mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C6C5_114X229MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c7_81x114mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C7_81X114MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c7c6_81x162mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C7C6_81X162MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c8_57x81mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C8_57X81MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_c9_40x57mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C9_40X57MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_dl_110x220mm",
-       {PRINT_PREVIEW_MEDIA_ISO_DL_110X220MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_id-1_53.98x85.6mm",
-       {PRINT_PREVIEW_MEDIA_ISO_ID_1_53_98X85_6MM, MediaSizeGroup::kSizeNamed}},
-      // Duplicate of iso_b7_88x125mm.
-      {"iso_id-3_88x125mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B7_88X125MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_ra0_860x1220mm",
-       {PRINT_PREVIEW_MEDIA_ISO_RA0_860X1220MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_ra1_610x860mm",
-       {PRINT_PREVIEW_MEDIA_ISO_RA1_610X860MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_ra2_430x610mm",
-       {PRINT_PREVIEW_MEDIA_ISO_RA2_430X610MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_ra3_305x430mm",
-       {PRINT_PREVIEW_MEDIA_ISO_RA3_305X430MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_ra4_215x305mm",
-       {PRINT_PREVIEW_MEDIA_ISO_RA4_215X305MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_sra0_900x1280mm",
-       {PRINT_PREVIEW_MEDIA_ISO_SRA0_900X1280MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_sra1_640x900mm",
-       {PRINT_PREVIEW_MEDIA_ISO_SRA1_640X900MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_sra2_450x640mm",
-       {PRINT_PREVIEW_MEDIA_ISO_SRA2_450X640MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_sra3_320x450mm",
-       {PRINT_PREVIEW_MEDIA_ISO_SRA3_320X450MM, MediaSizeGroup::kSizeNamed}},
-      {"iso_sra4_225x320mm",
-       {PRINT_PREVIEW_MEDIA_ISO_SRA4_225X320MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b0_1030x1456mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B0_1030X1456MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b10_32x45mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B10_32X45MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b1_728x1030mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B1_728X1030MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b2_515x728mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B2_515X728MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b3_364x515mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B3_364X515MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b4_257x364mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B4_257X364MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b5_182x257mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B5_182X257MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b6_128x182mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B6_128X182MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b7_91x128mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B7_91X128MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b8_64x91mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B8_64X91MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_b9_45x64mm",
-       {PRINT_PREVIEW_MEDIA_JIS_B9_45X64MM, MediaSizeGroup::kSizeNamed}},
-      {"jis_exec_216x330mm",
-       {PRINT_PREVIEW_MEDIA_JIS_EXEC_216X330MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_chou2_111.1x146mm",
-       {PRINT_PREVIEW_MEDIA_JPN_CHOU2_111_1X146MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_chou3_120x235mm",
-       {PRINT_PREVIEW_MEDIA_JPN_CHOU3_120X235MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_chou40_90x225mm",
-       {PRINT_PREVIEW_MEDIA_JPN_CHOU40_90X225MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_chou4_90x205mm",
-       {PRINT_PREVIEW_MEDIA_JPN_CHOU4_90X205MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_hagaki_100x148mm",
-       {PRINT_PREVIEW_MEDIA_JPN_HAGAKI_100X148MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kahu_240x322.1mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAHU_240X322_1MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku1_270x382mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU1_270X382MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku2_240x332mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU2_240X332MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku3_216x277mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU3_216X277MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku4_197x267mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU4_197X267MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku5_190x240mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU5_190X240MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku7_142x205mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU7_142X205MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_kaku8_119x197mm",
-       {PRINT_PREVIEW_MEDIA_JPN_KAKU8_119X197MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_oufuku_148x200mm",
-       {PRINT_PREVIEW_MEDIA_JPN_OUFUKU_148X200MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_you4_105x235mm",
-       {PRINT_PREVIEW_MEDIA_JPN_YOU4_105X235MM, MediaSizeGroup::kSizeNamed}},
-      {"jpn_you6_98x190mm",
-       {PRINT_PREVIEW_MEDIA_JPN_YOU6_98X190MM, MediaSizeGroup::kSizeNamed}},
-      {"na_10x11_10x11in",
-       {PRINT_PREVIEW_MEDIA_NA_10X11_10X11IN, MediaSizeGroup::kSizeIn}},
-      {"na_10x13_10x13in",
-       {PRINT_PREVIEW_MEDIA_NA_10X13_10X13IN, MediaSizeGroup::kSizeIn}},
-      {"na_10x14_10x14in",
-       {PRINT_PREVIEW_MEDIA_NA_10X14_10X14IN, MediaSizeGroup::kSizeIn}},
-      {"na_10x15_10x15in",
-       {PRINT_PREVIEW_MEDIA_NA_10X15_10X15IN, MediaSizeGroup::kSizeIn}},
-      {"na_11x12_11x12in",
-       {PRINT_PREVIEW_MEDIA_NA_11X12_11X12IN, MediaSizeGroup::kSizeIn}},
-      {"na_11x15_11x15in",
-       {PRINT_PREVIEW_MEDIA_NA_11X15_11X15IN, MediaSizeGroup::kSizeIn}},
-      {"na_12x19_12x19in",
-       {PRINT_PREVIEW_MEDIA_NA_12X19_12X19IN, MediaSizeGroup::kSizeIn}},
-      {"na_5x7_5x7in",
-       {PRINT_PREVIEW_MEDIA_NA_5X7_5X7IN, MediaSizeGroup::kSizeIn}},
-      {"na_6x9_6x9in",
-       {PRINT_PREVIEW_MEDIA_NA_6X9_6X9IN, MediaSizeGroup::kSizeNamed}},
-      {"na_7x9_7x9in",
-       {PRINT_PREVIEW_MEDIA_NA_7X9_7X9IN, MediaSizeGroup::kSizeNamed}},
-      {"na_9x11_9x11in",
-       {PRINT_PREVIEW_MEDIA_NA_9X11_9X11IN, MediaSizeGroup::kSizeNamed}},
-      {"na_a2_4.375x5.75in",
-       {PRINT_PREVIEW_MEDIA_NA_A2_4_375X5_75IN, MediaSizeGroup::kSizeNamed}},
-      {"na_arch-a_9x12in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_A_9X12IN, MediaSizeGroup::kSizeNamed}},
-      {"na_arch-b_12x18in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_B_12X18IN, MediaSizeGroup::kSizeIn}},
-      {"na_arch-c_18x24in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_C_18X24IN, MediaSizeGroup::kSizeIn}},
-      {"na_arch-d_24x36in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_D_24X36IN, MediaSizeGroup::kSizeIn}},
-      {"na_arch-e2_26x38in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_E2_26X38IN, MediaSizeGroup::kSizeIn}},
-      {"na_arch-e3_27x39in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_E3_27X39IN, MediaSizeGroup::kSizeIn}},
-      {"na_arch-e_36x48in",
-       {PRINT_PREVIEW_MEDIA_NA_ARCH_E_36X48IN, MediaSizeGroup::kSizeIn}},
-      {"na_b-plus_12x19.17in",
-       {PRINT_PREVIEW_MEDIA_NA_B_PLUS_12X19_17IN, MediaSizeGroup::kSizeNamed}},
-      {"na_c5_6.5x9.5in",
-       {PRINT_PREVIEW_MEDIA_NA_C5_6_5X9_5IN, MediaSizeGroup::kSizeNamed}},
-      {"na_c_17x22in",
-       {PRINT_PREVIEW_MEDIA_NA_C_17X22IN, MediaSizeGroup::kSizeIn}},
-      {"na_d_22x34in",
-       {PRINT_PREVIEW_MEDIA_NA_D_22X34IN, MediaSizeGroup::kSizeIn}},
-      {"na_e_34x44in",
-       {PRINT_PREVIEW_MEDIA_NA_E_34X44IN, MediaSizeGroup::kSizeIn}},
-      {"na_edp_11x14in",
-       {PRINT_PREVIEW_MEDIA_NA_EDP_11X14IN, MediaSizeGroup::kSizeNamed}},
-      {"na_eur-edp_12x14in",
-       {PRINT_PREVIEW_MEDIA_NA_EUR_EDP_12X14IN, MediaSizeGroup::kSizeNamed}},
-      {"na_executive_7.25x10.5in",
-       {PRINT_PREVIEW_MEDIA_NA_EXECUTIVE_7_25X10_5IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_f_44x68in",
-       {PRINT_PREVIEW_MEDIA_NA_F_44X68IN, MediaSizeGroup::kSizeIn}},
-      {"na_fanfold-eur_8.5x12in",
-       {PRINT_PREVIEW_MEDIA_NA_FANFOLD_EUR_8_5X12IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_fanfold-us_11x14.875in",
-       {PRINT_PREVIEW_MEDIA_NA_FANFOLD_US_11X14_875IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_foolscap_8.5x13in",
-       {PRINT_PREVIEW_MEDIA_NA_FOOLSCAP_8_5X13IN, MediaSizeGroup::kSizeNamed}},
-      {"na_govt-legal_8x13in",
-       {PRINT_PREVIEW_MEDIA_NA_GOVT_LEGAL_8X13IN, MediaSizeGroup::kSizeIn}},
-      {"na_govt-letter_8x10in",
-       {PRINT_PREVIEW_MEDIA_NA_GOVT_LETTER_8X10IN, MediaSizeGroup::kSizeIn}},
-      {"na_index-3x5_3x5in",
-       {PRINT_PREVIEW_MEDIA_NA_INDEX_3X5_3X5IN, MediaSizeGroup::kSizeIn}},
-      {"na_index-4x6-ext_6x8in",
-       {PRINT_PREVIEW_MEDIA_NA_INDEX_4X6_EXT_6X8IN, MediaSizeGroup::kSizeIn}},
-      {"na_index-4x6_4x6in",
-       {PRINT_PREVIEW_MEDIA_NA_INDEX_4X6_4X6IN, MediaSizeGroup::kSizeIn}},
-      {"na_index-5x8_5x8in",
-       {PRINT_PREVIEW_MEDIA_NA_INDEX_5X8_5X8IN, MediaSizeGroup::kSizeIn}},
-      {"na_invoice_5.5x8.5in",
-       {PRINT_PREVIEW_MEDIA_NA_INVOICE_5_5X8_5IN, MediaSizeGroup::kSizeNamed}},
-      {"na_ledger_11x17in",
-       {PRINT_PREVIEW_MEDIA_NA_LEDGER_11X17IN, MediaSizeGroup::kSizeNamed}},
-      {"na_legal-extra_9.5x15in",
-       {PRINT_PREVIEW_MEDIA_NA_LEGAL_EXTRA_9_5X15IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_legal_8.5x14in",
-       {PRINT_PREVIEW_MEDIA_NA_LEGAL_8_5X14IN, MediaSizeGroup::kSizeNamed}},
-      {"na_letter-extra_9.5x12in",
-       {PRINT_PREVIEW_MEDIA_NA_LETTER_EXTRA_9_5X12IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_letter-plus_8.5x12.69in",
-       {PRINT_PREVIEW_MEDIA_NA_LETTER_PLUS_8_5X12_69IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_letter_8.5x11in",
-       {PRINT_PREVIEW_MEDIA_NA_LETTER_8_5X11IN, MediaSizeGroup::kSizeNamed}},
-      {"na_monarch_3.875x7.5in",
-       {PRINT_PREVIEW_MEDIA_NA_MONARCH_3_875X7_5IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_number-10_4.125x9.5in",
-       {PRINT_PREVIEW_MEDIA_NA_NUMBER_10_4_125X9_5IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_number-11_4.5x10.375in",
-       {PRINT_PREVIEW_MEDIA_NA_NUMBER_11_4_5X10_375IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_number-12_4.75x11in",
-       {PRINT_PREVIEW_MEDIA_NA_NUMBER_12_4_75X11IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_number-14_5x11.5in",
-       {PRINT_PREVIEW_MEDIA_NA_NUMBER_14_5X11_5IN, MediaSizeGroup::kSizeNamed}},
-      {"na_number-9_3.875x8.875in",
-       {PRINT_PREVIEW_MEDIA_NA_NUMBER_9_3_875X8_875IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_oficio_8.5x13.4in",
-       {PRINT_PREVIEW_MEDIA_NA_OFICIO_8_5X13_4IN, MediaSizeGroup::kSizeNamed}},
-      {"na_personal_3.625x6.5in",
-       {PRINT_PREVIEW_MEDIA_NA_PERSONAL_3_625X6_5IN,
-        MediaSizeGroup::kSizeNamed}},
-      {"na_quarto_8.5x10.83in",
-       {PRINT_PREVIEW_MEDIA_NA_QUARTO_8_5X10_83IN, MediaSizeGroup::kSizeNamed}},
-      {"na_super-a_8.94x14in",
-       {PRINT_PREVIEW_MEDIA_NA_SUPER_A_8_94X14IN, MediaSizeGroup::kSizeNamed}},
-      {"na_super-b_13x19in",
-       {PRINT_PREVIEW_MEDIA_NA_SUPER_B_13X19IN, MediaSizeGroup::kSizeNamed}},
-      {"na_wide-format_30x42in",
-       {PRINT_PREVIEW_MEDIA_NA_WIDE_FORMAT_30X42IN, MediaSizeGroup::kSizeIn}},
-      {"oe_12x16_12x16in",
-       {PRINT_PREVIEW_MEDIA_OE_12X16_12X16IN, MediaSizeGroup::kSizeIn}},
-      {"oe_14x17_14x17in",
-       {PRINT_PREVIEW_MEDIA_OE_14X17_14X17IN, MediaSizeGroup::kSizeIn}},
-      {"oe_18x22_18x22in",
-       {PRINT_PREVIEW_MEDIA_OE_18X22_18X22IN, MediaSizeGroup::kSizeIn}},
-      {"oe_a2plus_17x24in",
-       {PRINT_PREVIEW_MEDIA_OE_A2PLUS_17X24IN, MediaSizeGroup::kSizeIn}},
-      {"oe_business-card_2x3.5in",
-       {PRINT_PREVIEW_MEDIA_OE_BUSINESS_CARD_2X3_5IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-10r_10x12in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_10R_10X12IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-12r_12x15in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_12R_12X15IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-14x18_14x18in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_14X18_14X18IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-16r_16x20in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_16R_16X20IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-20r_20x24in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_20R_20X24IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-22r_22x29.5in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_22R_22X29_5IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-22x28_22x28in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_22X28_22X28IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-24r_24x31.5in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_24R_24X31_5IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-24x30_24x30in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_24X30_24X30IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-30r_30x40in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_30R_30X40IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-l_3.5x5in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_L_3_5X5IN, MediaSizeGroup::kSizeIn}},
-      // Duplicate of na_10x15_10x15in.
-      {"oe_photo-s10r_10x15in",
-       {PRINT_PREVIEW_MEDIA_NA_10X15_10X15IN, MediaSizeGroup::kSizeIn}},
-      {"oe_photo-s8r_8x12in",
-       {PRINT_PREVIEW_MEDIA_OE_PHOTO_S8R_8X12IN, MediaSizeGroup::kSizeIn}},
-      {"oe_square-photo_4x4in",
-       {PRINT_PREVIEW_MEDIA_OE_SQUARE_PHOTO_4X4IN, MediaSizeGroup::kSizeIn}},
-      {"oe_square-photo_5x5in",
-       {PRINT_PREVIEW_MEDIA_OE_SQUARE_PHOTO_5X5IN, MediaSizeGroup::kSizeIn}},
-      {"om_16k_184x260mm",
-       {PRINT_PREVIEW_MEDIA_OM_16K_184X260MM, MediaSizeGroup::kSizeMm}},
-      {"om_16k_195x270mm",
-       {PRINT_PREVIEW_MEDIA_OM_16K_195X270MM, MediaSizeGroup::kSizeMm}},
-      {"om_business-card_55x85mm",
-       {PRINT_PREVIEW_MEDIA_OM_BUSINESS_CARD_55X85MM, MediaSizeGroup::kSizeMm}},
-      {"om_business-card_55x91mm",
-       {PRINT_PREVIEW_MEDIA_OM_BUSINESS_CARD_55X91MM, MediaSizeGroup::kSizeMm}},
-      {"om_card_54x86mm",
-       {PRINT_PREVIEW_MEDIA_OM_CARD_54X86MM, MediaSizeGroup::kSizeMm}},
-      {"om_dai-pa-kai_275x395mm",
-       {PRINT_PREVIEW_MEDIA_OM_DAI_PA_KAI_275X395MM, MediaSizeGroup::kSizeMm}},
-      {"om_dsc-photo_89x119mm",
-       {PRINT_PREVIEW_MEDIA_OM_DSC_PHOTO_89X119MM, MediaSizeGroup::kSizeNamed}},
-      {"om_folio-sp_215x315mm",
-       {PRINT_PREVIEW_MEDIA_OM_FOLIO_SP_215X315MM, MediaSizeGroup::kSizeMm}},
-      {"om_folio_210x330mm",
-       {PRINT_PREVIEW_MEDIA_OM_FOLIO_210X330MM, MediaSizeGroup::kSizeMm}},
-      {"om_invite_220x220mm",
-       {PRINT_PREVIEW_MEDIA_OM_INVITE_220X220MM, MediaSizeGroup::kSizeNamed}},
-      {"om_italian_110x230mm",
-       {PRINT_PREVIEW_MEDIA_OM_ITALIAN_110X230MM, MediaSizeGroup::kSizeNamed}},
-      {"om_juuro-ku-kai_198x275mm",
-       {PRINT_PREVIEW_MEDIA_OM_JUURO_KU_KAI_198X275MM,
-        MediaSizeGroup::kSizeMm}},
-      // Duplicate of the next because this was previously mapped wrong in cups.
-      {"om_large-photo_200x300",
-       {PRINT_PREVIEW_MEDIA_OM_LARGE_PHOTO_200X300, MediaSizeGroup::kSizeMm}},
-      {"om_large-photo_200x300mm",
-       {PRINT_PREVIEW_MEDIA_OM_LARGE_PHOTO_200X300, MediaSizeGroup::kSizeMm}},
-      {"om_medium-photo_130x180mm",
-       {PRINT_PREVIEW_MEDIA_OM_MEDIUM_PHOTO_130X180MM,
-        MediaSizeGroup::kSizeMm}},
-      {"om_pa-kai_267x389mm",
-       {PRINT_PREVIEW_MEDIA_OM_PA_KAI_267X389MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-30x40_300x400mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_30X40_300X400MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-30x45_300x450mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_30X45_300X450MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-35x46_350x460mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_35X46_350X460MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-40x60_400x600mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_40X60_400X600MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-50x75_500x750mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_50X75_500X750MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-50x76_500x760mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_50X76_500X760MM, MediaSizeGroup::kSizeMm}},
-      {"om_photo-60x90_600x900mm",
-       {PRINT_PREVIEW_MEDIA_OM_PHOTO_60X90_600X900MM, MediaSizeGroup::kSizeMm}},
-      // Duplicate of iso_c6c5_114x229mm.
-      {"om_postfix_114x229mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C6C5_114X229MM, MediaSizeGroup::kSizeNamed}},
-      {"om_small-photo_100x150mm",
-       {PRINT_PREVIEW_MEDIA_OM_SMALL_PHOTO_100X150MM, MediaSizeGroup::kSizeMm}},
-      {"om_square-photo_89x89mm",
-       {PRINT_PREVIEW_MEDIA_OM_SQUARE_PHOTO_89X89MM, MediaSizeGroup::kSizeMm}},
-      {"om_wide-photo_100x200mm",
-       {PRINT_PREVIEW_MEDIA_OM_WIDE_PHOTO_100X200MM, MediaSizeGroup::kSizeMm}},
-      // Duplicate of iso_c3_324x458mm.
-      {"prc_10_324x458mm",
-       {PRINT_PREVIEW_MEDIA_ISO_C3_324X458MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_16k_146x215mm",
-       {PRINT_PREVIEW_MEDIA_PRC_16K_146X215MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_1_102x165mm",
-       {PRINT_PREVIEW_MEDIA_PRC_1_102X165MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_2_102x176mm",
-       {PRINT_PREVIEW_MEDIA_PRC_2_102X176MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_32k_97x151mm",
-       {PRINT_PREVIEW_MEDIA_PRC_32K_97X151MM, MediaSizeGroup::kSizeNamed}},
-      // Duplicate of iso_b6_125x176mm.
-      {"prc_3_125x176mm",
-       {PRINT_PREVIEW_MEDIA_ISO_B6_125X176MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_4_110x208mm",
-       {PRINT_PREVIEW_MEDIA_PRC_4_110X208MM, MediaSizeGroup::kSizeNamed}},
-      // Duplicate of iso_dl_110x220mm.
-      {"prc_5_110x220mm",
-       {PRINT_PREVIEW_MEDIA_ISO_DL_110X220MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_6_120x320mm",
-       {PRINT_PREVIEW_MEDIA_PRC_6_120X320MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_7_160x230mm",
-       {PRINT_PREVIEW_MEDIA_PRC_7_160X230MM, MediaSizeGroup::kSizeNamed}},
-      {"prc_8_120x309mm",
-       {PRINT_PREVIEW_MEDIA_PRC_8_120X309MM, MediaSizeGroup::kSizeNamed}},
-      {"roc_16k_7.75x10.75in",
-       {PRINT_PREVIEW_MEDIA_ROC_16K_7_75X10_75IN, MediaSizeGroup::kSizeNamed}},
-      {"roc_8k_10.75x15.5in",
-       {PRINT_PREVIEW_MEDIA_ROC_8K_10_75X15_5IN, MediaSizeGroup::kSizeNamed}},
-  });
-
-  auto* it = kMediaMap.find(vendor_id);
+  auto* it = kMediaMap.find(
+      {size.width() / kMicronsPerPwgUnit, size.height() / kMicronsPerPwgUnit});
   return it != kMediaMap.end()
-             ? MediaSizeInfo{l10n_util::GetStringUTF16(it->second.first),
-                             it->second.second, /*registered_size=*/true}
-             : MediaSizeInfo{u"", MediaSizeGroup::kSizeNamed,
-                             /*registered_size=*/false};
+             ? MediaSizeInfo{std::string(it->second.vendor_id),
+                             l10n_util::GetStringUTF16(it->second.l10n_id),
+                             it->second.sort_group}
+             : MediaSizeInfo{"", u"", MediaSizeGroup::kSizeNamed};
 }
 
-// Generate a human-readable name and sort group from a PWG self-describing
-// name.  If `pwg_name` is not a valid self-describing media size, the returned
-// name will be empty.
-MediaSizeInfo InfoForSelfDescribingSize(const std::string& pwg_name) {
-  // The expected format is area_description_dimensions, and dimensions are
-  // WxHmm or WxHin.  Both W and H can contain decimals.
-  static const base::NoDestructor<re2::RE2> media_name_pattern(
-      "[^_]+_([^_]+)_([\\d.]+)x([\\d.]+)(in|mm)");
-  std::string description;
-  std::string width;
-  std::string height;
-  std::string unit_str;
-  if (!RE2::FullMatch(pwg_name, *media_name_pattern, &description, &width,
-                      &height, &unit_str)) {
-    PRINTER_LOG(ERROR) << "Can't generate name for invalid IPP media size "
-                       << pwg_name;
-    return {u"", MediaSizeGroup::kSizeNamed, /*registered_size=*/false};
-  }
-  Unit units = unit_str == "in" ? Unit::kInches : Unit::kMillimeters;
+// Generate a vendor ID, human-readable name, and sort group from size
+// information.
+MediaSizeInfo InfoForUnregisteredSize(const gfx::Size& size_um) {
+  int width_um = size_um.width();
+  int height_um = size_um.height();
 
-  // If the name appears to end with approximately the paper dimensions, just
-  // display the dimensions.  This avoids having things like "Card 4x6" and
-  // "4 X 7" mixed with "4 x 6 in".
-  static const base::NoDestructor<re2::RE2> description_dimensions_pattern(
-      ".*\\b([\\d.]+)-?x-?([\\d.]+)(in|mm)?$");
-  std::string name_width;
-  std::string name_height;
-  if (RE2::FullMatch(description, *description_dimensions_pattern, &name_width,
-                     &name_height) &&
-      base::StartsWith(width, name_width) &&
-      base::StartsWith(height, name_height)) {
-    switch (units) {
-      case Unit::kInches:
-        return {l10n_util::GetStringFUTF16(
-                    PRINT_PREVIEW_MEDIA_DIMENSIONS_INCHES,
-                    base::ASCIIToUTF16(width), base::ASCIIToUTF16(height)),
-                MediaSizeGroup::kSizeIn, /*registered_size=*/false};
+  // Generate a vendor ID so we have something to populate the field with.
+  std::string vendor_id =
+      base::StrCat({"om_", base::NumberToString(width_um), "x",
+                    base::NumberToString(height_um), "um_",
+                    base::NumberToString(width_um / kMicronsPerMm), "x",
+                    base::NumberToString(height_um / kMicronsPerMm), "mm"});
 
-      case Unit::kMillimeters:
-        return {l10n_util::GetStringFUTF16(PRINT_PREVIEW_MEDIA_DIMENSIONS_MM,
-                                           base::ASCIIToUTF16(width),
-                                           base::ASCIIToUTF16(height)),
-                MediaSizeGroup::kSizeMm, /*registered_size=*/false};
-    }
+  MediaSizeGroup group = MediaSizeGroup::kSizeMm;
+  int message_id = PRINT_PREVIEW_MEDIA_DIMENSIONS_MM;
+  int conversion_factor = kMicronsPerMm;
+  int max_fractional_digits = 0;
+
+  // Try converting to inches. If either width or height is a multiple of
+  // 1/4 inch, display the size as inches. Otherwise, display the size as
+  // millimeters.
+  if (width_um % (kMicronsPerInch / 4) == 0 ||
+      height_um % (kMicronsPerInch / 4) == 0) {
+    group = MediaSizeGroup::kSizeIn;
+    message_id = PRINT_PREVIEW_MEDIA_DIMENSIONS_INCHES;
+    conversion_factor = kMicronsPerInch;
+    max_fractional_digits = 3;
   }
 
-  // For other names, attempt to generate a readable name by splitting into
-  // words and title-casing each word.  Self-describing names are always ASCII,
-  // so it is safe to do case conversion without considering locales.  We don't
-  // have any way to know if the results unambiguously describe a paper size the
-  // user would recognize, so also append the dimensions.  The final output is
-  // dependent on the quality of the descriptions provided by the printer, but
-  // should in any case be better than simply displaying the raw region and
-  // description.
-  std::vector<std::string> words = base::SplitString(
-      description, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-  for (std::string& word : words) {
-    word[0] = base::ToUpperASCII(word[0]);  // Safe due to NONEMPTY split above.
-    for (size_t i = 1; i < word.size(); i++) {
-      word[i] = base::ToLowerASCII(word[i]);
-    }
-  }
-  std::string clean_name = base::JoinString(words, " ");
-
-  switch (units) {
-    case Unit::kInches:
-      return {l10n_util::GetStringFUTF16(
-                  PRINT_PREVIEW_MEDIA_NAME_WITH_DIMENSIONS_INCHES,
-                  base::ASCIIToUTF16(clean_name), base::ASCIIToUTF16(width),
-                  base::ASCIIToUTF16(height)),
-              MediaSizeGroup::kSizeNamed, /*registered_size=*/false};
-
-    case Unit::kMillimeters:
-      return {l10n_util::GetStringFUTF16(
-                  PRINT_PREVIEW_MEDIA_NAME_WITH_DIMENSIONS_MM,
-                  base::ASCIIToUTF16(clean_name), base::ASCIIToUTF16(width),
-                  base::ASCIIToUTF16(height)),
-              MediaSizeGroup::kSizeNamed, /*registered_size=*/false};
-  }
+  // If the width and height are in inches, display them with up to 3 digits
+  // after the decimal point. 3 digits is the sweet spot where even unusual
+  // fractions like sixths, eighths, and ninths are legible without showing the
+  // rounding errors from their conversion to PWG units. The "up to" is
+  // important since even many unregistered sizes include integer dimensions,
+  // and "5 x 6.5 in" is more legible than "5.000 x 6.500 in". Even more
+  // importantly, it matches how registered but unnamed sizes like "3 x 5 in"
+  // are written. For millimeter sizes, round to the nearest integer, since any
+  // fractional part is probably a rounding error from the mm->pt->mm conversion
+  // imposed by the PPD format.
+  return {vendor_id,
+          l10n_util::GetStringFUTF16(
+              message_id,
+              base::FormatDouble(static_cast<double>(width_um) /
+                                     static_cast<double>(conversion_factor),
+                                 0, max_fractional_digits),
+              base::FormatDouble(static_cast<double>(height_um) /
+                                     static_cast<double>(conversion_factor),
+                                 0, max_fractional_digits)),
+          group};
 }
 
 }  // namespace
@@ -819,26 +786,10 @@
                                      PrinterSemanticCapsAndDefaults::Paper p)
     : size_info(msi), paper(p) {}
 
-MediaSizeInfo LocalizePaperDisplayName(const std::string& vendor_id) {
-  // We can't do anything without a vendor ID.
-  if (vendor_id.empty()) {
-    return {u"", MediaSizeGroup::kSizeNamed, /*registered_size=*/false};
-  }
-
-  MediaSizeInfo size_info = InfoForVendorId(vendor_id);
-  if (size_info.name.empty()) {
-    // If it wasn't a standard PWG media size name, check to see if there
-    // is a standard name with the same dimensions.
-    std::string std_vendor_id = std::string(StandardNameForSize(vendor_id));
-    if (!std_vendor_id.empty()) {
-      PRINTER_LOG(DEBUG) << "Mapped non-standard media name " << vendor_id
-                         << " to " << std_vendor_id;
-      size_info = InfoForVendorId(std_vendor_id);
-      size_info.registered_size = false;
-    }
-  }
-  return size_info.name.empty() ? InfoForSelfDescribingSize(vendor_id)
-                                : size_info;
+MediaSizeInfo LocalizePaperDisplayName(const gfx::Size& size_um) {
+  MediaSizeInfo size_info = InfoForStandardSize(size_um);
+  return size_info.display_name.empty() ? InfoForUnregisteredSize(size_um)
+                                        : size_info;
 }
 
 void SortPaperDisplayNames(std::vector<PaperWithSizeInfo>& papers) {
@@ -848,13 +799,6 @@
 
   // Break apart the list into separate sort groups.
   for (auto& p : papers) {
-    // Drop borderless sizes so they don't get sorted ahead of standard sizes.
-    // TODO(b/218752273): Remove once borderless sizes are handled properly.
-    if (base::Contains(p.paper.vendor_id, ".borderless_") ||
-        base::Contains(p.paper.vendor_id, ".fb_")) {
-      continue;
-    }
-
     switch (p.size_info.sort_group) {
       case MediaSizeGroup::kSizeMm:
         mm_sizes.emplace_back(p);
@@ -874,50 +818,30 @@
   std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
   DCHECK(U_SUCCESS(error));
 
-  // Sort dimensional sizes (inch and mm) by width, then height, then name.
-  // Official sizes come before unregistered sizes if they have the same
-  // dimensions.
-  auto size_sort = [&collator](const PaperWithSizeInfo& a,
-                               const PaperWithSizeInfo& b) {
+  // Sort dimensional sizes (inch and mm) by width, then height.
+  auto size_sort = [](const PaperWithSizeInfo& a, const PaperWithSizeInfo& b) {
     const gfx::Size& size_a = a.paper.size_um;
     const gfx::Size& size_b = b.paper.size_um;
 
     if (size_a.width() != size_b.width())
       return size_a.width() < size_b.width();
-
-    if (size_a.height() != size_b.height())
-      return size_a.height() < size_b.height();
-
-    if (a.size_info.registered_size != b.size_info.registered_size) {
-      return a.size_info.registered_size;
-    }
-
-    // Same dimensions and official status.  Sort by display name.
-    UCollationResult comp = base::i18n::CompareString16WithCollator(
-        *collator, a.size_info.name, b.size_info.name);
-    return comp == UCOL_LESS;
+    return size_a.height() < size_b.height();
   };
   std::sort(mm_sizes.begin(), mm_sizes.end(), size_sort);
   std::sort(in_sizes.begin(), in_sizes.end(), size_sort);
 
-  // Sort named sizes by name, then width, then height.  Official sizes with the
-  // same name come before unofficial sizes.
+  // Sort named sizes by name, then width, then height.
   auto name_sort = [&collator](const PaperWithSizeInfo& a,
                                const PaperWithSizeInfo& b) {
     const gfx::Size& size_a = a.paper.size_um;
     const gfx::Size& size_b = b.paper.size_um;
 
     UCollationResult comp = base::i18n::CompareString16WithCollator(
-        *collator, a.size_info.name, b.size_info.name);
+        *collator, a.size_info.display_name, b.size_info.display_name);
     if (comp != UCOL_EQUAL)
       return comp == UCOL_LESS;
 
-    // Same name.  Sort registered sizes ahead of unofficial sizes.
-    if (a.size_info.registered_size != b.size_info.registered_size) {
-      return a.size_info.registered_size;
-    }
-
-    // Same name and registration status.  Sort by width, then height.
+    // Same name.  Sort by width, then height.
     if (size_a.width() != size_b.width())
       return size_a.width() < size_b.width();
     return size_a.height() < size_b.height();
diff --git a/chrome/common/printing/print_media_l10n.h b/chrome/common/printing/print_media_l10n.h
index 642931b..3fcf004c 100644
--- a/chrome/common/printing/print_media_l10n.h
+++ b/chrome/common/printing/print_media_l10n.h
@@ -19,9 +19,9 @@
 };
 
 struct MediaSizeInfo {
-  std::u16string name;
+  std::string vendor_id;
+  std::u16string display_name;
   MediaSizeGroup sort_group;
-  bool registered_size;
 };
 
 struct PaperWithSizeInfo {
@@ -31,13 +31,12 @@
   PrinterSemanticCapsAndDefaults::Paper paper;
 };
 
-// Maps a paper vendor ID to a localized name and sort group.  The returned name
-// will be automatically generated if the vendor ID does not have a known
-// mapping.  If the vendor ID is not a valid PWG self-describing media name,
-// the returned name will be empty.  The returned names are u16strings to
-// facilitate subsequent sorting; they need to be converted to UTF-8 before
-// updating a `Paper` object.
-MediaSizeInfo LocalizePaperDisplayName(const std::string& vendor_id);
+// Maps a paper size to a vendor ID, localized display name and sort group. The
+// returned ID and name will be automatically generated if the size does not
+// have a known mapping. The display names are u16strings to facilitate
+// subsequent sorting; they need to be converted to UTF-8 before updating a
+// `Paper` object.
+MediaSizeInfo LocalizePaperDisplayName(const gfx::Size& size_um);
 
 // Sorts a list of paper sizes in place by using the paired sort groups.
 void SortPaperDisplayNames(std::vector<PaperWithSizeInfo>& papers);
diff --git a/chrome/common/printing/print_media_l10n_unittest.cc b/chrome/common/printing/print_media_l10n_unittest.cc
index da8022d..fa1473f 100644
--- a/chrome/common/printing/print_media_l10n_unittest.cc
+++ b/chrome/common/printing/print_media_l10n_unittest.cc
@@ -21,38 +21,62 @@
 namespace {
 
 struct MediaInfoTestCase {
-  const char* vendor_id;
+  gfx::Size size_um;
+  std::string expected_vendor_id;
   std::u16string expected_localized_name;
   MediaSizeGroup expected_group;
 };
 
 void VerifyLocalizedInfo(const MediaInfoTestCase& test_case) {
-  MediaSizeInfo info = LocalizePaperDisplayName(test_case.vendor_id);
-  EXPECT_EQ(info.name, test_case.expected_localized_name);
+  MediaSizeInfo info = LocalizePaperDisplayName(test_case.size_um);
+  EXPECT_EQ(info.vendor_id, test_case.expected_vendor_id);
+  EXPECT_EQ(info.display_name, test_case.expected_localized_name);
   EXPECT_EQ(info.sort_group, test_case.expected_group);
 }
 
 void VerifyPaperSizeMatch(const PaperWithSizeInfo& lhs,
                           const PaperWithSizeInfo& rhs) {
-  EXPECT_EQ(lhs.size_info.name, rhs.size_info.name);
+  EXPECT_EQ(lhs.size_info.vendor_id, rhs.size_info.vendor_id);
+  EXPECT_EQ(lhs.size_info.display_name, rhs.size_info.display_name);
   EXPECT_EQ(lhs.size_info.sort_group, rhs.size_info.sort_group);
   EXPECT_EQ(lhs.paper, rhs.paper);
 }
 
 }  // namespace
 
-// Verifies that we localize some common names.
-TEST(PrintMediaL10N, LocalizeSomeCommonNames) {
+// Verifies that we localize some common paper sizes.
+TEST(PrintMediaL10N, LocalizeSomeCommonSizes) {
   const MediaInfoTestCase kTestCases[] = {
-      {"na_c_17x22in", u"17 x 22 in", MediaSizeGroup::kSizeIn},
-      {"iso_a0_841x1189mm", u"A0", MediaSizeGroup::kSizeNamed},
-      {"iso_a1_594x841mm", u"A1", MediaSizeGroup::kSizeNamed},
-      {"iso_a4_210x297mm", u"A4", MediaSizeGroup::kSizeNamed},
-      {"na_govt-legal_8x13in", u"8 x 13 in", MediaSizeGroup::kSizeIn},
-      {"na_govt-letter_8x10in", u"8 x 10 in", MediaSizeGroup::kSizeIn},
-      {"na_letter_8.5x11in", u"Letter", MediaSizeGroup::kSizeNamed},
-      {"oe_photo-l_3.5x5in", u"3.5 x 5 in", MediaSizeGroup::kSizeIn},
-      {"om_business-card_55x91mm", u"55 x 91 mm", MediaSizeGroup::kSizeMm},
+      {{431800, 558800},
+       "na_c_17x22in",
+       u"17 x 22 in",
+       MediaSizeGroup::kSizeIn},
+      {{841000, 1189000},
+       "iso_a0_841x1189mm",
+       u"A0",
+       MediaSizeGroup::kSizeNamed},
+      {{594000, 841000}, "iso_a1_594x841mm", u"A1", MediaSizeGroup::kSizeNamed},
+      {{210000, 297000}, "iso_a4_210x297mm", u"A4", MediaSizeGroup::kSizeNamed},
+      {{203200, 330200},
+       "na_govt-legal_8x13in",
+       u"8 x 13 in",
+       MediaSizeGroup::kSizeIn},
+      {{203200, 254000},
+       "na_govt-letter_8x10in",
+       u"8 x 10 in",
+       MediaSizeGroup::kSizeIn},
+      {{215900, 279400},
+       "na_letter_8.5x11in",
+       u"Letter",
+       MediaSizeGroup::kSizeNamed},
+      {{88900, 127000},
+       "oe_photo-l_3.5x5in",
+       u"3.5 x 5 in",
+       MediaSizeGroup::kSizeIn},
+      {{55000, 91000},
+       "om_business-card_55x91mm",
+       u"55 x 91 mm",
+       MediaSizeGroup::kSizeMm},
   };
 
   for (const auto& test_case : kTestCases) {
@@ -60,66 +84,38 @@
   }
 }
 
-// Verifies that we return the empty string when no localization is
-// found for a given media name.
-TEST(PrintMediaL10N, DoWithoutCommonName) {
+// Verifies that we generate a sensible vendor ID and display name when no
+// localization is found for a given media size.
+TEST(PrintMediaL10N, LocalizeNonStandardSizes) {
   const MediaInfoTestCase kTestCases[] = {
-      {"", u"", MediaSizeGroup::kSizeNamed},
-      {"lorem_ipsum_8x10", u"", MediaSizeGroup::kSizeNamed},
-      {"q_e_d_130x200mm", u"", MediaSizeGroup::kSizeNamed},
-      {"not at all a valid vendor ID", u"", MediaSizeGroup::kSizeNamed},
-  };
-
-  for (const auto& test_case : kTestCases) {
-    VerifyLocalizedInfo(test_case);
-  }
-}
-
-// Verifies that duplicates have the same localization.
-TEST(PrintMediaL10N, LocalizeDuplicateNames) {
-  const struct {
-    const char* duplicate_vendor_id;
-    const char* vendor_id;
-  } kTestCases[] = {
-      {"oe_photo-s10r_10x15in", "na_10x15_10x15in"},
-      {"om_large-photo_200x300", "om_large-photo_200x300mm"},
-      {"om_postfix_114x229mm", "iso_c6c5_114x229mm"},
-      {"prc_10_324x458mm", "iso_c3_324x458mm"},
-      {"prc_3_125x176mm", "iso_b6_125x176mm"},
-      {"prc_5_110x220mm", "iso_dl_110x220mm"},
-      {"iso_id-3_88x125mm", "iso_b7_88x125mm"},
-      {"na_letter_8.5x11in", "na_card-letter_8.5x11in"},
-      {"na_letter_8.5x11in", "na_letter.fb_8.5x11in"},
-      {"na_letter_8.5x11in", "na_card-letter.fb_8.5x11in"},
-  };
-
-  for (const auto& test_case : kTestCases) {
-    MediaSizeInfo duplicate =
-        LocalizePaperDisplayName(test_case.duplicate_vendor_id);
-    MediaSizeInfo original = LocalizePaperDisplayName(test_case.vendor_id);
-
-    EXPECT_EQ(duplicate.name, original.name);
-    EXPECT_EQ(duplicate.sort_group, original.sort_group);
-  }
-}
-
-// Verifies that we generate names for unrecognized sizes correctly.
-TEST(PrintMediaL10N, LocalizeSelfDescribingSizes) {
-  const MediaInfoTestCase kTestCases[] = {
-      {"invalid_size", u"", MediaSizeGroup::kSizeNamed},
-      {"om_photo-31x41_310x410mm", u"310 x 410 mm", MediaSizeGroup::kSizeMm},
-      {"om_t-4-x-7_4x7in", u"4 x 7 in", MediaSizeGroup::kSizeIn},
-      {"om_4-x-7_101.6x180.6mm", u"4 X 7 (101.6 x 180.6 mm)",
-       MediaSizeGroup::kSizeNamed},
-      {"om_custom-1_209.9x297.04mm", u"Custom 1 (209.9 x 297.04 mm)",
-       MediaSizeGroup::kSizeNamed},
-      {"om_double-postcard-rotated_200.03x148.17mm",
-       u"Double Postcard Rotated (200.03 x 148.17 mm)",
-       MediaSizeGroup::kSizeNamed},
-      {"oe_photo-8x10-tab_8x10.5in", u"Photo 8x10 Tab (8 x 10.5 in)",
-       MediaSizeGroup::kSizeNamed},
-      {"na_card-letter_8.5x11in", u"Letter", MediaSizeGroup::kSizeNamed},
-      {"na_letter.fb_8.5x11in", u"Letter", MediaSizeGroup::kSizeNamed},
+      {{310000, 410000},
+       "om_310000x410000um_310x410mm",
+       u"310 x 410 mm",
+       MediaSizeGroup::kSizeMm},
+      {{101600, 177800},
+       "om_101600x177800um_101x177mm",
+       u"4 x 7 in",
+       MediaSizeGroup::kSizeIn},
+      {{101600, 180620},
+       "om_101600x180620um_101x180mm",
+       u"4 x 7.111 in",
+       MediaSizeGroup::kSizeIn},
+      {{209900, 297040},
+       "om_209900x297040um_209x297mm",
+       u"210 x 297 mm",
+       MediaSizeGroup::kSizeMm},
+      {{200030, 148170},
+       "om_200030x148170um_200x148mm",
+       u"200 x 148 mm",
+       MediaSizeGroup::kSizeMm},
+      {{203200, 266700},
+       "om_203200x266700um_203x266mm",
+       u"8 x 10.5 in",
+       MediaSizeGroup::kSizeIn},
+      {{133350, 180620},
+       "om_133350x180620um_133x180mm",
+       u"5.25 x 7.111 in",
+       MediaSizeGroup::kSizeIn},
   };
 
   for (const auto& test_case : kTestCases) {
@@ -129,15 +125,13 @@
 
 // Verifies that paper sizes are returned in the expected order of groups.
 TEST(PrintMediaL10N, SortGroupsOrdered) {
-  PaperWithSizeInfo mm = {
-      MediaSizeInfo{u"mm", MediaSizeGroup::kSizeMm, /*registered_size=*/false},
-      Paper{"metric", "mm", gfx::Size()}};
-  PaperWithSizeInfo in = {
-      MediaSizeInfo{u"in", MediaSizeGroup::kSizeIn, /*registered_size=*/false},
-      Paper{"inches", "in", gfx::Size()}};
-  PaperWithSizeInfo named = {MediaSizeInfo{u"named", MediaSizeGroup::kSizeNamed,
-                                           /*registered_size=*/false},
-                             Paper{"named size", "named", gfx::Size()}};
+  PaperWithSizeInfo mm = {MediaSizeInfo{"", u"mm", MediaSizeGroup::kSizeMm},
+                          Paper{"metric", "mm", gfx::Size()}};
+  PaperWithSizeInfo in = {MediaSizeInfo{"", u"in", MediaSizeGroup::kSizeIn},
+                          Paper{"inches", "in", gfx::Size()}};
+  PaperWithSizeInfo named = {
+      MediaSizeInfo{"", u"named", MediaSizeGroup::kSizeNamed},
+      Paper{"named size", "named", gfx::Size()}};
 
   std::vector<PaperWithSizeInfo> papers = {mm, named, in};
   std::vector<PaperWithSizeInfo> expected = {in, mm, named};
@@ -147,48 +141,40 @@
   }
 }
 
-// Verifies that inch paper sizes are sorted by width, height, name.
+// Verifies that inch paper sizes are sorted by width, then height.
 TEST(PrintMediaL10N, SortInchSizes) {
   PaperWithSizeInfo p1 = {
-      MediaSizeInfo{u"1x3", MediaSizeGroup::kSizeIn, /*registered_size=*/false},
+      MediaSizeInfo{"", u"1x3", MediaSizeGroup::kSizeIn},
       Paper{"1x3", "in", gfx::Size(1, 3), gfx::Rect(0, 0, 1, 3)}};
   PaperWithSizeInfo p2 = {
-      MediaSizeInfo{u"2x1", MediaSizeGroup::kSizeIn, /*registered_size=*/false},
+      MediaSizeInfo{"", u"2x1", MediaSizeGroup::kSizeIn},
       Paper{"2x1", "in", gfx::Size(2, 1), gfx::Rect(0, 0, 2, 1)}};
   PaperWithSizeInfo p3 = {
-      MediaSizeInfo{u"2x2", MediaSizeGroup::kSizeIn, /*registered_size=*/false},
+      MediaSizeInfo{"", u"2x2", MediaSizeGroup::kSizeIn},
       Paper{"2x2", "in", gfx::Size(2, 2), gfx::Rect(0, 0, 2, 2)}};
-  PaperWithSizeInfo p4 = {
-      MediaSizeInfo{u"2x2 B", MediaSizeGroup::kSizeIn,
-                    /*registered_size=*/false},
-      Paper{"2x2 B", "in", gfx::Size(2, 2), gfx::Rect(0, 0, 2, 2)}};
 
-  std::vector<PaperWithSizeInfo> papers = {p4, p1, p2, p3};
-  std::vector<PaperWithSizeInfo> expected = {p1, p2, p3, p4};
+  std::vector<PaperWithSizeInfo> papers = {p2, p3, p1};
+  std::vector<PaperWithSizeInfo> expected = {p1, p2, p3};
   SortPaperDisplayNames(papers);
   for (size_t i = 0; i < expected.size(); i++) {
     VerifyPaperSizeMatch(papers[i], expected[i]);
   }
 }
 
-// Verifies that mm paper sizes are sorted by width, height, name.
+// Verifies that mm paper sizes are sorted by width, then height.
 TEST(PrintMediaL10N, SortMmSizes) {
   PaperWithSizeInfo p1 = {
-      MediaSizeInfo{u"1x3", MediaSizeGroup::kSizeMm, /*registered_size=*/false},
+      MediaSizeInfo{"", u"1x3", MediaSizeGroup::kSizeMm},
       Paper{"1x3", "mm", gfx::Size(1, 3), gfx::Rect(0, 0, 1, 3)}};
   PaperWithSizeInfo p2 = {
-      MediaSizeInfo{u"2x1", MediaSizeGroup::kSizeMm, /*registered_size=*/false},
+      MediaSizeInfo{"", u"2x1", MediaSizeGroup::kSizeMm},
       Paper{"2x1", "mm", gfx::Size(2, 1), gfx::Rect(0, 0, 2, 1)}};
   PaperWithSizeInfo p3 = {
-      MediaSizeInfo{u"2x2", MediaSizeGroup::kSizeMm, /*registered_size=*/false},
+      MediaSizeInfo{"", u"2x2", MediaSizeGroup::kSizeMm},
       Paper{"2x2", "mm", gfx::Size(2, 2), gfx::Rect(0, 0, 2, 2)}};
-  PaperWithSizeInfo p4 = {
-      MediaSizeInfo{u"2x2 B", MediaSizeGroup::kSizeMm,
-                    /*registered_size=*/false},
-      Paper{"2x2 B", "mm", gfx::Size(2, 2), gfx::Rect(0, 0, 2, 2)}};
 
-  std::vector<PaperWithSizeInfo> papers = {p4, p1, p2, p3};
-  std::vector<PaperWithSizeInfo> expected = {p1, p2, p3, p4};
+  std::vector<PaperWithSizeInfo> papers = {p2, p3, p1};
+  std::vector<PaperWithSizeInfo> expected = {p1, p2, p3};
   SortPaperDisplayNames(papers);
   for (size_t i = 0; i < expected.size(); i++) {
     VerifyPaperSizeMatch(papers[i], expected[i]);
@@ -198,20 +184,16 @@
 // Verifies that named paper sizes are sorted by name, width, height.
 TEST(PrintMediaL10N, SortNamedSizes) {
   PaperWithSizeInfo p1 = {
-      MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
+      MediaSizeInfo{"", u"AAA", MediaSizeGroup::kSizeNamed},
       Paper{"AAA", "name", gfx::Size(50, 50), gfx::Rect(0, 0, 50, 50)}};
   PaperWithSizeInfo p2 = {
-      MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
+      MediaSizeInfo{"", u"BBB", MediaSizeGroup::kSizeNamed},
       Paper{"BBB", "name1", gfx::Size(1, 3), gfx::Rect(0, 0, 1, 3)}};
   PaperWithSizeInfo p3 = {
-      MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
+      MediaSizeInfo{"", u"BBB", MediaSizeGroup::kSizeNamed},
       Paper{"BBB", "name2", gfx::Size(2, 2), gfx::Rect(0, 0, 2, 2)}};
   PaperWithSizeInfo p4 = {
-      MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
+      MediaSizeInfo{"", u"BBB", MediaSizeGroup::kSizeNamed},
       Paper{"BBB", "name3", gfx::Size(2, 3), gfx::Rect(0, 0, 2, 3)}};
 
   std::vector<PaperWithSizeInfo> papers = {p4, p1, p2, p3};
@@ -222,59 +204,4 @@
   }
 }
 
-TEST(PrintMediaL10N, RemoveBorderlessSizes) {
-  PaperWithSizeInfo p1 = {MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                                        /*registered_size=*/false},
-                          Paper{"AAA", "oe_aaa.fb_8x10in", gfx::Size(8, 10)}};
-  PaperWithSizeInfo p2 = {MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeNamed},
-                          Paper{"BBB", "oe_bbb_4x6in", gfx::Size(4, 6)}};
-  PaperWithSizeInfo p3 = {
-      MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeNamed},
-      Paper{"BBB", "oe_bbb.borderless_4x6in", gfx::Size(4, 6)}};
-  PaperWithSizeInfo p4 = {MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                                        /*registered_size=*/false},
-                          Paper{"AAA", "oe_aaa.8x10in", gfx::Size(8, 10)}};
-
-  std::vector<PaperWithSizeInfo> papers = {p1, p2, p3, p4};
-  std::vector<PaperWithSizeInfo> expected = {p4, p2};
-  SortPaperDisplayNames(papers);
-  ASSERT_EQ(papers.size(), expected.size());
-  for (size_t i = 0; i < expected.size(); i++) {
-    VerifyPaperSizeMatch(papers[i], expected[i]);
-  }
-}
-
-// Verifies that PWG registered size names sort above unregistered names with
-// the same dimensions.
-TEST(PrintMediaL10N, SortNonstandardSizes) {
-  PaperWithSizeInfo p1 = {
-      MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
-      Paper{"AAA", "na_card-letter_8.5x11in", gfx::Size(9, 11)}};
-  PaperWithSizeInfo p2 = {
-      MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                    /*registered_size=*/false},
-      Paper{"AAA", "na_card-letter_8x9in", gfx::Size(8, 11)}};
-  PaperWithSizeInfo p3 = {
-      MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeIn, /*registered_size=*/false},
-      Paper{"AAA", "oe_aaa.8x10in", gfx::Size(8, 10)}};
-  PaperWithSizeInfo p4 = {MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeNamed,
-                                        /*registered_size=*/true},
-                          Paper{"AAA", "na_letter_8.5x11in", gfx::Size(9, 11)}};
-  PaperWithSizeInfo p5 = {
-      MediaSizeInfo{u"BBB", MediaSizeGroup::kSizeIn, /*registered_size=*/true},
-      Paper{"BBB", "na_govt-letter_8x10in", gfx::Size(8, 10)}};
-  PaperWithSizeInfo p6 = {
-      MediaSizeInfo{u"AAA", MediaSizeGroup::kSizeIn, /*registered_size=*/true},
-      Paper{"AAA", "na_govt-letter_8x10in", gfx::Size(8, 10)}};
-
-  std::vector<PaperWithSizeInfo> papers = {p1, p2, p3, p4, p5, p6};
-  std::vector<PaperWithSizeInfo> expected = {p6, p5, p3, p4, p2, p1};
-  SortPaperDisplayNames(papers);
-  ASSERT_EQ(papers.size(), expected.size());
-  for (size_t i = 0; i < expected.size(); i++) {
-    VerifyPaperSizeMatch(papers[i], expected[i]);
-  }
-}
-
 }  // namespace printing
diff --git a/chrome/common/printing/printer_capabilities.cc b/chrome/common/printing/printer_capabilities.cc
index b068271..010f0a5 100644
--- a/chrome/common/printing/printer_capabilities.cc
+++ b/chrome/common/printing/printer_capabilities.cc
@@ -59,22 +59,19 @@
 
 #if BUILDFLAG(PRINT_MEDIA_L10N_ENABLED)
 // Iterate on the `Papers` of a given printer `info` and set the
-// `display_name` members, localizing where possible. We expect the
-// backend to have populated non-empty display names already, so we
-// don't touch media display names that we can't localize.
-// The `Papers` will be sorted in place when this function returns.
-void PopulateAndSortAllPaperDisplayNames(PrinterSemanticCapsAndDefaults& info) {
-  MediaSizeInfo default_paper_display =
-      LocalizePaperDisplayName(info.default_paper.vendor_id);
-  if (!default_paper_display.name.empty()) {
-    info.default_paper.display_name =
-        base::UTF16ToUTF8(default_paper_display.name);
-  }
+// `display_name` members, localizing where possible, as well as the `vendor_id`
+// members. The `Papers` will be sorted in place when this function returns.
+void PopulateAndSortAllPaperNames(PrinterSemanticCapsAndDefaults& info) {
+  MediaSizeInfo default_paper =
+      LocalizePaperDisplayName(info.default_paper.size_um);
+  info.default_paper.vendor_id = default_paper.vendor_id;
+  info.default_paper.display_name =
+      base::UTF16ToUTF8(default_paper.display_name);
 
   // Pair the paper entries with their sort info so they can be sorted.
   std::vector<PaperWithSizeInfo> size_list;
   for (PrinterSemanticCapsAndDefaults::Paper& paper : info.papers) {
-    size_list.emplace_back(LocalizePaperDisplayName(paper.vendor_id),
+    size_list.emplace_back(LocalizePaperDisplayName(paper.size_um),
                            std::move(paper));
   }
 
@@ -83,9 +80,8 @@
   info.papers.clear();
   for (auto& pair : size_list) {
     auto& paper = info.papers.emplace_back(std::move(pair.paper));
-    if (!pair.size_info.name.empty()) {
-      paper.display_name = base::UTF16ToUTF8(pair.size_info.name);
-    }
+    paper.vendor_id = pair.size_info.vendor_id;
+    paper.display_name = base::UTF16ToUTF8(pair.size_info.display_name);
   }
 }
 #endif  // BUILDFLAG(PRINT_MEDIA_L10N_ENABLED)
@@ -119,16 +115,17 @@
     return base::Value();
 
 #if BUILDFLAG(PRINT_MEDIA_L10N_ENABLED)
-  bool populate_paper_display_names = true;
+  bool populate_paper_names = true;
 #if BUILDFLAG(IS_MAC)
-  // Paper display name localization requires standardized vendor ID names
-  // populated by CUPS IPP. If the CUPS IPP backend is not enabled, localization
-  // will not properly occur.
-  populate_paper_display_names =
+  // Paper display name localization and vendor ID assignment is intended for
+  // use with the CUPS IPP backend. If the CUPS IPP backend is not enabled,
+  // localization will not properly occur.
+  populate_paper_names =
       base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend);
 #endif
-  if (populate_paper_display_names)
-    PopulateAndSortAllPaperDisplayNames(*caps);
+  if (populate_paper_names) {
+    PopulateAndSortAllPaperNames(*caps);
+  }
 #endif  // BUILDFLAG(PRINT_MEDIA_L10N_ENABLED)
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/common/printing/printer_capabilities_unittest.cc b/chrome/common/printing/printer_capabilities_unittest.cc
index 019db59..095942a 100644
--- a/chrome/common/printing/printer_capabilities_unittest.cc
+++ b/chrome/common/printing/printer_capabilities_unittest.cc
@@ -206,7 +206,11 @@
 
   // Verify the 3 paper sizes are the ones in |caps->papers|, followed by the
   // ones in |user_defined_papers|.
+#if BUILDFLAG(PRINT_MEDIA_L10N_ENABLED)
+  VerifyPaper((*media_option)[0], "0 x 0 mm", "om_100x234um_0x0mm", {100, 234});
+#else
   VerifyPaper((*media_option)[0], "printer_foo", "printer_vendor", {100, 234});
+#endif
   VerifyPaper((*media_option)[1], "foo", "vendor", {200, 300});
   VerifyPaper((*media_option)[2], "bar", "vendor", {600, 600});
 }
@@ -272,7 +276,8 @@
 
   // Verify the paper sizes are the ones in `caps->papers` in the correct
   // order.
-  VerifyPaper((*media_option)[0], "2 x 3 in", "oe_2x3_2x3in", {50800, 76200});
+  VerifyPaper((*media_option)[0], "2 x 3 in", "om_50800x76200um_50x76mm",
+              {50800, 76200});
   VerifyPaper((*media_option)[1], "3.5 x 5 in", "oe_photo-l_3.5x5in",
               {88900, 127000});
   VerifyPaper((*media_option)[2], "4 x 4 in", "oe_square-photo_4x4in",
@@ -281,18 +286,19 @@
               {101600, 152400});
   VerifyPaper((*media_option)[4], "5 x 5 in", "oe_square-photo_5x5in",
               {127000, 127000});
-  VerifyPaper((*media_option)[5], "1 x 1 mm", "om_1-x-1_1x1mm", {1000, 1000});
-  VerifyPaper((*media_option)[6], "210 x 330 mm", "om_folio_210x330mm",
+  VerifyPaper((*media_option)[5], "9 x 12.1 in", "om_228600x307340um_228x307mm",
+              {228600, 307340});
+  VerifyPaper((*media_option)[6], "1 x 1 mm", "om_1000x1000um_1x1mm",
+              {1000, 1000});
+  VerifyPaper((*media_option)[7], "210 x 330 mm", "om_folio_210x330mm",
               {210000, 330000});
-  VerifyPaper((*media_option)[7], "215 x 315 mm", "om_folio-sp_215x315mm",
+  VerifyPaper((*media_option)[8], "215 x 315 mm", "om_folio-sp_215x315mm",
               {215000, 315000});
-  VerifyPaper((*media_option)[8], "215 x 400 mm", "om_photo-21x40_215x400mm",
-              {215000, 400000});
-  VerifyPaper((*media_option)[9], "A4", "iso_a4_210x297mm", {210000, 297000});
-  VerifyPaper((*media_option)[10], "Custom 1 (9 x 12.1 in)",
-              "na_custom-1_9x12.1in", {228600, 307340});
-  VerifyPaper((*media_option)[11], "Custom 2 (299.6 x 405.3 mm)",
-              "na_custom-2_299.6x405.3mm", {299600, 405300});
+  VerifyPaper((*media_option)[9], "215 x 400 mm",
+              "om_215000x400000um_215x400mm", {215000, 400000});
+  VerifyPaper((*media_option)[10], "300 x 405 mm",
+              "om_299600x405300um_299x405mm", {299600, 405300});
+  VerifyPaper((*media_option)[11], "A4", "iso_a4_210x297mm", {210000, 297000});
   VerifyPaper((*media_option)[12], "Letter", "na_letter_8.5x11in",
               {215900, 279400});
   VerifyPaper((*media_option)[13], "foo", "vendor", {200, 300});
diff --git a/chrome/renderer/net/available_offline_content_helper.cc b/chrome/renderer/net/available_offline_content_helper.cc
index 32acb5bd..88f028e 100644
--- a/chrome/renderer/net/available_offline_content_helper.cc
+++ b/chrome/renderer/net/available_offline_content_helper.cc
@@ -45,37 +45,30 @@
   return encoded;
 }
 
-base::Value AvailableContentToValue(const AvailableOfflineContentPtr& content) {
+base::Value::Dict AvailableContentToValue(
+    const AvailableOfflineContentPtr& content) {
   // All pieces of text content downloaded from the web will be base64 encoded
   // to lessen security risks when this dictionary is passed as a string to
   // |ExecuteJavaScript|.
-  std::string base64_encoded;
-  base::Value value(base::Value::Type::DICT);
-  value.SetKey("ID", base::Value(content->id));
-  value.SetKey("name_space", base::Value(content->name_space));
-  value.SetKey("title_base64",
-               base::Value(ConvertToUTF16Base64(content->title)));
-  value.SetKey("snippet_base64",
-               base::Value(ConvertToUTF16Base64(content->snippet)));
-  value.SetKey("date_modified", base::Value(content->date_modified));
-  value.SetKey("attribution_base64",
-               base::Value(ConvertToUTF16Base64(content->attribution)));
-  value.SetKey("thumbnail_data_uri",
-               base::Value(content->thumbnail_data_uri.spec()));
-  value.SetKey("favicon_data_uri",
-               base::Value(content->favicon_data_uri.spec()));
-  value.SetKey("content_type",
-               base::Value(static_cast<int>(content->content_type)));
-  return value;
+  return base::Value::Dict()
+      .Set("ID", content->id)
+      .Set("name_space", content->name_space)
+      .Set("title_base64", ConvertToUTF16Base64(content->title))
+      .Set("snippet_base64", ConvertToUTF16Base64(content->snippet))
+      .Set("date_modified", content->date_modified)
+      .Set("attribution_base64", ConvertToUTF16Base64(content->attribution))
+      .Set("thumbnail_data_uri", content->thumbnail_data_uri.spec())
+      .Set("favicon_data_uri", content->favicon_data_uri.spec())
+      .Set("content_type", static_cast<int>(content->content_type));
 }
 
-base::Value AvailableContentListToValue(
+base::Value::List AvailableContentListToValue(
     const std::vector<AvailableOfflineContentPtr>& content_list) {
   base::Value::List value;
   for (const auto& content : content_list) {
     value.Append(AvailableContentToValue(content));
   }
-  return base::Value(std::move(value));
+  return value;
 }
 
 void RecordSuggestionPresented(
diff --git a/chrome/services/cups_proxy/cups_proxy_service.cc b/chrome/services/cups_proxy/cups_proxy_service.cc
index 2a978cf..85420994 100644
--- a/chrome/services/cups_proxy/cups_proxy_service.cc
+++ b/chrome/services/cups_proxy/cups_proxy_service.cc
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/no_destructor.h"
 #include "chrome/services/cups_proxy/cups_proxy_service_delegate.h"
 #include "chrome/services/cups_proxy/proxy_manager.h"
 #include "chromeos/ash/components/dbus/cups_proxy/cups_proxy_client.h"
@@ -18,16 +17,15 @@
 
 namespace cups_proxy {
 
+namespace {
+
+CupsProxyService* g_instance = nullptr;
+
+}  // namespace
+
 CupsProxyService::CupsProxyService() = default;
 CupsProxyService::~CupsProxyService() = default;
 
-// static
-void CupsProxyService::Spawn(
-    std::unique_ptr<CupsProxyServiceDelegate> delegate) {
-  static base::NoDestructor<CupsProxyService> service;
-  service->BindToCupsProxyDaemon(std::move(delegate));
-}
-
 void CupsProxyService::BindToCupsProxyDaemon(
     std::unique_ptr<CupsProxyServiceDelegate> delegate) {
   DCHECK(delegate);
@@ -71,4 +69,24 @@
   DVLOG(1) << "CupsProxyService: bootstrap success!";
 }
 
+// static
+void CupsProxyService::Spawn(
+    std::unique_ptr<CupsProxyServiceDelegate> delegate) {
+  DCHECK(!g_instance);
+  g_instance = new CupsProxyService();
+  g_instance->BindToCupsProxyDaemon(std::move(delegate));
+}
+
+// static
+CupsProxyService* CupsProxyService::GetInstance() {
+  return g_instance;
+}
+
+// static
+void CupsProxyService::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+  g_instance = nullptr;
+}
+
 }  // namespace cups_proxy
diff --git a/chrome/services/cups_proxy/cups_proxy_service.h b/chrome/services/cups_proxy/cups_proxy_service.h
index 18669f96..9e42a35 100644
--- a/chrome/services/cups_proxy/cups_proxy_service.h
+++ b/chrome/services/cups_proxy/cups_proxy_service.h
@@ -20,10 +20,6 @@
 // This service lives in the browser process and is managed by the
 // CupsProxyServiceManager. It bootstraps/maintains a mojom connection with the
 // CupsProxyDaemon.
-//
-// Note: There is no method granting a service handle since beyond creation,
-// this service's only client is the daemon, who's connection is managed
-// internally.
 class CupsProxyService {
  public:
   CupsProxyService(const CupsProxyService&) = delete;
@@ -32,8 +28,13 @@
   // Spawns the global service instance.
   static void Spawn(std::unique_ptr<CupsProxyServiceDelegate> delegate);
 
+  // Gets the global service instance. May be null.
+  static CupsProxyService* GetInstance();
+
+  // Destroys the global service instance.
+  static void Shutdown();
+
  private:
-  friend base::NoDestructor<CupsProxyService>;
   CupsProxyService();
   ~CupsProxyService();
 
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_medium.cc b/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
index de7e6ae7..a4a7730 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_medium.cc
@@ -208,6 +208,18 @@
   return false;
 }
 
+bool BleV2Medium::GetRemotePeripheral(const std::string& mac_address,
+                                      GetRemotePeripheralCallback callback) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool BleV2Medium::GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
+                                      GetRemotePeripheralCallback callback) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
 void BleV2Medium::PresentChanged(bool present) {
   NOTIMPLEMENTED();
 }
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_medium.h b/chrome/services/sharing/nearby/platform/ble_v2_medium.h
index 587fa258..b8ff8d631 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_medium.h
+++ b/chrome/services/sharing/nearby/platform/ble_v2_medium.h
@@ -85,6 +85,11 @@
 
   bool IsExtendedAdvertisementsAvailable() override;
 
+  bool GetRemotePeripheral(const std::string& mac_address,
+                           GetRemotePeripheralCallback callback) override;
+  bool GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
+                           GetRemotePeripheralCallback callback) override;
+
  private:
   // bluetooth::mojom::AdapterObserver:
   void PresentChanged(bool present) override;
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_peripheral.cc b/chrome/services/sharing/nearby/platform/ble_v2_peripheral.cc
index f96ecf3..357ea562 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_peripheral.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_peripheral.cc
@@ -19,6 +19,11 @@
   return device_info_->address;
 }
 
+BleV2Peripheral::UniqueId BleV2Peripheral::GetUniqueId() const {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
 void BleV2Peripheral::UpdateDeviceInfo(
     bluetooth::mojom::DeviceInfoPtr device_info) {
   DCHECK_EQ(device_info_->address, device_info->address);
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_peripheral.h b/chrome/services/sharing/nearby/platform/ble_v2_peripheral.h
index 9688ba6..1f8ab5d 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_peripheral.h
+++ b/chrome/services/sharing/nearby/platform/ble_v2_peripheral.h
@@ -24,6 +24,7 @@
   ~BleV2Peripheral() override;
 
   std::string GetAddress() const override;
+  UniqueId GetUniqueId() const override;
   void UpdateDeviceInfo(bluetooth::mojom::DeviceInfoPtr device_info);
 
  private:
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2e96182d..c2dda7f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4476,7 +4476,6 @@
         "//chromeos/ash/components/dbus:test_support",
         "//chromeos/ash/components/dbus/attestation",
         "//chromeos/ash/components/dbus/attestation:attestation_proto",
-        "//chromeos/ash/components/dbus/authpolicy",
         "//chromeos/ash/components/dbus/biod",
         "//chromeos/ash/components/dbus/biod:biod_proto",
         "//chromeos/ash/components/dbus/cicerone",
@@ -7270,13 +7269,13 @@
       "../browser/ui/webui/settings/reset_settings_handler_unittest.cc",
       "../browser/ui/webui/settings/safety_check_extensions_handler_unittest.cc",
       "../browser/ui/webui/settings/safety_check_handler_unittest.cc",
+      "../browser/ui/webui/settings/safety_hub_handler_unittest.cc",
       "../browser/ui/webui/settings/settings_clear_browsing_data_handler_unittest.cc",
       "../browser/ui/webui/settings/settings_manage_profile_handler_unittest.cc",
       "../browser/ui/webui/settings/settings_security_key_handler_unittest.cc",
       "../browser/ui/webui/settings/settings_utils_unittest.cc",
       "../browser/ui/webui/settings/site_settings_handler_unittest.cc",
       "../browser/ui/webui/settings/site_settings_helper_unittest.cc",
-      "../browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc",
       "../browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler_unittest.cc",
       "../browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc",
       "../browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc",
@@ -8573,7 +8572,7 @@
     deps += [ "//dbus:test_support" ]
   }
 
-  if (is_android || is_chromeos_ash || is_win) {
+  if (is_android || is_chromeos || is_win) {
     sources += [ "../browser/media/protected_media_identifier_permission_context_unittest.cc" ]
   }
 
@@ -10601,6 +10600,8 @@
       "../browser/sync/test/integration/migration_watcher.h",
       "../browser/sync/test/integration/preferences_helper.cc",
       "../browser/sync/test/integration/preferences_helper.h",
+      "../browser/sync/test/integration/saved_tab_groups_helper.cc",
+      "../browser/sync/test/integration/saved_tab_groups_helper.h",
       "../browser/sync/test/integration/search_engines_helper.cc",
       "../browser/sync/test/integration/search_engines_helper.h",
       "../browser/sync/test/integration/send_tab_to_self_helper.cc",
@@ -10649,6 +10650,7 @@
     "//components/invalidation/impl",
     "//components/invalidation/impl:test_support",
     "//components/os_crypt/sync:test_support",
+    "//components/saved_tab_groups:core",
     "//components/signin/public/identity_manager:test_support",
     "//components/sync",
     "//components/sync:test_support",
diff --git a/chrome/test/base/devtools_listener.cc b/chrome/test/base/devtools_listener.cc
index 631abfb..177e225 100644
--- a/chrome/test/base/devtools_listener.cc
+++ b/chrome/test/base/devtools_listener.cc
@@ -221,7 +221,7 @@
   // pause in between verification attempts.
   bool missing_script = false;
   for (const auto& entry : *coverage_entries) {
-    const std::string* id = entry.FindStringPath("scriptId");
+    const std::string* id = entry.GetDict().FindString("scriptId");
     CHECK(id) << "Can't extract scriptId: " << entry;
     if (!script_ids.contains(*id)) {
       missing_script = true;
diff --git a/chrome/test/base/extension_js_browser_test.cc b/chrome/test/base/extension_js_browser_test.cc
index 15378717..01969213 100644
--- a/chrome/test/base/extension_js_browser_test.cc
+++ b/chrome/test/base/extension_js_browser_test.cc
@@ -94,11 +94,10 @@
   args.emplace_back(test_name);
   std::vector<std::u16string> scripts;
 
-  base::Value test_runner_params(base::Value::Type::DICT);
+  base::Value::Dict test_runner_params;
   if (embedded_test_server()->Started()) {
-    test_runner_params.SetKey(
-        "testServerBaseUrl",
-        base::Value(embedded_test_server()->base_url().spec()));
+    test_runner_params.Set("testServerBaseUrl",
+                           embedded_test_server()->base_url().spec());
   }
 
   if (!libs_loaded_) {
diff --git a/chrome/test/chromedriver/chrome/cast_tracker_unittest.cc b/chrome/test/chromedriver/chrome/cast_tracker_unittest.cc
index bc0e63b..953d52c 100644
--- a/chrome/test/chromedriver/chrome/cast_tracker_unittest.cc
+++ b/chrome/test/chromedriver/chrome/cast_tracker_unittest.cc
@@ -15,12 +15,11 @@
 
 namespace {
 
-base::Value CreateSink(const std::string& name, const std::string& id) {
-  base::Value sink(base::Value::Type::DICT);
-  sink.SetKey("name", base::Value(name));
-  sink.SetKey("id", base::Value(id));
-  sink.SetKey("session", base::Value("Example session"));
-  return sink;
+base::Value::Dict CreateSink(const std::string& name, const std::string& id) {
+  return base::Value::Dict()
+      .Set("name", base::Value(name))
+      .Set("id", base::Value(id))
+      .Set("session", base::Value("Example session"));
 }
 
 class MockDevToolsClient : public StubDevToolsClient {
@@ -54,9 +53,9 @@
   EXPECT_EQ(0u, cast_tracker_->sinks().GetList().size());
 
   base::Value::List sinks;
-  sinks.Append(CreateSink("sink1", "1"));
-  sinks.Append(CreateSink("sink2", "2"));
-  params.Set("sinks", base::Value(std::move(sinks)));
+  params.Set("sinks", base::Value::List()
+                          .Append(CreateSink("sink1", "1"))
+                          .Append(CreateSink("sink2", "2")));
   cast_tracker_->OnEvent(&devtools_client_, "Cast.sinksUpdated", params);
   EXPECT_EQ(2u, cast_tracker_->sinks().GetList().size());
 
diff --git a/chrome/test/data/web_apps/empty_web_app.json b/chrome/test/data/web_apps/empty_web_app.json
index 8b490ee..1ec1404 100644
--- a/chrome/test/data/web_apps/empty_web_app.json
+++ b/chrome/test/data/web_apps/empty_web_app.json
@@ -40,7 +40,7 @@
    "management_type_to_external_configuration_map": {
    },
    "manifest_icons": [  ],
-   "manifest_id": null,
+   "manifest_id": "",
    "manifest_update_time": "1601-01-01 00:00:00.000 UTC",
    "manifest_url": "",
    "note_taking_new_note_url": "",
@@ -63,7 +63,6 @@
    },
    "tab_strip": null,
    "theme_color": "none",
-   "unhashed_app_id": "",
    "url_handlers": [  ],
    "user_display_mode": "",
    "user_launch_ordinal": "INVALID[]",
diff --git a/chrome/test/data/web_apps/sample_web_app.json b/chrome/test/data/web_apps/sample_web_app.json
index 5aa5ed9..cd6eaa24 100644
--- a/chrome/test/data/web_apps/sample_web_app.json
+++ b/chrome/test/data/web_apps/sample_web_app.json
@@ -196,7 +196,7 @@
       }
    },
    "manifest_icons": [  ],
-   "manifest_id": null,
+   "manifest_id": "https://example.com/scope1234/start1234",
    "manifest_update_time": "1970-02-12 16:20:18.762 UTC",
    "manifest_url": "https://example.com/manifest1234.json",
    "note_taking_new_note_url": "",
@@ -315,7 +315,6 @@
    },
    "tab_strip": null,
    "theme_color": "rgba(151,34,83,0.8823529411764706)",
-   "unhashed_app_id": "https://example.com/scope1234/start1234",
    "url_handlers": [ {
       "exclude_paths": [  ],
       "has_origin_wildcard": true,
diff --git a/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts b/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
index feea448..62c80bd 100644
--- a/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
+++ b/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
@@ -623,6 +623,12 @@
   });
 
   test('moreButton', async function() {
+    // TODO(crbug.com/1432915): flaky on mac.
+    // <if expr="is_macosx">
+    if (1) {
+      this.skip();
+    }
+    // </if>
     await verifyActionOccured(
         browserProxy, PrivacySandboxPromptAction.NOTICE_SHOWN);
     await flushTasks();
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index 594ec6f..6b10b3c5 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -139,7 +139,7 @@
     "test_reset_browser_proxy.ts",
     "test_search_engines_browser_proxy.ts",
     "test_security_keys_browser_proxy.ts",
-    "test_site_settings_permissions_browser_proxy.ts",
+    "test_safety_hub_browser_proxy.ts",
     "test_site_settings_prefs_browser_proxy.ts",
     "test_sync_browser_proxy.ts",
     "test_util.ts",
diff --git a/chrome/test/data/webui/settings/OWNERS b/chrome/test/data/webui/settings/OWNERS
index ad7c7bbf..69e0e85b 100644
--- a/chrome/test/data/webui/settings/OWNERS
+++ b/chrome/test/data/webui/settings/OWNERS
@@ -28,7 +28,7 @@
 per-file site_list_tests.ts=sauski@google.com
 per-file site_settings_page_test.ts=sauski@google.com,rainhard@chromium.org,sideyilmaz@chromium.org
 per-file test_privacy_page_browser_proxy.ts=sauski@google.com,rainhard@chromium.org,sideyilmaz@chromium.org
-per-file test_site_settings_permissions_browser_proxy.ts=rainhard@chromium.org,sauski@google.com,sideyilmaz@chromium.org
+per-file test_safety_hub_browser_proxy.ts=sideyilmaz@chromium.org,rainhard@chromium.org,sauski@google.com
 per-file test_site_settings_prefs_browser_proxy.ts=sauski@google.com,rainhard@chromium.org,sideyilmaz@chromium.org
 per-file unused_site_permissions_interactive_ui_test.ts=rainhard@chromium.org,sideyilmaz@chromium.org
 per-file unused_site_permissions_test.ts=rainhard@chromium.org,sideyilmaz@chromium.org
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 7aae1f4..3aa8747d 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -70,7 +70,6 @@
     "os_paired_bluetooth_list_item_tests.js",
     "os_paired_bluetooth_list_tests.js",
     "os_page_availability_test.ts",
-    "os_people_page_test.js",
     "os_reset_page_test.js",
     "os_saved_devices_list_tests.js",
     "os_saved_devices_subpage_tests.js",
@@ -227,6 +226,8 @@
 
     "os_people_page/add_user_dialog_test.ts",
     "os_people_page/fingerprint_list_subpage_test.ts",
+    "os_people_page/os_people_page_test.ts",
+    "os_people_page/test_account_manager_browser_proxy.ts",
     "os_people_page/test_fingerprint_browser_proxy.ts",
 
     "os_printing_page/os_printing_page_test.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/cups_printer_entry_tests.js b/chrome/test/data/webui/settings/chromeos/cups_printer_entry_tests.js
index 1bc92f1c..bc3cd7e 100644
--- a/chrome/test/data/webui/settings/chromeos/cups_printer_entry_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/cups_printer_entry_tests.js
@@ -119,6 +119,7 @@
     printerEntryTestElement =
         document.createElement('settings-cups-printers-entry');
     assertTrue(!!printerEntryTestElement);
+    printerEntryTestElement.printerStatusReasonCache = new Map();
     document.body.appendChild(printerEntryTestElement);
   });
 
@@ -208,6 +209,11 @@
   // Verify the correct printer status icon is shown based on the printer's
   // status reason.
   test('savedPrinterCorrectPrinterStatusIcon', function() {
+    const printerStatusReasonCache = new Map();
+    printerStatusReasonCache.set('id1', PrinterStatusReason.NO_ERROR);
+    printerStatusReasonCache.set('id2', PrinterStatusReason.OUT_OF_PAPER);
+
+    printerEntryTestElement.printerStatusReasonCache = printerStatusReasonCache;
     printerEntryTestElement.printerEntry =
         createPrinterEntry(PrinterType.SAVED);
 
@@ -218,18 +224,14 @@
             .icon);
 
     // Set to a good status reason.
-    printerEntryTestElement.set(
-        'printerEntry.printerInfo.printerStatusReason',
-        PrinterStatusReason.NO_ERROR);
+    printerEntryTestElement.set('printerEntry.printerInfo.printerId', 'id1');
     assertEquals(
         'os-settings:printer-status-green',
         printerEntryTestElement.shadowRoot.querySelector('#printerStatusIcon')
             .icon);
 
     // Set to an error status reason.
-    printerEntryTestElement.set(
-        'printerEntry.printerInfo.printerStatusReason',
-        PrinterStatusReason.OUT_OF_PAPER);
+    printerEntryTestElement.set('printerEntry.printerInfo.printerId', 'id2');
     assertEquals(
         'os-settings:printer-status-red',
         printerEntryTestElement.shadowRoot.querySelector('#printerStatusIcon')
diff --git a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
index 020fd28..c88e0a73 100644
--- a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
@@ -676,27 +676,27 @@
     assertTrue(!!savedPrintersElement);
 
     // For each of the 3 saved printers verify it gets the correct printer
-    // status reason based on the printer status previously set.
+    // icon based on the printer status previously set.
     const printerListEntries = getPrinterEntries(savedPrintersElement);
     assertEquals(3, printerListEntries.length);
     for (const entry of printerListEntries) {
-      let expectedPrinterStatusReason;
-      const printerInfo = entry.printerEntry.printerInfo;
-      switch (printerInfo.printerId) {
+      let expectedPrinterIcon;
+      switch (entry.printerEntry.printerInfo.printerId) {
         case 'id1':
-          expectedPrinterStatusReason = PrinterStatusReason.NO_ERROR;
+          expectedPrinterIcon = 'os-settings:printer-status-green';
           break;
         case 'id2':
-          expectedPrinterStatusReason = PrinterStatusReason.PRINTER_UNREACHABLE;
+          expectedPrinterIcon = 'os-settings:printer-status-red';
           break;
         case 'id3':
-          expectedPrinterStatusReason = PrinterStatusReason.UNKNOWN_REASON;
+          expectedPrinterIcon = 'os-settings:printer-status-grey';
           break;
         default:
           assertNotReached();
       }
       assertEquals(
-          expectedPrinterStatusReason, printerInfo.printerStatusReason);
+          expectedPrinterIcon,
+          entry.shadowRoot.querySelector('#printerStatusIcon').icon);
     }
   });
 
diff --git a/chrome/test/data/webui/settings/chromeos/os_page_availability_test.ts b/chrome/test/data/webui/settings/chromeos/os_page_availability_test.ts
index 8929ad9d..9251444 100644
--- a/chrome/test/data/webui/settings/chromeos/os_page_availability_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_page_availability_test.ts
@@ -30,7 +30,7 @@
 
     const loadTimeControlled: LoadTimeControlledPage[] = [
       {pageName: 'kerberos', loadTimeId: 'isKerberosEnabled'},
-      {pageName: 'reset', loadTimeId: 'allowPowerwash'},
+      {pageName: 'osReset', loadTimeId: 'allowPowerwash'},
     ];
     loadTimeControlled.forEach(({pageName, loadTimeId}) => {
       test(`${pageName} page is available when ${loadTimeId}=true`, () => {
@@ -49,7 +49,6 @@
 
   suite('When signed in as user', () => {
     const alwaysAvailable: Array<keyof OsPageAvailability> = [
-      'a11y',
       'apps',
       'bluetooth',
       'crostini',
@@ -57,13 +56,14 @@
       'device',
       'files',
       'internet',
-      'languages',
       'multidevice',
-      'people',
+      'osAccessibility',
+      'osLanguages',
+      'osPeople',
+      'osPrinting',
+      'osPrivacy',
+      'osSearch',
       'personalization',
-      'printing',
-      'privacy',
-      'search',
     ];
     alwaysAvailable.forEach((pageName) => {
       test(`${pageName} page should always be available`, () => {
@@ -81,17 +81,17 @@
     });
 
     const alwaysAvailable: Array<keyof OsPageAvailability> = [
-      'a11y',
       'apps',
       'bluetooth',
       'crostini',
       'dateTime',
       'device',
       'internet',
-      'languages',
-      'printing',
-      'privacy',
-      'search',
+      'osAccessibility',
+      'osLanguages',
+      'osPrinting',
+      'osPrivacy',
+      'osSearch',
     ];
     alwaysAvailable.forEach((pageName) => {
       test(`${pageName} page should always be available`, () => {
@@ -103,7 +103,7 @@
     const neverAvailable: Array<keyof OsPageAvailability> = [
       'files',
       'multidevice',
-      'people',
+      'osPeople',
       'personalization',
     ];
     neverAvailable.forEach((pageName) => {
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/os_people_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/os_people_page_test.ts
new file mode 100644
index 0000000..484cb54
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/os_people_page_test.ts
@@ -0,0 +1,275 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-settings/chromeos/os_settings.js';
+
+import {AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {createPageAvailabilityForTesting, OsSettingsPeoplePageElement, PageStatus, ProfileInfoBrowserProxy, ProfileInfoBrowserProxyImpl, Router, routes, settingMojom, SyncBrowserProxy, SyncBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
+import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertStringContains, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+
+import {TestSyncBrowserProxy} from '../test_os_sync_browser_proxy.js';
+import {TestProfileInfoBrowserProxy} from '../test_profile_info_browser_proxy.js';
+
+import {TestAccountManagerBrowserProxy} from './test_account_manager_browser_proxy.js';
+
+suite('<os-settings-people-page>', () => {
+  let peoplePage: OsSettingsPeoplePageElement;
+  let browserProxy: ProfileInfoBrowserProxy&TestProfileInfoBrowserProxy;
+  let syncBrowserProxy: SyncBrowserProxy&TestSyncBrowserProxy;
+  let accountManagerBrowserProxy: AccountManagerBrowserProxy&
+      TestAccountManagerBrowserProxy;
+
+  setup(() => {
+    browserProxy = new TestProfileInfoBrowserProxy();
+    ProfileInfoBrowserProxyImpl.setInstance(browserProxy);
+
+    syncBrowserProxy = new TestSyncBrowserProxy();
+    SyncBrowserProxyImpl.setInstance(syncBrowserProxy);
+
+    accountManagerBrowserProxy = new TestAccountManagerBrowserProxy();
+    AccountManagerBrowserProxyImpl.setInstanceForTesting(
+        accountManagerBrowserProxy);
+  });
+
+  teardown(() => {
+    peoplePage.remove();
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  test('Profile name and picture, account manager disabled', async () => {
+    loadTimeData.overrideValues({
+      isAccountManagerEnabled: false,
+    });
+    peoplePage = document.createElement('os-settings-people-page');
+    peoplePage.pageAvailability = createPageAvailabilityForTesting();
+    document.body.appendChild(peoplePage);
+
+    await browserProxy.whenCalled('getProfileInfo');
+    await syncBrowserProxy.whenCalled('getSyncStatus');
+    flush();
+
+    // Get page elements.
+    const profileIconEl =
+        peoplePage.shadowRoot!.querySelector<HTMLElement>('#profile-icon');
+    assertTrue(!!profileIconEl);
+    const profileRowEl = peoplePage.shadowRoot!.querySelector('#profile-row');
+    assertTrue(!!profileRowEl);
+    const profileNameEl = peoplePage.shadowRoot!.querySelector('#profile-name');
+    assertTrue(!!profileNameEl);
+
+    assertEquals(
+        browserProxy.fakeProfileInfo.name, profileNameEl.textContent!.trim());
+    const bg = profileIconEl.style.backgroundImage;
+    assertTrue(bg.includes(browserProxy.fakeProfileInfo.iconUrl));
+    const profileLabelEl =
+        peoplePage.shadowRoot!.querySelector('#profile-label');
+    assertTrue(!!profileLabelEl);
+    assertEquals('fakeUsername', profileLabelEl.textContent!.trim());
+
+    const iconDataUrl = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEA' +
+        'LAAAAAABAAEAAAICTAEAOw==';
+    webUIListenerCallback(
+        'profile-info-changed', {name: 'pushedName', iconUrl: iconDataUrl});
+
+    flush();
+    assertEquals('pushedName', profileNameEl.textContent!.trim());
+    const newBg = profileIconEl.style.backgroundImage;
+    assertTrue(newBg.includes(iconDataUrl));
+
+    // Profile row items aren't actionable.
+    assertFalse(profileIconEl.hasAttribute('actionable'));
+    assertFalse(profileRowEl.hasAttribute('actionable'));
+
+    // Sub-page trigger is hidden.
+    const element = peoplePage.shadowRoot!.querySelector<CrIconButtonElement>(
+        '#account-manager-subpage-trigger');
+    assertTrue(!!element);
+    assertTrue(element.hidden);
+  });
+
+  test('parental controls page is shown when enabled', () => {
+    loadTimeData.overrideValues({
+      // Simulate parental controls.
+      showParentalControls: true,
+    });
+
+    peoplePage = document.createElement('os-settings-people-page');
+    document.body.appendChild(peoplePage);
+    flush();
+
+    // Setup button is shown and enabled.
+    assert(peoplePage.shadowRoot!.querySelector(
+        'settings-parental-controls-page'));
+  });
+
+  test('Deep link to parental controls page', async () => {
+    loadTimeData.overrideValues({
+      // Simulate parental controls.
+      showParentalControls: true,
+    });
+
+    peoplePage = document.createElement('os-settings-people-page');
+    document.body.appendChild(peoplePage);
+    flush();
+
+    const params = new URLSearchParams();
+    params.append(
+        'settingId', settingMojom.Setting.kSetUpParentalControls.toString());
+    Router.getInstance().navigateTo(routes.OS_PEOPLE, params);
+
+    const element =
+        peoplePage.shadowRoot!.querySelector('settings-parental-controls-page');
+    assertTrue(!!element);
+    const deepLinkElement =
+        element.shadowRoot!.querySelector<HTMLElement>('#setupButton');
+    assertTrue(!!deepLinkElement);
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Setup button should be focused for settingId=315.');
+  });
+
+  test('Deep link to encryption options on old sync page', async () => {
+    peoplePage = document.createElement('os-settings-people-page');
+    document.body.appendChild(peoplePage);
+    flush();
+
+    // Load the sync page.
+    Router.getInstance().navigateTo(routes.SYNC);
+    flush();
+    await waitAfterNextRender(peoplePage);
+
+    // Make the sync page configurable.
+    const syncPage =
+        peoplePage.shadowRoot!.querySelector('os-settings-sync-subpage');
+    assertTrue(!!syncPage);
+    syncPage.syncPrefs = {
+      customPassphraseAllowed: true,
+      passphraseRequired: false,
+      appsRegistered: false,
+      appsSynced: false,
+      autofillRegistered: false,
+      autofillSynced: false,
+      bookmarksRegistered: false,
+      bookmarksSynced: false,
+      encryptAllData: false,
+      extensionsRegistered: false,
+      extensionsSynced: false,
+      passwordsRegistered: false,
+      passwordsSynced: false,
+      paymentsIntegrationEnabled: false,
+      preferencesRegistered: false,
+      preferencesSynced: false,
+      readingListRegistered: false,
+      readingListSynced: false,
+      syncAllDataTypes: false,
+      tabsRegistered: false,
+      tabsSynced: false,
+      themesRegistered: false,
+      themesSynced: false,
+      trustedVaultKeysRequired: false,
+      typedUrlsRegistered: false,
+      typedUrlsSynced: false,
+      wifiConfigurationsRegistered: false,
+      wifiConfigurationsSynced: false,
+    };
+    webUIListenerCallback('page-status-changed', PageStatus.CONFIGURE);
+    const configureElement = syncPage.shadowRoot!.querySelector<HTMLElement>(
+        '#' + PageStatus.CONFIGURE);
+    assertTrue(!!configureElement);
+    assertFalse(configureElement.hidden);
+    const spinnerElement = syncPage.shadowRoot!.querySelector<HTMLElement>(
+        '#' + PageStatus.SPINNER);
+    assertTrue(!!spinnerElement);
+    assertTrue(spinnerElement.hidden);
+
+    // Try the deep link.
+    const params = new URLSearchParams();
+    params.append(
+        'settingId',
+        settingMojom.Setting.kNonSplitSyncEncryptionOptions.toString());
+    Router.getInstance().navigateTo(routes.SYNC, params);
+
+    // Flush to make sure the dropdown expands.
+    flush();
+    const element = syncPage.shadowRoot!.querySelector(
+        'os-settings-sync-encryption-options');
+    assertTrue(!!element);
+    const radioGroupElement =
+        element.shadowRoot!.querySelector<CrRadioGroupElement>(
+            '#encryptionRadioGroup');
+    assertTrue(!!radioGroupElement);
+    const radioButton = radioGroupElement.get('buttons_')[0];
+    assertTrue(!!radioButton);
+    const deepLinkElement = radioButton.shadowRoot!.querySelector('#button');
+    assert(deepLinkElement);
+
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Encryption option should be focused for settingId=316.');
+  });
+
+  test('GAIA name and picture, account manager enabled', async () => {
+    const fakeOsProfileName = 'Currently signed in as username';
+    loadTimeData.overrideValues({
+      isAccountManagerEnabled: true,
+      // settings-account-manager-subpage requires this to have a value.
+      secondaryGoogleAccountSigninAllowed: true,
+      osProfileName: fakeOsProfileName,
+    });
+    peoplePage = document.createElement('os-settings-people-page');
+    peoplePage.pageAvailability = createPageAvailabilityForTesting();
+    document.body.appendChild(peoplePage);
+
+    await accountManagerBrowserProxy.whenCalled('getAccounts');
+    await syncBrowserProxy.whenCalled('getSyncStatus');
+    flush();
+
+    // Get page elements.
+    const profileIconEl =
+        peoplePage.shadowRoot!.querySelector<HTMLElement>('#profile-icon');
+    assertTrue(!!profileIconEl);
+    const profileRowEl = peoplePage.shadowRoot!.querySelector('#profile-row');
+    assertTrue(!!profileRowEl);
+    const profileNameEl = peoplePage.shadowRoot!.querySelector('#profile-name');
+    assertTrue(!!profileNameEl);
+
+    assertStringContains(
+        profileIconEl.style.backgroundImage,
+        'data:image/png;base64,primaryAccountPicData');
+    assertEquals(fakeOsProfileName, profileNameEl.textContent!.trim());
+
+    // Rather than trying to mock sendWithPromise('getPluralString', ...)
+    // just force an update.
+    await peoplePage['updateAccounts_']();
+    const profileLabelEl =
+        peoplePage.shadowRoot!.querySelector('#profile-label');
+    assertTrue(!!profileLabelEl);
+    assertEquals('3 Google Accounts', profileLabelEl.textContent!.trim());
+
+    // Profile row items are actionable.
+    assertTrue(profileIconEl.hasAttribute('actionable'));
+    assertTrue(profileRowEl.hasAttribute('actionable'));
+
+    // Sub-page trigger is shown.
+    const subpageTrigger =
+        peoplePage.shadowRoot!.querySelector<CrIconButtonElement>(
+            '#account-manager-subpage-trigger');
+    assertTrue(!!subpageTrigger);
+    assertFalse(subpageTrigger.hidden);
+
+    // Sub-page trigger navigates to Google account manager.
+    subpageTrigger.click();
+    assertEquals(routes.ACCOUNT_MANAGER, Router.getInstance().currentRoute);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/test_account_manager_browser_proxy.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/test_account_manager_browser_proxy.ts
new file mode 100644
index 0000000..07ac90c9
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/test_account_manager_browser_proxy.ts
@@ -0,0 +1,83 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {Account, AccountManagerBrowserProxy} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+export class TestAccountManagerBrowserProxy extends TestBrowserProxy implements
+    AccountManagerBrowserProxy {
+  constructor() {
+    super([
+      'getAccounts',
+      'addAccount',
+      'reauthenticateAccount',
+      'removeAccount',
+      'migrateAccount',
+      'changeArcAvailability',
+    ]);
+  }
+
+  getAccounts(): Promise<Account[]> {
+    this.methodCalled('getAccounts');
+
+    return Promise.resolve([
+      {
+        id: '123',
+        accountType: 1,
+        isDeviceAccount: true,
+        isSignedIn: true,
+        unmigrated: false,
+        isManaged: true,
+        fullName: 'Primary Account',
+        pic: 'data:image/png;base64,primaryAccountPicData',
+        email: 'primary@gmail.com',
+        isAvailableInArc: true,
+      },
+      {
+        id: '456',
+        accountType: 1,
+        isDeviceAccount: false,
+        isSignedIn: true,
+        unmigrated: false,
+        isManaged: false,
+        fullName: 'Secondary Account 1',
+        email: 'user1@example.com',
+        pic: '',
+        isAvailableInArc: true,
+      },
+      {
+        id: '789',
+        accountType: 1,
+        isDeviceAccount: false,
+        isSignedIn: false,
+        unmigrated: false,
+        isManaged: false,
+        fullName: 'Secondary Account 2',
+        email: 'user2@example.com',
+        pic: '',
+        isAvailableInArc: false,
+      },
+    ]);
+  }
+
+  addAccount(): void {
+    this.methodCalled('addAccount');
+  }
+
+  reauthenticateAccount(accountEmail: string): void {
+    this.methodCalled('reauthenticateAccount', accountEmail);
+  }
+
+  removeAccount(account: Account): void {
+    this.methodCalled('removeAccount', account);
+  }
+
+  migrateAccount(accountEmail: string): void {
+    this.methodCalled('migrateAccount', accountEmail);
+  }
+
+  changeArcAvailability(account: Account, isAvailableInArc: boolean): void {
+    this.methodCalled('changeArcAvailability', [account, isAvailableInArc]);
+  }
+}
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page_test.js b/chrome/test/data/webui/settings/chromeos/os_people_page_test.js
deleted file mode 100644
index c3a529a..0000000
--- a/chrome/test/data/webui/settings/chromeos/os_people_page_test.js
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://os-settings/chromeos/os_settings.js';
-
-import {AccountManagerBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
-import {createPageAvailabilityForTesting, PageStatus, ProfileInfoBrowserProxyImpl, Router, routes, SyncBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
-import {assert} from 'chrome://resources/ash/common/assert.js';
-import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js';
-import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {TestProfileInfoBrowserProxy} from 'chrome://webui-test/settings/chromeos/test_profile_info_browser_proxy.js';
-import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
-
-import {TestSyncBrowserProxy} from './test_os_sync_browser_proxy.js';
-
-/** @implements {AccountManagerBrowserProxy} */
-class TestAccountManagerBrowserProxy extends TestBrowserProxy {
-  constructor() {
-    super([
-      'getAccounts',
-      'addAccount',
-      'reauthenticateAccount',
-      'removeAccount',
-    ]);
-  }
-
-  /** @override */
-  getAccounts() {
-    this.methodCalled('getAccounts');
-
-    return Promise.resolve([
-      {
-        id: '123',
-        accountType: 1,
-        isDeviceAccount: true,
-        isSignedIn: true,
-        unmigrated: false,
-        fullName: 'Primary Account',
-        pic: 'data:image/png;base64,primaryAccountPicData',
-        email: 'primary@gmail.com',
-      },
-      {
-        id: '456',
-        accountType: 1,
-        isDeviceAccount: false,
-        isSignedIn: true,
-        unmigrated: false,
-        fullName: 'Secondary Account 1',
-        email: 'user1@example.com',
-        pic: '',
-      },
-      {
-        id: '789',
-        accountType: 1,
-        isDeviceAccount: false,
-        isSignedIn: false,
-        unmigrated: false,
-        fullName: 'Secondary Account 2',
-        email: 'user2@example.com',
-        pic: '',
-      },
-    ]);
-  }
-
-  /** @override */
-  addAccount() {
-    this.methodCalled('addAccount');
-  }
-
-  /** @override */
-  reauthenticateAccount(account_email) {
-    this.methodCalled('reauthenticateAccount', account_email);
-  }
-
-  /** @override */
-  removeAccount(account) {
-    this.methodCalled('removeAccount', account);
-  }
-}
-
-suite('PeoplePageTests', function() {
-  /** @type {SettingsPeoplePageElement} */
-  let peoplePage = null;
-  /** @type {ProfileInfoBrowserProxy} */
-  let browserProxy = null;
-  /** @type {SyncBrowserProxy} */
-  let syncBrowserProxy = null;
-  /** @type {AccountManagerBrowserProxy} */
-  let accountManagerBrowserProxy = null;
-
-  setup(function() {
-    browserProxy = new TestProfileInfoBrowserProxy();
-    ProfileInfoBrowserProxyImpl.setInstance(browserProxy);
-
-    syncBrowserProxy = new TestSyncBrowserProxy();
-    SyncBrowserProxyImpl.setInstance(syncBrowserProxy);
-
-    accountManagerBrowserProxy = new TestAccountManagerBrowserProxy();
-    AccountManagerBrowserProxyImpl.setInstanceForTesting(
-        accountManagerBrowserProxy);
-
-    PolymerTest.clearBody();
-  });
-
-  teardown(function() {
-    peoplePage.remove();
-    Router.getInstance().resetRouteForTesting();
-  });
-
-  test('Profile name and picture, account manager disabled', async () => {
-    loadTimeData.overrideValues({
-      isAccountManagerEnabled: false,
-    });
-    peoplePage = document.createElement('os-settings-people-page');
-    peoplePage.pageAvailability = createPageAvailabilityForTesting();
-    document.body.appendChild(peoplePage);
-
-    await browserProxy.whenCalled('getProfileInfo');
-    await syncBrowserProxy.whenCalled('getSyncStatus');
-    flush();
-
-    // Get page elements.
-    const profileIconEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-icon'));
-    const profileRowEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-row'));
-    const profileNameEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-name'));
-
-    assertEquals(
-        browserProxy.fakeProfileInfo.name, profileNameEl.textContent.trim());
-    const bg = profileIconEl.style.backgroundImage;
-    assertTrue(bg.includes(browserProxy.fakeProfileInfo.iconUrl));
-    assertEquals(
-        'fakeUsername',
-        peoplePage.shadowRoot.querySelector('#profile-label')
-            .textContent.trim());
-
-    const iconDataUrl = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEA' +
-        'LAAAAAABAAEAAAICTAEAOw==';
-    webUIListenerCallback(
-        'profile-info-changed', {name: 'pushedName', iconUrl: iconDataUrl});
-
-    flush();
-    assertEquals('pushedName', profileNameEl.textContent.trim());
-    const newBg = profileIconEl.style.backgroundImage;
-    assertTrue(newBg.includes(iconDataUrl));
-
-    // Profile row items aren't actionable.
-    assertFalse(profileIconEl.hasAttribute('actionable'));
-    assertFalse(profileRowEl.hasAttribute('actionable'));
-
-    // Sub-page trigger is hidden.
-    assertTrue(
-        peoplePage.shadowRoot.querySelector('#account-manager-subpage-trigger')
-            .hidden);
-  });
-
-  test('parental controls page is shown when enabled', () => {
-    loadTimeData.overrideValues({
-      // Simulate parental controls.
-      showParentalControls: true,
-    });
-
-    peoplePage = document.createElement('os-settings-people-page');
-    document.body.appendChild(peoplePage);
-    flush();
-
-    // Setup button is shown and enabled.
-    assert(
-        peoplePage.shadowRoot.querySelector('settings-parental-controls-page'));
-  });
-
-  test('Deep link to parental controls page', async () => {
-    loadTimeData.overrideValues({
-      // Simulate parental controls.
-      showParentalControls: true,
-    });
-
-    peoplePage = document.createElement('os-settings-people-page');
-    document.body.appendChild(peoplePage);
-    flush();
-
-    const params = new URLSearchParams();
-    params.append('settingId', '315');
-    Router.getInstance().navigateTo(routes.OS_PEOPLE, params);
-
-    const deepLinkElement =
-        peoplePage.shadowRoot.querySelector('settings-parental-controls-page')
-            .shadowRoot.querySelector('#setupButton');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Setup button should be focused for settingId=315.');
-  });
-
-  test('Deep link to encryption options on old sync page', async () => {
-    peoplePage = document.createElement('os-settings-people-page');
-    document.body.appendChild(peoplePage);
-    flush();
-
-    // Load the sync page.
-    Router.getInstance().navigateTo(routes.SYNC);
-    flush();
-    await waitAfterNextRender(peoplePage);
-
-    // Make the sync page configurable.
-    const syncPage =
-        peoplePage.shadowRoot.querySelector('os-settings-sync-subpage');
-    assert(syncPage);
-    syncPage.syncPrefs = {
-      customPassphraseAllowed: true,
-      passphraseRequired: false,
-    };
-    webUIListenerCallback('page-status-changed', PageStatus.CONFIGURE);
-    assertFalse(
-        syncPage.shadowRoot.querySelector('#' + PageStatus.CONFIGURE).hidden);
-    assertTrue(
-        syncPage.shadowRoot.querySelector('#' + PageStatus.SPINNER).hidden);
-
-    // Try the deep link.
-    const params = new URLSearchParams();
-    params.append('settingId', '316');
-    Router.getInstance().navigateTo(routes.SYNC, params);
-
-    // Flush to make sure the dropdown expands.
-    flush();
-    const deepLinkElement =
-        syncPage.shadowRoot.querySelector('os-settings-sync-encryption-options')
-            .shadowRoot.querySelector('#encryptionRadioGroup')
-            .buttons_[0]
-            .shadowRoot.querySelector('#button');
-    assert(deepLinkElement);
-
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Encryption option should be focused for settingId=316.');
-  });
-
-  test('GAIA name and picture, account manager enabled', async () => {
-    const fakeOsProfileName = 'Currently signed in as username';
-    loadTimeData.overrideValues({
-      isAccountManagerEnabled: true,
-      // settings-account-manager-subpage requires this to have a value.
-      secondaryGoogleAccountSigninAllowed: true,
-      osProfileName: fakeOsProfileName,
-    });
-    peoplePage = document.createElement('os-settings-people-page');
-    peoplePage.pageAvailability = createPageAvailabilityForTesting();
-    document.body.appendChild(peoplePage);
-
-    await accountManagerBrowserProxy.whenCalled('getAccounts');
-    await syncBrowserProxy.whenCalled('getSyncStatus');
-    flush();
-
-    // Get page elements.
-    const profileIconEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-icon'));
-    const profileRowEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-row'));
-    const profileNameEl =
-        assert(peoplePage.shadowRoot.querySelector('#profile-name'));
-
-    chai.assert.include(
-        profileIconEl.style.backgroundImage,
-        'data:image/png;base64,primaryAccountPicData');
-    assertEquals(fakeOsProfileName, profileNameEl.textContent.trim());
-
-    // Rather than trying to mock sendWithPromise('getPluralString', ...)
-    // just force an update.
-    await peoplePage.updateAccounts_();
-    assertEquals(
-        '3 Google Accounts',
-        peoplePage.shadowRoot.querySelector('#profile-label')
-            .textContent.trim());
-
-    // Profile row items are actionable.
-    assertTrue(profileIconEl.hasAttribute('actionable'));
-    assertTrue(profileRowEl.hasAttribute('actionable'));
-
-    // Sub-page trigger is shown.
-    const subpageTrigger =
-        peoplePage.shadowRoot.querySelector('#account-manager-subpage-trigger');
-    assertFalse(subpageTrigger.hidden);
-
-    // Sub-page trigger navigates to Google account manager.
-    subpageTrigger.click();
-    assertEquals(Router.getInstance().currentRoute, routes.ACCOUNT_MANAGER);
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 0a6d420b..79272c03 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -550,6 +550,7 @@
  ['OsPairedBluetoothList', 'os_paired_bluetooth_list_tests.js'],
  ['OsPairedBluetoothListItem', 'os_paired_bluetooth_list_item_tests.js'],
  ['OsPageAvailability', 'os_page_availability_test.js'],
+ ['OsPeoplePage', 'os_people_page/os_people_page_test.js'],
  ['OsPeoplePageAddUserDialog', 'os_people_page/add_user_dialog_test.js'],
  [
    'OsPeoplePageFingerprintListSubpage',
@@ -590,7 +591,6 @@
    'ParentalControlsPage',
    'parental_controls_page/parental_controls_page_test.js'
  ],
- ['PeoplePage', 'os_people_page_test.js'],
  [
    'PeoplePageAccountManagerSubpage',
    'people_page_account_manager_subpage_test.js',
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
index 58e22b8e..05cc8666 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
@@ -193,7 +193,7 @@
     });
 
     [{
-      pageName: 'reset',
+      pageName: 'osReset',
       elementName: 'os-settings-reset-page',
     },
      {
diff --git a/chrome/test/data/webui/settings/safety_check_permissions_test.ts b/chrome/test/data/webui/settings/safety_check_permissions_test.ts
index 2ca6585..fccc10d 100644
--- a/chrome/test/data/webui/settings/safety_check_permissions_test.ts
+++ b/chrome/test/data/webui/settings/safety_check_permissions_test.ts
@@ -8,29 +8,29 @@
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {MetricsBrowserProxyImpl, Route, Router, routes, SafetyCheckIconStatus, SafetyCheckInteractions, SettingsRoutes, SettingsSafetyCheckNotificationPermissionsElement, SettingsSafetyCheckPageElement, SettingsSafetyCheckUnusedSitePermissionsElement} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {ContentSettingsTypes, NotificationPermission, UnusedSitePermissions, SiteSettingsPermissionsBrowserProxyImpl, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {ContentSettingsTypes, NotificationPermission, UnusedSitePermissions, SafetyHubBrowserProxyImpl, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
 import {assertSafetyCheckChild} from './safety_check_test_utils.js';
 import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
-import {TestSiteSettingsPermissionsBrowserProxy} from './test_site_settings_permissions_browser_proxy.js';
+import {TestSafetyHubBrowserProxy} from './test_safety_hub_browser_proxy.js';
 import {TestSiteSettingsPrefsBrowserProxy} from './test_site_settings_prefs_browser_proxy.js';
 // clang-format on
 
 suite('SafetyCheckUnusedSitePermissionsUiTests', function() {
   let page: SettingsSafetyCheckUnusedSitePermissionsElement;
   let testRoutes: SettingsRoutes;
-  let browserProxy: TestSiteSettingsPermissionsBrowserProxy;
+  let browserProxy: TestSafetyHubBrowserProxy;
   let metricsBrowserProxy: TestMetricsBrowserProxy;
 
   const origin1 = 'www.example1.com';
   const origin2 = 'www.example2.com';
 
   setup(function() {
-    browserProxy = new TestSiteSettingsPermissionsBrowserProxy();
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(browserProxy);
+    browserProxy = new TestSafetyHubBrowserProxy();
+    SafetyHubBrowserProxyImpl.setInstance(browserProxy);
     metricsBrowserProxy = new TestMetricsBrowserProxy();
     MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
     testRoutes = {
@@ -221,7 +221,7 @@
   let testRoutes: SettingsRoutes;
   let metricsBrowserProxy: TestMetricsBrowserProxy;
   let prefsBrowserProxy: TestSiteSettingsPrefsBrowserProxy;
-  let permissionsBrowserProxy: TestSiteSettingsPermissionsBrowserProxy;
+  let permissionsBrowserProxy: TestSafetyHubBrowserProxy;
   const notificationElementName =
       'settings-safety-check-notification-permissions';
   const unusedSiteElementName = 'settings-safety-check-unused-site-permissions';
@@ -245,9 +245,8 @@
     MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
     prefsBrowserProxy = new TestSiteSettingsPrefsBrowserProxy();
     SiteSettingsPrefsBrowserProxyImpl.setInstance(prefsBrowserProxy);
-    permissionsBrowserProxy = new TestSiteSettingsPermissionsBrowserProxy();
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(
-        permissionsBrowserProxy);
+    permissionsBrowserProxy = new TestSafetyHubBrowserProxy();
+    SafetyHubBrowserProxyImpl.setInstance(permissionsBrowserProxy);
     testRoutes = {
       PRIVACY: new Route('/privacy'),
       BASIC: new Route('/'),
diff --git a/chrome/test/data/webui/settings/site_settings_page_test.ts b/chrome/test/data/webui/settings/site_settings_page_test.ts
index f538620..3a3b1176 100644
--- a/chrome/test/data/webui/settings/site_settings_page_test.ts
+++ b/chrome/test/data/webui/settings/site_settings_page_test.ts
@@ -6,13 +6,13 @@
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {ContentSetting, CookieControlsMode, ContentSettingsTypes, defaultSettingLabel, NotificationSetting, SettingsSiteSettingsPageElement, SiteSettingsPermissionsBrowserProxyImpl, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {ContentSetting, CookieControlsMode, ContentSettingsTypes, defaultSettingLabel, NotificationSetting, SettingsSiteSettingsPageElement, SafetyHubBrowserProxyImpl, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
 import {CrLinkRowElement} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isChildVisible} from 'chrome://webui-test/test_util.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
-import {TestSiteSettingsPermissionsBrowserProxy} from './test_site_settings_permissions_browser_proxy.js';
+import {TestSafetyHubBrowserProxy} from './test_safety_hub_browser_proxy.js';
 import {TestSiteSettingsPrefsBrowserProxy} from './test_site_settings_prefs_browser_proxy.js';
 
 // clang-format on
@@ -262,14 +262,11 @@
 
 suite('UnusedSitePermissionsReview', function() {
   let page: SettingsSiteSettingsPageElement;
-  let siteSettingsPermissionsBrowserProxy:
-      TestSiteSettingsPermissionsBrowserProxy;
+  let siteSettingsPermissionsBrowserProxy: TestSafetyHubBrowserProxy;
 
   setup(function() {
-    siteSettingsPermissionsBrowserProxy =
-        new TestSiteSettingsPermissionsBrowserProxy();
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(
-        siteSettingsPermissionsBrowserProxy);
+    siteSettingsPermissionsBrowserProxy = new TestSafetyHubBrowserProxy();
+    SafetyHubBrowserProxyImpl.setInstance(siteSettingsPermissionsBrowserProxy);
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
   });
 
@@ -310,8 +307,7 @@
  */
 suite('UnusedSitePermissionsReviewDisabled', function() {
   let page: SettingsSiteSettingsPageElement;
-  let siteSettingsPermissionsBrowserProxy:
-      TestSiteSettingsPermissionsBrowserProxy;
+  let siteSettingsPermissionsBrowserProxy: TestSafetyHubBrowserProxy;
 
   suiteSetup(function() {
     loadTimeData.overrideValues({
@@ -320,10 +316,8 @@
   });
 
   setup(function() {
-    siteSettingsPermissionsBrowserProxy =
-        new TestSiteSettingsPermissionsBrowserProxy();
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(
-        siteSettingsPermissionsBrowserProxy);
+    siteSettingsPermissionsBrowserProxy = new TestSafetyHubBrowserProxy();
+    SafetyHubBrowserProxyImpl.setInstance(siteSettingsPermissionsBrowserProxy);
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
   });
 
diff --git a/chrome/test/data/webui/settings/test_site_settings_permissions_browser_proxy.ts b/chrome/test/data/webui/settings/test_safety_hub_browser_proxy.ts
similarity index 85%
rename from chrome/test/data/webui/settings/test_site_settings_permissions_browser_proxy.ts
rename to chrome/test/data/webui/settings/test_safety_hub_browser_proxy.ts
index 2f03969a..99b341cb 100644
--- a/chrome/test/data/webui/settings/test_site_settings_permissions_browser_proxy.ts
+++ b/chrome/test/data/webui/settings/test_safety_hub_browser_proxy.ts
@@ -4,17 +4,17 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {UnusedSitePermissions, SiteSettingsPermissionsBrowserProxy} from 'chrome://settings/lazy_load.js';
+import {UnusedSitePermissions, SafetyHubBrowserProxy} from 'chrome://settings/lazy_load.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 // clang-format on
 
 /**
- * A test version of SiteSettingsPermissionsBrowserProxy. Provides helper
+ * A test version of SafetyHubBrowserProxy. Provides helper
  * methods for allowing tests to know when a method was called, as well as
  * specifying mock responses.
  */
-export class TestSiteSettingsPermissionsBrowserProxy extends TestBrowserProxy
-    implements SiteSettingsPermissionsBrowserProxy {
+export class TestSafetyHubBrowserProxy extends TestBrowserProxy implements
+    SafetyHubBrowserProxy {
   private unusedSitePermissions_: UnusedSitePermissions[] = [];
 
   constructor() {
diff --git a/chrome/test/data/webui/settings/unused_site_permissions_interactive_ui_test.ts b/chrome/test/data/webui/settings/unused_site_permissions_interactive_ui_test.ts
index 0dcd20da..78efea5 100644
--- a/chrome/test/data/webui/settings/unused_site_permissions_interactive_ui_test.ts
+++ b/chrome/test/data/webui/settings/unused_site_permissions_interactive_ui_test.ts
@@ -5,9 +5,9 @@
 // clang-format off
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {SettingsUnusedSitePermissionsElement, ContentSettingsTypes, SiteSettingsPermissionsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {SettingsUnusedSitePermissionsElement, ContentSettingsTypes, SafetyHubBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
 
-import {TestSiteSettingsPermissionsBrowserProxy} from './test_site_settings_permissions_browser_proxy.js';
+import {TestSafetyHubBrowserProxy} from './test_safety_hub_browser_proxy.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
@@ -15,7 +15,7 @@
 
 suite('CrSettingsUnusedSitePermissionsInteractiveUITest', function() {
   // The mock proxy object to use during test.
-  let browserProxy: TestSiteSettingsPermissionsBrowserProxy;
+  let browserProxy: TestSafetyHubBrowserProxy;
 
   let testElement: SettingsUnusedSitePermissionsElement;
 
@@ -54,9 +54,9 @@
   }
 
   setup(async function() {
-    browserProxy = new TestSiteSettingsPermissionsBrowserProxy();
+    browserProxy = new TestSafetyHubBrowserProxy();
     browserProxy.setUnusedSitePermissions(mockData);
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(browserProxy);
+    SafetyHubBrowserProxyImpl.setInstance(browserProxy);
 
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     testElement = document.createElement('settings-unused-site-permissions');
diff --git a/chrome/test/data/webui/settings/unused_site_permissions_test.ts b/chrome/test/data/webui/settings/unused_site_permissions_test.ts
index d967e20..63eb2fe2 100644
--- a/chrome/test/data/webui/settings/unused_site_permissions_test.ts
+++ b/chrome/test/data/webui/settings/unused_site_permissions_test.ts
@@ -8,18 +8,18 @@
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {ContentSettingsTypes, SettingsUnusedSitePermissionsElement, SiteSettingsPermissionsBrowserProxyImpl, UnusedSitePermissions} from 'chrome://settings/lazy_load.js';
+import {ContentSettingsTypes, SettingsUnusedSitePermissionsElement, SafetyHubBrowserProxyImpl, UnusedSitePermissions} from 'chrome://settings/lazy_load.js';
 import {MetricsBrowserProxyImpl, Router, routes, SafetyCheckUnusedSitePermissionsModuleInteractions, SettingsRoutes} from 'chrome://settings/settings.js';
 import {isMac} from 'chrome://resources/js/platform.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 
 import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
-import {TestSiteSettingsPermissionsBrowserProxy} from './test_site_settings_permissions_browser_proxy.js';
+import {TestSafetyHubBrowserProxy} from './test_safety_hub_browser_proxy.js';
 
 // clang-format on
 
 suite('CrSettingsUnusedSitePermissionsTest', function() {
-  let browserProxy: TestSiteSettingsPermissionsBrowserProxy;
+  let browserProxy: TestSafetyHubBrowserProxy;
   let metricsBrowserProxy: TestMetricsBrowserProxy;
 
   let testElement: SettingsUnusedSitePermissionsElement;
@@ -112,9 +112,9 @@
   }
 
   setup(async function() {
-    browserProxy = new TestSiteSettingsPermissionsBrowserProxy();
+    browserProxy = new TestSafetyHubBrowserProxy();
     browserProxy.setUnusedSitePermissions(mockData);
-    SiteSettingsPermissionsBrowserProxyImpl.setInstance(browserProxy);
+    SafetyHubBrowserProxyImpl.setInstance(browserProxy);
     metricsBrowserProxy = new TestMetricsBrowserProxy();
     MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
     testRoutes = {
diff --git a/chrome/test/fuzzing/html_in_process_fuzzer.cc b/chrome/test/fuzzing/html_in_process_fuzzer.cc
index 459ec8387..9a997c9 100644
--- a/chrome/test/fuzzing/html_in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/html_in_process_fuzzer.cc
@@ -18,17 +18,11 @@
   HtmlInProcessFuzzer()
       : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
 
-  void SetUpOnMainThread() override {
-    InProcessFuzzer::SetUpOnMainThread();
-    host_resolver()->AddRule("*", "127.0.0.1");
-    https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
-    https_test_server_.RegisterRequestHandler(base::BindRepeating(
-        &HtmlInProcessFuzzer::HandleHTTPRequest, base::Unretained(this)));
-    ASSERT_TRUE(https_test_server_.Start());
-  }
+  void SetUpOnMainThread() override;
   int Fuzz(const uint8_t* data, size_t size) override;
-  std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
-      const net::test_server::HttpRequest& request) const;
+  static std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
+      std::string response_body,
+      const net::test_server::HttpRequest& request);
 
   net::EmbeddedTestServer https_test_server_;
   std::string current_fuzz_case_;
@@ -36,13 +30,23 @@
 
 REGISTER_IN_PROCESS_FUZZER(HtmlInProcessFuzzer)
 
+void HtmlInProcessFuzzer::SetUpOnMainThread() {
+  InProcessFuzzer::SetUpOnMainThread();
+  host_resolver()->AddRule("*", "127.0.0.1");
+  https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  https_test_server_.RegisterRequestHandler(base::BindRepeating(
+      &HtmlInProcessFuzzer::HandleHTTPRequest, current_fuzz_case_));
+  ASSERT_TRUE(https_test_server_.Start());
+}
+
 std::unique_ptr<net::test_server::HttpResponse>
 HtmlInProcessFuzzer::HandleHTTPRequest(
-    const net::test_server::HttpRequest& request) const {
+    std::string response_body,
+    const net::test_server::HttpRequest& request) {
   std::unique_ptr<net::test_server::BasicHttpResponse> response;
   response = std::make_unique<net::test_server::BasicHttpResponse>();
   response->set_content_type("text/html");
-  response->set_content(current_fuzz_case_);
+  response->set_content(response_body);
   response->set_code(net::HTTP_OK);
   return response;
 }
diff --git a/chrome/test/fuzzing/in_process_fuzzer.cc b/chrome/test/fuzzing/in_process_fuzzer.cc
index 0edd622..8dd9d14 100644
--- a/chrome/test/fuzzing/in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/in_process_fuzzer.cc
@@ -74,8 +74,7 @@
 
  private:
   std::unique_ptr<InProcessFuzzer> fuzzer_;
-  std::unique_ptr<content::ContentMainDelegate>
-      content_main_delegate_;  // TODO remove unique_ptr
+  std::unique_ptr<content::ContentMainDelegate> content_main_delegate_;
   std::vector<std::string> libfuzzer_arguments_;
 };
 
@@ -169,5 +168,5 @@
   FuzzTestLauncherDelegate* fuzzer_launcher_delegate =
       new FuzzTestLauncherDelegate(std::move(fuzzer),
                                    std::move(libfuzzer_arguments));
-  return content::LaunchTests(fuzzer_launcher_delegate, 1, argc, argv);
+  return LaunchChromeTests(1, fuzzer_launcher_delegate, argc, argv);
 }
diff --git a/chrome/test/fuzzing/in_process_fuzzer.h b/chrome/test/fuzzing/in_process_fuzzer.h
index e250834e..021dd35 100644
--- a/chrome/test/fuzzing/in_process_fuzzer.h
+++ b/chrome/test/fuzzing/in_process_fuzzer.h
@@ -21,6 +21,19 @@
 //
 // Register your subclass with REGISTER_IN_PROCESS_FUZZER. There can only
 // be one per executable.
+//
+// Different fuzz frameworks might run this in different ways.
+// For instance,
+// * libfuzzer runs this in a multi-process Chrome browser_test
+//   environment.
+// * centipede runs it in single-process browser_test mode (currently),
+//   with an external fuzz co-ordinator running multiple instances
+//   of Chrome.
+// * in the future, snapshot fuzzers might pause a VM and resume
+//   clones of it (to ensure a cleaner state for each iteration)
+// To the extent possible, you should write your fuzzer to be
+// implementation-independent and semantically express what
+// should happen during such fuzzing of the whole browser.
 class InProcessFuzzer : virtual public InProcessBrowserTest {
  public:
   // Called by the main function to create this class.
diff --git a/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc b/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
index 94b92148..cdde0590 100644
--- a/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/kombucha_in_process_fuzzer.cc
@@ -23,30 +23,34 @@
     InteractiveBrowserTestT::TearDownOnMainThread();
   }
 
-  void SetUpOnMainThread() override {
-    InteractiveBrowserTestT::SetUpOnMainThread();
-    host_resolver()->AddRule("*", "127.0.0.1");
-    embedded_test_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
-    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
-        &KombuchaInProcessFuzzer::HandleHTTPRequest, base::Unretained(this)));
-    ASSERT_TRUE(embedded_test_server()->Start());
-  }
+  void SetUpOnMainThread() override;
   int Fuzz(const uint8_t* data, size_t size) override;
-  std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
-      const net::test_server::HttpRequest& request) const;
+  static std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
+      std::string response_body,
+      const net::test_server::HttpRequest& request);
 
   std::string current_fuzz_case_;
 };
 
 REGISTER_IN_PROCESS_FUZZER(KombuchaInProcessFuzzer)
 
+void KombuchaInProcessFuzzer::SetUpOnMainThread() {
+  InteractiveBrowserTestT::SetUpOnMainThread();
+  host_resolver()->AddRule("*", "127.0.0.1");
+  embedded_test_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+      &KombuchaInProcessFuzzer::HandleHTTPRequest, current_fuzz_case_));
+  ASSERT_TRUE(embedded_test_server()->Start());
+}
+
 std::unique_ptr<net::test_server::HttpResponse>
 KombuchaInProcessFuzzer::HandleHTTPRequest(
-    const net::test_server::HttpRequest& request) const {
+    std::string response_body,
+    const net::test_server::HttpRequest& request) {
   std::unique_ptr<net::test_server::BasicHttpResponse> response;
   response = std::make_unique<net::test_server::BasicHttpResponse>();
   response->set_content_type("text/html");
-  response->set_content(current_fuzz_case_);
+  response->set_content(response_body);
   response->set_code(net::HTTP_OK);
   return response;
 }
diff --git a/chrome/updater/mac/setup/keystone.mm b/chrome/updater/mac/setup/keystone.mm
index acb4b6c1..65da096 100644
--- a/chrome/updater/mac/setup/keystone.mm
+++ b/chrome/updater/mac/setup/keystone.mm
@@ -269,8 +269,6 @@
           .Append(FILE_PATH_LITERAL("ksinstall"));
   base::CommandLine command_line(ksinstall_path);
   command_line.AppendSwitch("uninstall");
-  if (IsSystemInstall(scope))
-    command_line = MakeElevated(command_line);
   base::Process process = base::LaunchProcess(command_line, {});
   if (!process.IsValid()) {
     LOG(ERROR) << "Failed to launch ksinstall.";
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 032c64c..eb38839c 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -282,6 +282,9 @@
 void RunOfflineInstall(UpdaterScope scope,
                        bool is_legacy_install,
                        bool is_silent_install);
+
+base::CommandLine MakeElevated(base::CommandLine command_line);
+
 }  // namespace updater::test
 
 #endif  // CHROME_UPDATER_TEST_INTEGRATION_TESTS_IMPL_H_
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index 9e212d05..ab75205 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -233,4 +233,9 @@
   // TODO(crbug.com/1286574).
 }
 
+base::CommandLine MakeElevated(base::CommandLine command_line) {
+  command_line.PrependWrapper("/usr/bin/sudo");
+  return command_line;
+}
+
 }  // namespace updater::test
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index 2a19f66f..21920ea 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -356,4 +356,9 @@
   // TODO(crbug.com/1286574).
 }
 
+base::CommandLine MakeElevated(base::CommandLine command_line) {
+  command_line.PrependWrapper("/usr/bin/sudo");
+  return command_line;
+}
+
 }  // namespace updater::test
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index ec10730..d9be4b4 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -1913,4 +1913,8 @@
   EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
 }
 
+base::CommandLine MakeElevated(base::CommandLine command_line) {
+  return command_line;
+}
+
 }  // namespace updater::test
diff --git a/chrome/updater/util/util.cc b/chrome/updater/util/util.cc
index 8e08490..35d42b0 100644
--- a/chrome/updater/util/util.cc
+++ b/chrome/updater/util/util.cc
@@ -239,13 +239,6 @@
       *base::CommandLine::ForCurrentProcess(), app_id);
 }
 
-base::CommandLine MakeElevated(base::CommandLine command_line) {
-#if BUILDFLAG(IS_MAC)
-  command_line.PrependWrapper("/usr/bin/sudo");
-#endif
-  return command_line;
-}
-
 // The log file is created in DIR_LOCAL_APP_DATA or DIR_ROAMING_APP_DATA.
 absl::optional<base::FilePath> GetLogFilePath(UpdaterScope scope) {
   const absl::optional<base::FilePath> log_dir = GetInstallDirectory(scope);
diff --git a/chrome/updater/util/util.h b/chrome/updater/util/util.h
index 06ec8df..9b728fc 100644
--- a/chrome/updater/util/util.h
+++ b/chrome/updater/util/util.h
@@ -125,10 +125,6 @@
 // Initializes logging for an executable.
 void InitLogging(UpdaterScope updater_scope);
 
-// Wraps the 'command_line' to be executed in an elevated context.
-// On macOS this is done with 'sudo'.
-base::CommandLine MakeElevated(base::CommandLine command_line);
-
 // Functor used by associative containers of strings as a case-insensitive ASCII
 // compare. `StringT` could be either UTF-8 or UTF-16.
 struct CaseInsensitiveASCIICompare {
diff --git a/chromecast/cast_core/OWNERS b/chromecast/cast_core/OWNERS
index 3a59a73..91ca5da6 100644
--- a/chromecast/cast_core/OWNERS
+++ b/chromecast/cast_core/OWNERS
@@ -1,4 +1,3 @@
 brettk@google.com
 mfoltz@chromium.org
-rwkeane@google.com
 vigeni@google.com
diff --git a/chromeos/ash/components/dbus/BUILD.gn b/chromeos/ash/components/dbus/BUILD.gn
index 37a7ba9..16071a2 100644
--- a/chromeos/ash/components/dbus/BUILD.gn
+++ b/chromeos/ash/components/dbus/BUILD.gn
@@ -48,8 +48,6 @@
     "//base",
     "//base/test:test_support",
     "//chromeos/ash/components/dbus/audio",
-    "//chromeos/ash/components/dbus/authpolicy",
-    "//chromeos/ash/components/dbus/authpolicy:authpolicy_proto",
     "//chromeos/ash/components/dbus/biod:test_support",
     "//chromeos/ash/components/dbus/cec_service:unit_tests",
     "//chromeos/ash/components/dbus/cros_disks:unit_tests",
@@ -82,7 +80,6 @@
   }
   sources = [
     "audio/cras_audio_client_unittest.cc",
-    "authpolicy/fake_authpolicy_client_unittest.cc",
     "biod/biod_client_unittest.cc",
     "biod/fake_biod_client_unittest.cc",
     "dbus_thread_manager_unittest.cc",
diff --git a/chromeos/ash/components/dbus/authpolicy/BUILD.gn b/chromeos/ash/components/dbus/authpolicy/BUILD.gn
deleted file mode 100644
index 6f7d3a7b..0000000
--- a/chromeos/ash/components/dbus/authpolicy/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2019 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/ui.gni")
-import("//third_party/protobuf/proto_library.gni")
-
-assert(is_chromeos_ash, "Non Chrome OS builds cannot depend on //chromeos/ash")
-
-component("authpolicy") {
-  defines = [ "IS_AUTHPOLICY_IMPL" ]
-
-  public_deps = [ "//components/policy/proto" ]
-
-  deps = [
-    ":authpolicy_proto",
-    "//base",
-    "//chromeos/ash/components/install_attributes",
-    "//chromeos/dbus/common",
-    "//components/account_id",
-    "//dbus",
-
-    # For FakeAuthPolicyClient
-    "//chromeos/ash/components/dbus/cryptohome:cryptohome_proto",
-    "//chromeos/ash/components/dbus/session_manager",
-    "//components/policy:cloud_policy_proto_generated_compile",
-  ]
-
-  sources = [
-    "authpolicy_client.cc",
-    "authpolicy_client.h",
-    "fake_authpolicy_client.cc",
-    "fake_authpolicy_client.h",
-  ]
-}
-
-proto_library("authpolicy_proto") {
-  sources = [
-    "//third_party/cros_system_api/dbus/authpolicy/active_directory_info.proto",
-  ]
-
-  proto_out_dir = "chromeos/ash/components/dbus/authpolicy"
-}
diff --git a/chromeos/ash/components/dbus/authpolicy/DEPS b/chromeos/ash/components/dbus/authpolicy/DEPS
deleted file mode 100644
index 723e81e..0000000
--- a/chromeos/ash/components/dbus/authpolicy/DEPS
+++ /dev/null
@@ -1,5 +0,0 @@
-specific_include_rules = {
-  "fake_authpolicy_client.*\.cc": [
-    "+chromeos/ash/components/install_attributes",
-  ],
-}
diff --git a/chromeos/ash/components/dbus/authpolicy/DIR_METADATA b/chromeos/ash/components/dbus/authpolicy/DIR_METADATA
deleted file mode 100644
index 563e159..0000000
--- a/chromeos/ash/components/dbus/authpolicy/DIR_METADATA
+++ /dev/null
@@ -1,6 +0,0 @@
-buganizer_public {
-  component_id: 1252893  # ChromeOS Public Tracker > Enterprise & Edu > Identity > Active Directory
-}
-buganizer {
-  component_id: 1253670
-}
diff --git a/chromeos/ash/components/dbus/authpolicy/OWNERS b/chromeos/ash/components/dbus/authpolicy/OWNERS
deleted file mode 100644
index 1f6c549..0000000
--- a/chromeos/ash/components/dbus/authpolicy/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-rsorokin@google.com
diff --git a/chromeos/ash/components/dbus/authpolicy/authpolicy_client.cc b/chromeos/ash/components/dbus/authpolicy/authpolicy_client.cc
deleted file mode 100644
index f508e6f..0000000
--- a/chromeos/ash/components/dbus/authpolicy/authpolicy_client.cc
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/dbus/authpolicy/authpolicy_client.h"
-
-#include <utility>
-
-#include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/task/single_thread_task_runner.h"
-#include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
-#include "components/account_id/account_id.h"
-#include "dbus/bus.h"
-#include "dbus/message.h"
-#include "dbus/object_proxy.h"
-
-namespace ash {
-
-namespace {
-
-// Policy fetch may take up to 300 seconds.  To ensure that a second policy
-// fetch queuing after the first one can succeed (e.g. user policy following
-// device policy), the D-Bus timeout needs to be at least twice that value.
-// JoinADDomain() is an exception since it's always guaranteed to be the first
-// call.
-constexpr int kSlowDbusTimeoutMilliseconds = 630 * 1000;
-
-AuthPolicyClient* g_instance = nullptr;
-
-authpolicy::ErrorType GetErrorFromReader(dbus::MessageReader* reader) {
-  int32_t int_error;
-  if (!reader->PopInt32(&int_error)) {
-    DLOG(ERROR) << "AuthPolicyClient: Failed to get an error from the response";
-    return authpolicy::ERROR_DBUS_FAILURE;
-  }
-  if (int_error < 0 || int_error >= authpolicy::ERROR_COUNT)
-    return authpolicy::ERROR_UNKNOWN;
-  return static_cast<authpolicy::ErrorType>(int_error);
-}
-
-authpolicy::ErrorType GetErrorAndProto(
-    dbus::Response* response,
-    google::protobuf::MessageLite* protobuf) {
-  if (!response) {
-    DLOG(ERROR) << "Auth: Failed to  call to authpolicy";
-    return authpolicy::ERROR_DBUS_FAILURE;
-  }
-  dbus::MessageReader reader(response);
-  const authpolicy::ErrorType error(GetErrorFromReader(&reader));
-
-  if (error != authpolicy::ERROR_NONE)
-    return error;
-
-  if (!reader.PopArrayOfBytesAsProto(protobuf)) {
-    DLOG(ERROR) << "Failed to parse protobuf.";
-    return authpolicy::ERROR_DBUS_FAILURE;
-  }
-  return authpolicy::ERROR_NONE;
-}
-
-class AuthPolicyClientImpl : public AuthPolicyClient {
- public:
-  AuthPolicyClientImpl() {}
-
-  AuthPolicyClientImpl(const AuthPolicyClientImpl&) = delete;
-  AuthPolicyClientImpl& operator=(const AuthPolicyClientImpl&) = delete;
-
-  ~AuthPolicyClientImpl() override = default;
-
-  // AuthPolicyClient override.
-  void JoinAdDomain(const authpolicy::JoinDomainRequest& request,
-                    int password_fd,
-                    JoinCallback callback) override {
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kJoinADDomainMethod);
-    dbus::MessageWriter writer(&method_call);
-    if (!writer.AppendProtoAsArrayOfBytes(request)) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(callback), authpolicy::ERROR_DBUS_FAILURE,
-                         std::string()));
-      return;
-    }
-    writer.AppendFileDescriptor(password_fd);
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(&AuthPolicyClientImpl::HandleJoinCallback,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void AuthenticateUser(const authpolicy::AuthenticateUserRequest& request,
-                        int password_fd,
-                        AuthCallback callback) override {
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kAuthenticateUserMethod);
-    dbus::MessageWriter writer(&method_call);
-    if (!writer.AppendProtoAsArrayOfBytes(request)) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(callback), authpolicy::ERROR_DBUS_FAILURE,
-                         authpolicy::ActiveDirectoryAccountInfo()));
-      return;
-    }
-    writer.AppendFileDescriptor(password_fd);
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(&AuthPolicyClientImpl::HandleCallback<
-                           authpolicy::ActiveDirectoryAccountInfo>,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void GetUserStatus(const authpolicy::GetUserStatusRequest& request,
-                     GetUserStatusCallback callback) override {
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kGetUserStatusMethod);
-    dbus::MessageWriter writer(&method_call);
-    if (!writer.AppendProtoAsArrayOfBytes(request)) {
-      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE,
-          base::BindOnce(std::move(callback), authpolicy::ERROR_DBUS_FAILURE,
-                         authpolicy::ActiveDirectoryUserStatus()));
-      return;
-    }
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(&AuthPolicyClientImpl::HandleCallback<
-                           authpolicy::ActiveDirectoryUserStatus>,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void GetUserKerberosFiles(const std::string& object_guid,
-                            GetUserKerberosFilesCallback callback) override {
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kGetUserKerberosFilesMethod);
-    dbus::MessageWriter writer(&method_call);
-    writer.AppendString(object_guid);
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(
-            &AuthPolicyClientImpl::HandleCallback<authpolicy::KerberosFiles>,
-            weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void RefreshDevicePolicy(RefreshPolicyCallback callback) override {
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kRefreshDevicePolicyMethod);
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(&AuthPolicyClientImpl::HandleRefreshPolicyCallback,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void RefreshUserPolicy(const AccountId& account_id,
-                         RefreshPolicyCallback callback) override {
-    DCHECK(account_id.GetAccountType() == AccountType::ACTIVE_DIRECTORY);
-    dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
-                                 authpolicy::kRefreshUserPolicyMethod);
-    dbus::MessageWriter writer(&method_call);
-    writer.AppendString(account_id.GetObjGuid());
-    proxy_->CallMethod(
-        &method_call, kSlowDbusTimeoutMilliseconds,
-        base::BindOnce(&AuthPolicyClientImpl::HandleRefreshPolicyCallback,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
-  void ConnectToSignal(
-      const std::string& signal_name,
-      dbus::ObjectProxy::SignalCallback signal_callback,
-      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
-    proxy_->ConnectToSignal(authpolicy::kAuthPolicyInterface, signal_name,
-                            std::move(signal_callback),
-                            std::move(on_connected_callback));
-  }
-
-  void WaitForServiceToBeAvailable(
-      dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback)
-      override {
-    proxy_->WaitForServiceToBeAvailable(std::move(callback));
-  }
-
-  void Init(dbus::Bus* bus) {
-    bus_ = bus;
-    proxy_ = bus_->GetObjectProxy(
-        authpolicy::kAuthPolicyServiceName,
-        dbus::ObjectPath(authpolicy::kAuthPolicyServicePath));
-  }
-
- private:
-  void HandleRefreshPolicyCallback(RefreshPolicyCallback callback,
-                                   dbus::Response* response) {
-    if (!response) {
-      DLOG(ERROR) << "RefreshDevicePolicy: failed to call to authpolicy";
-      std::move(callback).Run(authpolicy::ERROR_DBUS_FAILURE);
-      return;
-    }
-    dbus::MessageReader reader(response);
-    std::move(callback).Run(GetErrorFromReader(&reader));
-  }
-
-  void HandleJoinCallback(JoinCallback callback, dbus::Response* response) {
-    if (!response) {
-      DLOG(ERROR) << "Join: Couldn't call to authpolicy";
-      std::move(callback).Run(authpolicy::ERROR_DBUS_FAILURE, std::string());
-      return;
-    }
-
-    dbus::MessageReader reader(response);
-    authpolicy::ErrorType error = GetErrorFromReader(&reader);
-    std::string machine_domain;
-    if (error == authpolicy::ERROR_NONE) {
-      if (!reader.PopString(&machine_domain))
-        error = authpolicy::ERROR_DBUS_FAILURE;
-    }
-    std::move(callback).Run(error, machine_domain);
-  }
-
-  template <class T>
-  void HandleCallback(base::OnceCallback<void(authpolicy::ErrorType error,
-                                              const T& response)> callback,
-                      dbus::Response* response) {
-    T proto;
-    authpolicy::ErrorType error(GetErrorAndProto(response, &proto));
-    std::move(callback).Run(error, proto);
-  }
-
-  raw_ptr<dbus::Bus, ExperimentalAsh> bus_ = nullptr;
-  raw_ptr<dbus::ObjectProxy, ExperimentalAsh> proxy_ = nullptr;
-
-  // Note: This should remain the last member so it'll be destroyed and
-  // invalidate its weak pointers before any other members are destroyed.
-  base::WeakPtrFactory<AuthPolicyClientImpl> weak_ptr_factory_{this};
-};
-
-}  // namespace
-
-AuthPolicyClient::AuthPolicyClient() {
-  DCHECK(!g_instance);
-  g_instance = this;
-}
-
-AuthPolicyClient::~AuthPolicyClient() {
-  DCHECK_EQ(this, g_instance);
-  g_instance = nullptr;
-}
-
-// static
-void AuthPolicyClient::Initialize(dbus::Bus* bus) {
-  DCHECK(bus);
-  (new AuthPolicyClientImpl)->Init(bus);
-}
-
-// static
-void AuthPolicyClient::InitializeFake() {
-  if (!FakeAuthPolicyClient::Get())
-    new FakeAuthPolicyClient();
-}
-
-// static
-void AuthPolicyClient::Shutdown() {
-  DCHECK(g_instance);
-  delete g_instance;
-}
-
-// static
-AuthPolicyClient* AuthPolicyClient::Get() {
-  return g_instance;
-}
-
-}  // namespace ash
diff --git a/chromeos/ash/components/dbus/authpolicy/authpolicy_client.h b/chromeos/ash/components/dbus/authpolicy/authpolicy_client.h
deleted file mode 100644
index 82ec15ab..0000000
--- a/chromeos/ash/components/dbus/authpolicy/authpolicy_client.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_AUTHPOLICY_CLIENT_H_
-#define CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_AUTHPOLICY_CLIENT_H_
-
-#include <string>
-
-#include "base/component_export.h"
-#include "base/functional/callback.h"
-#include "chromeos/ash/components/dbus/authpolicy/active_directory_info.pb.h"
-#include "dbus/object_proxy.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
-
-class AccountId;
-
-namespace ash {
-
-// AuthPolicyClient is used to communicate with the org.chromium.AuthPolicy
-// sevice. All method should be called from the origin thread (UI thread) which
-// initializes the DBusThreadManager instance.
-class COMPONENT_EXPORT(AUTHPOLICY) AuthPolicyClient {
- public:
-  using AuthCallback = base::OnceCallback<void(
-      authpolicy::ErrorType error,
-      const authpolicy::ActiveDirectoryAccountInfo& account_info)>;
-  using GetUserStatusCallback = base::OnceCallback<void(
-      authpolicy::ErrorType error,
-      const authpolicy::ActiveDirectoryUserStatus& user_status)>;
-  using GetUserKerberosFilesCallback =
-      base::OnceCallback<void(authpolicy::ErrorType error,
-                              const authpolicy::KerberosFiles& kerberos_files)>;
-  using JoinCallback =
-      base::OnceCallback<void(authpolicy::ErrorType error,
-                              const std::string& machine_domain)>;
-  using RefreshPolicyCallback =
-      base::OnceCallback<void(authpolicy::ErrorType error)>;
-
-  // Creates and initializes the global instance. |bus| must not be null.
-  static void Initialize(dbus::Bus* bus);
-
-  // Creates and initializes a fake global instance if not already created.
-  static void InitializeFake();
-
-  // Destroys the global instance which must have been initialized.
-  static void Shutdown();
-
-  // Returns the global instance if initialized. May return null.
-  static AuthPolicyClient* Get();
-
-  AuthPolicyClient(const AuthPolicyClient&) = delete;
-  AuthPolicyClient& operator=(const AuthPolicyClient&) = delete;
-
-  // Calls JoinADDomain to join a machine/device to an Active Directory domain.
-  // Password is read from the |password_fd|. |callback| is called after getting
-  // (or failing to get) D-BUS response.
-  virtual void JoinAdDomain(const authpolicy::JoinDomainRequest& request,
-                            int password_fd,
-                            JoinCallback callback) = 0;
-
-  // Calls AuthenticateUser to authenticate a user against Active Directory.
-  // Password is read from the |password_fd|. |callback| is called after getting
-  // (or failing to get) D-BUS response.
-  virtual void AuthenticateUser(
-      const authpolicy::AuthenticateUserRequest& request,
-      int password_fd,
-      AuthCallback callback) = 0;
-
-  // Calls GetUserStatus. If Active Directory server is online it fetches
-  // ActiveDirectoryUserStatus for the user specified by |request|.
-  // |callback| is called after getting (or failing to get) D-Bus response.
-  virtual void GetUserStatus(const authpolicy::GetUserStatusRequest& request,
-                             GetUserStatusCallback callback) = 0;
-
-  // Calls GetUserKerberosFiles. If authpolicyd has Kerberos files for the user
-  // specified by |object_guid| it sends them in response: credentials cache and
-  // krb5 config files.
-  virtual void GetUserKerberosFiles(const std::string& object_guid,
-                                    GetUserKerberosFilesCallback callback) = 0;
-
-  // Calls RefreshDevicePolicy - handle policy for the device.
-  // Fetch GPO files from Active directory server, parse it, encode it into
-  // protobuf and send to SessionManager. Callback is called after that.
-  virtual void RefreshDevicePolicy(RefreshPolicyCallback callback) = 0;
-
-  // Calls RefreshUserPolicy - handle policy for the user specified by
-  // |account_id|. Similar to RefreshDevicePolicy.
-  virtual void RefreshUserPolicy(const AccountId& account_id,
-                                 RefreshPolicyCallback callback) = 0;
-
-  // Connects callbacks to D-Bus signal |signal_name| sent by authpolicyd.
-  virtual void ConnectToSignal(
-      const std::string& signal_name,
-      dbus::ObjectProxy::SignalCallback signal_callback,
-      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
-
-  virtual void WaitForServiceToBeAvailable(
-      dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) = 0;
-
- protected:
-  // Initialize/Shutdown should be used instead.
-  AuthPolicyClient();
-  virtual ~AuthPolicyClient();
-};
-
-}  // namespace ash
-
-#endif  // CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_AUTHPOLICY_CLIENT_H_
diff --git a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.cc b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.cc
deleted file mode 100644
index fedd650c..0000000
--- a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.cc
+++ /dev/null
@@ -1,357 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
-
-#include <memory>
-#include <vector>
-
-#include "base/files/file_util.h"
-#include "base/functional/bind.h"
-#include "base/hash/md5.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/strings/string_split.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/threading/platform_thread.h"
-#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
-#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
-#include "chromeos/ash/components/install_attributes/install_attributes.h"
-#include "components/account_id/account_id.h"
-#include "components/policy/proto/cloud_policy.pb.h"
-#include "dbus/message.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
-
-namespace em = enterprise_management;
-
-namespace ash {
-
-namespace {
-
-constexpr size_t kMaxMachineNameLength = 15;
-constexpr char kInvalidMachineNameCharacters[] = "\\/:*?\"<>|";
-constexpr char kDefaultKerberosCreds[] = "credentials";
-constexpr char kDefaultKerberosConf[] = "configuration";
-
-FakeAuthPolicyClient* g_instance = nullptr;
-
-void OnStorePolicy(AuthPolicyClient::RefreshPolicyCallback callback,
-                   bool success) {
-  const authpolicy::ErrorType error =
-      success ? authpolicy::ERROR_NONE : authpolicy::ERROR_STORE_POLICY_FAILED;
-  std::move(callback).Run(error);
-}
-
-// Posts |closure| on the ThreadTaskRunner with |delay|.
-void PostDelayedClosure(base::OnceClosure closure,
-                        const base::TimeDelta& delay) {
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE, std::move(closure), delay);
-}
-
-// Runs |signal_callback| with Signal*. Needed to own Signal object.
-void RunSignalCallback(const std::string& interface_name,
-                       const std::string& method_name,
-                       dbus::ObjectProxy::SignalCallback signal_callback) {
-  signal_callback.Run(
-      std::make_unique<dbus::Signal>(interface_name, method_name).get());
-}
-
-// Reads the password from the file descriptor `password_fd`.
-// Not very efficient, but simple!
-std::string ReadPassword(int password_fd) {
-  std::string password;
-  char c;
-  while (base::ReadFromFD(password_fd, &c, 1))
-    password.push_back(c);
-  return password;
-}
-
-}  // namespace
-
-FakeAuthPolicyClient::FakeAuthPolicyClient() {
-  DCHECK(!g_instance);
-  g_instance = this;
-}
-
-FakeAuthPolicyClient::~FakeAuthPolicyClient() {
-  DCHECK_EQ(this, g_instance);
-  g_instance = nullptr;
-}
-
-// static
-FakeAuthPolicyClient* FakeAuthPolicyClient::Get() {
-  return g_instance;
-}
-
-void FakeAuthPolicyClient::JoinAdDomain(
-    const authpolicy::JoinDomainRequest& request,
-    int password_fd,
-    JoinCallback callback) {
-  DCHECK(!InstallAttributes::Get()->IsActiveDirectoryManaged());
-  authpolicy::ErrorType error = authpolicy::ERROR_NONE;
-  std::string machine_domain;
-  if (!started_) {
-    LOG(ERROR) << "authpolicyd not started";
-    error = authpolicy::ERROR_DBUS_FAILURE;
-  } else if (request.machine_name().size() > kMaxMachineNameLength) {
-    error = authpolicy::ERROR_MACHINE_NAME_TOO_LONG;
-  } else if (request.machine_name().empty() ||
-             request.machine_name().find_first_of(
-                 kInvalidMachineNameCharacters) != std::string::npos) {
-    error = authpolicy::ERROR_INVALID_MACHINE_NAME;
-  } else if (request.kerberos_encryption_types() ==
-             authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY) {
-    // Pretend that server does not support legacy types.
-    error = authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE;
-  } else {
-    std::vector<std::string> parts =
-        base::SplitString(request.user_principal_name(), "@",
-                          base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (parts.size() != 2 || parts[0].empty() || parts[1].empty()) {
-      error = authpolicy::ERROR_PARSE_UPN_FAILED;
-    } else {
-      machine_domain = parts[1];
-    }
-  }
-
-  if (error == authpolicy::ERROR_NONE)
-    machine_name_ = request.machine_name();
-  if (error != authpolicy::ERROR_NONE)
-    machine_domain.clear();
-  else if (request.has_machine_domain() && !request.machine_domain().empty())
-    machine_domain = request.machine_domain();
-  PostDelayedClosure(base::BindOnce(std::move(callback), error, machine_domain),
-                     dbus_operation_delay_);
-}
-
-void FakeAuthPolicyClient::AuthenticateUser(
-    const authpolicy::AuthenticateUserRequest& request,
-    int password_fd,
-    AuthCallback callback) {
-  DCHECK(InstallAttributes::Get()->IsActiveDirectoryManaged());
-
-  auth_password_ = ReadPassword(password_fd);
-
-  authpolicy::ErrorType error = authpolicy::ERROR_NONE;
-  authpolicy::ActiveDirectoryAccountInfo account_info;
-  if (auth_error_ != authpolicy::ERROR_NONE) {
-    error = auth_error_;
-  } else if (!started_) {
-    LOG(ERROR) << "authpolicyd not started";
-    error = authpolicy::ERROR_DBUS_FAILURE;
-  } else {
-    std::vector<std::string> parts =
-        base::SplitString(request.user_principal_name(), "@",
-                          base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (parts.size() != 2 || parts[0].empty() || parts[1].empty())
-      error = authpolicy::ERROR_PARSE_UPN_FAILED;
-  }
-
-  if (error == authpolicy::ERROR_NONE) {
-    if (request.account_id().empty()) {
-      account_info.set_account_id(
-          base::MD5String(request.user_principal_name()));
-    } else {
-      account_info.set_account_id(request.account_id());
-    }
-    SetUserKerberosFiles(kDefaultKerberosCreds, kDefaultKerberosConf);
-  }
-  PostDelayedClosure(base::BindOnce(std::move(callback), error, account_info),
-                     dbus_operation_delay_);
-}
-
-void FakeAuthPolicyClient::GetUserStatus(
-    const authpolicy::GetUserStatusRequest& request,
-    GetUserStatusCallback callback) {
-  authpolicy::ActiveDirectoryUserStatus user_status;
-  user_status.set_password_status(password_status_);
-  user_status.set_tgt_status(tgt_status_);
-
-  authpolicy::ActiveDirectoryAccountInfo* const account_info =
-      user_status.mutable_account_info();
-  account_info->set_account_id(request.account_id());
-  if (!display_name_.empty())
-    account_info->set_display_name(display_name_);
-  if (!given_name_.empty())
-    account_info->set_given_name(given_name_);
-
-  PostDelayedClosure(
-      base::BindOnce(std::move(callback), authpolicy::ERROR_NONE, user_status),
-      dbus_operation_delay_);
-  if (!on_get_status_closure_.is_null())
-    PostDelayedClosure(std::move(on_get_status_closure_),
-                       dbus_operation_delay_);
-}
-
-void FakeAuthPolicyClient::GetUserKerberosFiles(
-    const std::string& object_guid,
-    GetUserKerberosFilesCallback callback) {
-  authpolicy::KerberosFiles files;
-  files.set_krb5cc(user_kerberos_creds());
-  files.set_krb5conf(user_kerberos_conf());
-  PostDelayedClosure(
-      base::BindOnce(std::move(callback), authpolicy::ERROR_NONE, files),
-      dbus_operation_delay_);
-}
-
-void FakeAuthPolicyClient::RefreshDevicePolicy(RefreshPolicyCallback callback) {
-  if (!started_) {
-    LOG(ERROR) << "authpolicyd not started";
-    std::move(callback).Run(authpolicy::ERROR_DBUS_FAILURE);
-    return;
-  }
-
-  if (!InstallAttributes::Get()->IsActiveDirectoryManaged()) {
-    // Pretend that policy was fetched and cached inside authpolicyd.
-    std::move(callback).Run(
-        authpolicy::ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT);
-    return;
-  }
-
-  // On first refresh, we need to restore |machine_name| and |dm_token| from
-  // the stored policy.
-  if (machine_name_.empty() || dm_token_.empty()) {
-    SessionManagerClient::Get()->RetrieveDevicePolicy(
-        base::BindOnce(&FakeAuthPolicyClient::OnDevicePolicyRetrieved,
-                       weak_factory_.GetWeakPtr(), std::move(callback)));
-    return;
-  }
-
-  StoreDevicePolicy(std::move(callback));
-}
-
-void FakeAuthPolicyClient::RefreshUserPolicy(const AccountId& account_id,
-                                             RefreshPolicyCallback callback) {
-  if (refresh_user_policy_error_.has_value()) {
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback),
-                                  refresh_user_policy_error_.value()));
-    refresh_user_policy_error_.reset();
-    return;
-  }
-  DCHECK(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  if (!started_) {
-    LOG(ERROR) << "authpolicyd not started";
-    std::move(callback).Run(authpolicy::ERROR_DBUS_FAILURE);
-    return;
-  }
-
-  em::CloudPolicySettings policy;
-  std::string payload;
-  CHECK(policy.SerializeToString(&payload));
-
-  em::PolicyData policy_data;
-  policy_data.set_policy_type("google/chromeos/user");
-  policy_data.set_username(account_id.GetUserEmail());
-  policy_data.set_device_id(account_id.GetObjGuid());
-  policy_data.set_timestamp(base::Time::Now().ToJavaTime());
-  policy_data.set_policy_value(payload);
-  for (const auto& id : user_affiliation_ids_)
-    policy_data.add_user_affiliation_ids(id);
-
-  em::PolicyFetchResponse response;
-  response.set_policy_data(policy_data.SerializeAsString());
-
-  cryptohome::AccountIdentifier account_identifier;
-  account_identifier.set_account_id(account_id.GetAccountIdKey());
-  SessionManagerClient::Get()->StorePolicyForUser(
-      account_identifier, response.SerializeAsString(),
-      base::BindOnce(&OnStorePolicy, std::move(callback)));
-}
-
-void FakeAuthPolicyClient::ConnectToSignal(
-    const std::string& signal_name,
-    dbus::ObjectProxy::SignalCallback signal_callback,
-    dbus::ObjectProxy::OnConnectedCallback on_connected_callback) {
-  DCHECK_EQ(authpolicy::kUserKerberosFilesChangedSignal, signal_name);
-  DCHECK(!user_kerberos_files_changed_callback_);
-  user_kerberos_files_changed_callback_ = signal_callback;
-  std::move(on_connected_callback)
-      .Run(authpolicy::kAuthPolicyInterface, signal_name, true /* success */);
-}
-
-void FakeAuthPolicyClient::WaitForServiceToBeAvailable(
-    dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) {
-  if (started_) {
-    // Explicitly violate async pattern so testing code would not have to wait
-    // the callback.
-    std::move(callback).Run(true /* service_is_available */);
-    return;
-  }
-  wait_for_service_to_be_available_callbacks_.push_back(std::move(callback));
-}
-
-void FakeAuthPolicyClient::SetUserKerberosFiles(const std::string& creds,
-                                                const std::string& conf) {
-  const bool run_signal =
-      user_kerberos_files_changed_callback_ &&
-      (creds != user_kerberos_creds_ || conf != user_kerberos_conf_);
-  user_kerberos_creds_ = creds;
-  user_kerberos_conf_ = conf;
-  if (run_signal) {
-    PostDelayedClosure(
-        base::BindOnce(RunSignalCallback, authpolicy::kAuthPolicyInterface,
-                       authpolicy::kUserKerberosFilesChangedSignal,
-                       user_kerberos_files_changed_callback_),
-        dbus_operation_delay_);
-  }
-}
-
-void FakeAuthPolicyClient::SetStarted(bool started) {
-  started_ = started;
-  if (started_) {
-    std::vector<chromeos::WaitForServiceToBeAvailableCallback> callbacks;
-    callbacks.swap(wait_for_service_to_be_available_callbacks_);
-    for (size_t i = 0; i < callbacks.size(); ++i)
-      std::move(callbacks[i]).Run(true /* service_is_available*/);
-  }
-}
-
-void FakeAuthPolicyClient::OnDevicePolicyRetrieved(
-    RefreshPolicyCallback callback,
-    SessionManagerClient::RetrievePolicyResponseType response_type,
-    const std::string& protobuf) {
-  if (response_type !=
-      SessionManagerClient::RetrievePolicyResponseType::SUCCESS) {
-    std::move(callback).Run(authpolicy::ERROR_DBUS_FAILURE);
-    return;
-  }
-
-  em::PolicyFetchResponse response;
-  response.ParseFromString(protobuf);
-
-  em::PolicyData policy_data;
-  policy_data.ParseFromString(response.policy_data());
-
-  if (policy_data.has_device_id())
-    machine_name_ = policy_data.device_id();
-  if (policy_data.has_request_token())
-    dm_token_ = policy_data.request_token();
-
-  StoreDevicePolicy(std::move(callback));
-}
-
-void FakeAuthPolicyClient::StoreDevicePolicy(RefreshPolicyCallback callback) {
-  std::string payload;
-  CHECK(device_policy_.SerializeToString(&payload));
-
-  em::PolicyData policy_data;
-  policy_data.set_policy_type("google/chromeos/device");
-  policy_data.set_device_id(machine_name_);
-  policy_data.set_request_token(dm_token_);
-  policy_data.set_policy_value(payload);
-  policy_data.set_timestamp(base::Time::Now().ToJavaTime());
-  for (const auto& id : device_affiliation_ids_)
-    policy_data.add_device_affiliation_ids(id);
-
-  em::PolicyFetchResponse response;
-  response.set_policy_data(policy_data.SerializeAsString());
-
-  SessionManagerClient::Get()->StoreDevicePolicy(
-      response.SerializeAsString(),
-      base::BindOnce(&OnStorePolicy, std::move(callback)));
-}
-
-}  // namespace ash
diff --git a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h
deleted file mode 100644
index edb8efe..0000000
--- a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_FAKE_AUTHPOLICY_CLIENT_H_
-#define CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_FAKE_AUTHPOLICY_CLIENT_H_
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/component_export.h"
-#include "base/containers/span.h"
-#include "base/memory/weak_ptr.h"
-#include "base/strings/string_piece.h"
-#include "base/time/time.h"
-#include "chromeos/ash/components/dbus/authpolicy/authpolicy_client.h"
-#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
-#include "components/policy/proto/chrome_device_policy.pb.h"
-#include "components/policy/proto/device_management_backend.pb.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-class AccountId;
-
-namespace ash {
-
-class COMPONENT_EXPORT(AUTHPOLICY) FakeAuthPolicyClient
-    : public AuthPolicyClient {
- public:
-  FakeAuthPolicyClient();
-
-  FakeAuthPolicyClient(const FakeAuthPolicyClient&) = delete;
-  FakeAuthPolicyClient& operator=(const FakeAuthPolicyClient&) = delete;
-
-  ~FakeAuthPolicyClient() override;
-
-  // Returns the fake global instance if initialized. May return null.
-  static FakeAuthPolicyClient* Get();
-
-  // AuthPolicyClient overrides.
-
-  // Performs basic checks on |request.machine_name| and
-  // |request.user_principal_name|. Could fail with ERROR_MACHINE_NAME_TOO_LONG,
-  // ERROR_INVALID_MACHINE_NAME or ERROR_PARSE_UPN_FAILED. Otherwise succeeds.
-  void JoinAdDomain(const authpolicy::JoinDomainRequest& request,
-                    int password_fd,
-                    JoinCallback callback) override;
-
-  // Runs `callback` with `auth_error_`. Stores given password in
-  // `auth_password_`.
-  void AuthenticateUser(const authpolicy::AuthenticateUserRequest& request,
-                        int password_fd,
-                        AuthCallback callback) override;
-
-  // Runs |callback| with |password_status_| and |tgt_status_|. Also calls
-  // |on_get_status_closure_| after that.
-  void GetUserStatus(const authpolicy::GetUserStatusRequest& request,
-                     GetUserStatusCallback callback) override;
-
-  // Runs |callback| with Kerberos files.
-  void GetUserKerberosFiles(const std::string& object_guid,
-                            GetUserKerberosFilesCallback callback) override;
-
-  // Writes device policy file and runs callback.
-  void RefreshDevicePolicy(RefreshPolicyCallback callback) override;
-
-  // Writes user policy file and runs callback.
-  void RefreshUserPolicy(const AccountId& account_id,
-                         RefreshPolicyCallback callback) override;
-
-  // Runs |on_connected_callback| with success. Then runs |signal_callback|
-  // once.
-  void ConnectToSignal(
-      const std::string& signal_name,
-      dbus::ObjectProxy::SignalCallback signal_callback,
-      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override;
-
-  void WaitForServiceToBeAvailable(
-      dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) override;
-
-  // Runs |user_kerberos_files_changed_callback_| if callback is set and files
-  // changed.
-  void SetUserKerberosFiles(const std::string& kerberos_creds,
-                            const std::string& kerberos_conf);
-  const std::string& user_kerberos_conf() { return user_kerberos_conf_; }
-  const std::string& user_kerberos_creds() { return user_kerberos_creds_; }
-
-  // Mark service as started. It's getting started by the
-  // UpstartClient::StartAuthPolicyService on the Active Directory managed
-  // devices. If |started| is true, it triggers calling
-  // |wait_for_service_to_be_available_callbacks_|.
-  void SetStarted(bool started);
-
-  bool started() const { return started_; }
-
-  void set_auth_error(authpolicy::ErrorType auth_error) {
-    auth_error_ = auth_error;
-  }
-
-  void set_display_name(const std::string& display_name) {
-    display_name_ = display_name;
-  }
-
-  void set_given_name(const std::string& given_name) {
-    given_name_ = given_name;
-  }
-
-  void set_password_status(
-      authpolicy::ActiveDirectoryUserStatus::PasswordStatus password_status) {
-    password_status_ = password_status;
-  }
-
-  void set_tgt_status(
-      authpolicy::ActiveDirectoryUserStatus::TgtStatus tgt_status) {
-    tgt_status_ = tgt_status;
-  }
-
-  void set_on_get_status_closure(base::OnceClosure on_get_status_closure) {
-    on_get_status_closure_ = std::move(on_get_status_closure);
-  }
-
-  void set_device_policy(
-      const enterprise_management::ChromeDeviceSettingsProto& device_policy) {
-    device_policy_ = device_policy;
-  }
-
-  void set_user_affiliation_ids(
-      const base::span<const base::StringPiece>& ids) {
-    user_affiliation_ids_.clear();
-    for (const auto& id : ids) {
-      user_affiliation_ids_.emplace_back(id);
-    }
-  }
-
-  void set_device_affiliation_ids(
-      const base::span<const base::StringPiece>& ids) {
-    device_affiliation_ids_.clear();
-    for (const auto& id : ids) {
-      device_affiliation_ids_.emplace_back(id);
-    }
-  }
-
-  void set_refresh_user_policy_error(authpolicy::ErrorType error) {
-    refresh_user_policy_error_ = error;
-  }
-
-  std::string auth_password() const { return auth_password_; }
-
-  void DisableOperationDelayForTesting() {
-    dbus_operation_delay_ = disk_operation_delay_ = base::Seconds(0);
-  }
-
- protected:
-  authpolicy::ErrorType auth_error_ = authpolicy::ERROR_NONE;
-
- private:
-  void OnDevicePolicyRetrieved(
-      RefreshPolicyCallback callback,
-      SessionManagerClient::RetrievePolicyResponseType response_type,
-      const std::string& protobuf);
-
-  void StoreDevicePolicy(RefreshPolicyCallback callback);
-
-  bool started_ = false;
-  // If valid called after GetUserStatusCallback is called.
-  base::OnceClosure on_get_status_closure_;
-  std::string display_name_;
-  std::string given_name_;
-  std::string machine_name_;
-  std::string dm_token_;
-  std::string user_kerberos_creds_;
-  std::string user_kerberos_conf_;
-
-  // Stores the password received in the last `AuthenticateUser()` call.
-  std::string auth_password_;
-
-  std::vector<std::string> user_affiliation_ids_;
-  std::vector<std::string> device_affiliation_ids_;
-
-  dbus::ObjectProxy::SignalCallback user_kerberos_files_changed_callback_;
-
-  authpolicy::ActiveDirectoryUserStatus::PasswordStatus password_status_ =
-      authpolicy::ActiveDirectoryUserStatus::PASSWORD_VALID;
-  authpolicy::ActiveDirectoryUserStatus::TgtStatus tgt_status_ =
-      authpolicy::ActiveDirectoryUserStatus::TGT_VALID;
-
-  // Delay operations to be more realistic.
-  base::TimeDelta dbus_operation_delay_ = base::Seconds(3);
-  base::TimeDelta disk_operation_delay_ = base::Milliseconds(100);
-
-  enterprise_management::ChromeDeviceSettingsProto device_policy_;
-
-  std::vector<chromeos::WaitForServiceToBeAvailableCallback>
-      wait_for_service_to_be_available_callbacks_;
-
-  absl::optional<authpolicy::ErrorType> refresh_user_policy_error_;
-
-  base::WeakPtrFactory<FakeAuthPolicyClient> weak_factory_{this};
-};
-
-}  // namespace ash
-
-#endif  // CHROMEOS_ASH_COMPONENTS_DBUS_AUTHPOLICY_FAKE_AUTHPOLICY_CLIENT_H_
diff --git a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
deleted file mode 100644
index 707b97f..0000000
--- a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
-
-#include "base/functional/bind.h"
-#include "base/test/repeating_test_future.h"
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
-#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
-#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
-#include "components/account_id/account_id.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace em = enterprise_management;
-
-namespace ash {
-namespace {
-
-constexpr char kCorrectMachineName[] = "machine_name";
-constexpr char kCorrectUserName[] = "user@domain.com";
-constexpr char kCorrectUserDomain[] = "domain.com";
-constexpr char kAccountId[] = "user-account-id";
-constexpr char kMachineDomain[] = "machine.domain";
-
-}  // namespace
-
-class FakeAuthPolicyClientTest : public ::testing::Test {
- public:
-  FakeAuthPolicyClientTest() = default;
-
-  FakeAuthPolicyClientTest(const FakeAuthPolicyClientTest&) = delete;
-  FakeAuthPolicyClientTest& operator=(const FakeAuthPolicyClientTest&) = delete;
-
- protected:
-  FakeAuthPolicyClient* authpolicy_client() {
-    return FakeAuthPolicyClient::Get();
-  }
-
-  void SetUp() override {
-    ::testing::Test::SetUp();
-    SessionManagerClient::InitializeFakeInMemory();
-    AuthPolicyClient::InitializeFake();
-    authpolicy_client()->DisableOperationDelayForTesting();
-  }
-
-  void TearDown() override {
-    AuthPolicyClient::Shutdown();
-    SessionManagerClient::Shutdown();
-  }
-
-  void JoinAdDomain(const std::string& machine_name,
-                    const std::string& username,
-                    AuthPolicyClient::JoinCallback callback) {
-    authpolicy::JoinDomainRequest request;
-    request.set_machine_name(machine_name);
-    request.set_user_principal_name(username);
-    authpolicy_client()->JoinAdDomain(request, /* password_fd */ -1,
-                                      std::move(callback));
-  }
-
-  void JoinAdDomainWithMachineDomain(const std::string& machine_name,
-                                     const std::string& machine_domain,
-                                     const std::string& username,
-                                     AuthPolicyClient::JoinCallback callback) {
-    authpolicy::JoinDomainRequest request;
-    request.set_machine_name(machine_name);
-    request.set_user_principal_name(username);
-    request.set_machine_domain(machine_domain);
-    authpolicy_client()->JoinAdDomain(request, /* password_fd */ -1,
-                                      std::move(callback));
-  }
-
-  void AuthenticateUser(const std::string& username,
-                        const std::string& account_id,
-                        AuthPolicyClient::AuthCallback callback) {
-    authpolicy::AuthenticateUserRequest request;
-    request.set_user_principal_name(username);
-    request.set_account_id(account_id);
-    authpolicy_client()->AuthenticateUser(request, /* password_fd */ -1,
-                                          std::move(callback));
-  }
-
-  void WaitForServiceToBeAvailable() {
-    authpolicy_client()->WaitForServiceToBeAvailable(base::BindOnce(
-        &FakeAuthPolicyClientTest::OnWaitForServiceToBeAvailableCalled,
-        base::Unretained(this)));
-  }
-
-  void OnWaitForServiceToBeAvailableCalled(bool is_service_available) {
-    service_available_future_.AddValue(is_service_available);
-  }
-
-  void LockDevice() {
-    install_attributes_.Get()->SetActiveDirectoryManaged("example.com",
-                                                         "device_id");
-  }
-
-  base::test::RepeatingTestFuture<bool> service_available_future_;
-
- private:
-  ScopedStubInstallAttributes install_attributes_;
-  base::test::SingleThreadTaskEnvironment task_environment_;
-};
-
-// Tests parsing machine name.
-TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_ParseMachineName) {
-  authpolicy_client()->SetStarted(true);
-  JoinAdDomain("correct_length1", kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_NONE, error);
-                     EXPECT_EQ(kCorrectUserDomain, domain);
-                   }));
-  JoinAdDomain("", kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  JoinAdDomain("too_long_machine_name", kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_MACHINE_NAME_TOO_LONG, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  JoinAdDomain("invalid:name", kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-
-  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
-  JoinAdDomain(">nvalidname", kCorrectUserName, future.GetCallback());
-  EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, future.Get<0>());
-  EXPECT_TRUE(future.Get<1>().empty());
-}
-
-// Tests join to a different machine domain.
-TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_MachineDomain) {
-  authpolicy_client()->SetStarted(true);
-  JoinAdDomainWithMachineDomain(kCorrectMachineName, kMachineDomain,
-                                kCorrectUserName,
-                                base::BindOnce([](authpolicy::ErrorType error,
-                                                  const std::string& domain) {
-                                  EXPECT_EQ(authpolicy::ERROR_NONE, error);
-                                  EXPECT_EQ(kMachineDomain, domain);
-                                }));
-
-  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
-  JoinAdDomainWithMachineDomain(kCorrectMachineName, "", kCorrectUserName,
-                                future.GetCallback());
-
-  EXPECT_EQ(authpolicy::ERROR_NONE, future.Get<0>());
-  EXPECT_EQ(kCorrectUserDomain, future.Get<1>());
-}
-
-// Tests parsing user name.
-TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_ParseUPN) {
-  authpolicy_client()->SetStarted(true);
-  JoinAdDomain(kCorrectMachineName, kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_NONE, error);
-                     EXPECT_EQ(kCorrectUserDomain, domain);
-                   }));
-  JoinAdDomain(kCorrectMachineName, "user",
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  JoinAdDomain(kCorrectMachineName, "",
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  JoinAdDomain(kCorrectMachineName, "user@",
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  JoinAdDomain(kCorrectMachineName, "@realm",
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-
-  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
-  JoinAdDomain(kCorrectMachineName, "user@realm@com", future.GetCallback());
-  EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, future.Get<0>());
-  EXPECT_TRUE(future.Get<1>().empty());
-}
-
-// Tests that fake server does not support legacy encryption types.
-TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_NotSupportedEncType) {
-  authpolicy_client()->SetStarted(true);
-  authpolicy::JoinDomainRequest request;
-  request.set_machine_name(kCorrectMachineName);
-  request.set_user_principal_name(kCorrectUserName);
-  request.set_kerberos_encryption_types(
-      authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY);
-
-  base::test::TestFuture<authpolicy::ErrorType, const std::string&> future;
-  authpolicy_client()->JoinAdDomain(request, /* password_fd */ -1,
-                                    future.GetCallback());
-  EXPECT_EQ(authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE,
-            future.Get<0>());
-  EXPECT_TRUE(future.Get<1>().empty());
-}
-
-// Test AuthenticateUser.
-TEST_F(FakeAuthPolicyClientTest, AuthenticateUser_ByAccountId) {
-  authpolicy_client()->SetStarted(true);
-  LockDevice();
-  // Check that account_id do not change.
-  AuthenticateUser(
-      kCorrectUserName, kAccountId,
-      base::BindOnce(
-          [](authpolicy::ErrorType error,
-             const authpolicy::ActiveDirectoryAccountInfo& account_info) {
-            EXPECT_EQ(authpolicy::ERROR_NONE, error);
-            EXPECT_EQ(kAccountId, account_info.account_id());
-          }));
-}
-
-// Tests calls to not started authpolicyd fails.
-TEST_F(FakeAuthPolicyClientTest, NotStartedAuthPolicyService) {
-  JoinAdDomain(kCorrectMachineName, kCorrectUserName,
-               base::BindOnce(
-                   [](authpolicy::ErrorType error, const std::string& domain) {
-                     EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error);
-                     EXPECT_TRUE(domain.empty());
-                   }));
-  LockDevice();
-  AuthenticateUser(
-      kCorrectUserName, std::string() /* account_id */,
-      base::BindOnce([](authpolicy::ErrorType error,
-                        const authpolicy::ActiveDirectoryAccountInfo&) {
-        EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error);
-      }));
-  authpolicy_client()->RefreshDevicePolicy(
-      base::BindOnce([](authpolicy::ErrorType error) {
-        EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error);
-      }));
-  base::test::TestFuture<authpolicy::ErrorType> future;
-  authpolicy_client()->RefreshUserPolicy(
-      AccountId::FromUserEmail(kCorrectUserName), future.GetCallback());
-  EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, future.Get());
-}
-
-// Tests RefreshDevicePolicy. On a not locked device it should cache policy. On
-// a locked device it should send policy to session_manager.
-TEST_F(FakeAuthPolicyClientTest, NotLockedDeviceCachesPolicy) {
-  authpolicy_client()->SetStarted(true);
-  authpolicy_client()->RefreshDevicePolicy(
-      base::BindOnce([](authpolicy::ErrorType error) {
-        EXPECT_EQ(authpolicy::ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT, error);
-      }));
-  LockDevice();
-  base::test::TestFuture<authpolicy::ErrorType> future;
-  authpolicy_client()->RefreshDevicePolicy(future.GetCallback());
-  EXPECT_EQ(authpolicy::ERROR_NONE, future.Get());
-}
-
-// Tests that RefreshDevicePolicy stores device policy in the session manager.
-TEST_F(FakeAuthPolicyClientTest, RefreshDevicePolicyStoresPolicy) {
-  authpolicy_client()->SetStarted(true);
-  LockDevice();
-
-  {
-    // Call RefreshDevicePolicy.
-    base::test::TestFuture<authpolicy::ErrorType> future;
-    em::ChromeDeviceSettingsProto policy;
-    policy.mutable_allow_new_users()->set_allow_new_users(true);
-    authpolicy_client()->set_device_policy(policy);
-    authpolicy_client()->RefreshDevicePolicy(future.GetCallback());
-    EXPECT_EQ(authpolicy::ERROR_NONE, future.Get());
-  }
-
-  {
-    // Retrieve device policy from the session manager.
-    std::string response_blob;
-    EXPECT_EQ(SessionManagerClient::RetrievePolicyResponseType::SUCCESS,
-              SessionManagerClient::Get()->BlockingRetrieveDevicePolicy(
-                  &response_blob));
-    em::PolicyFetchResponse response;
-    EXPECT_TRUE(response.ParseFromString(response_blob));
-    EXPECT_TRUE(response.has_policy_data());
-
-    em::PolicyData policy_data;
-    EXPECT_TRUE(policy_data.ParseFromString(response.policy_data()));
-
-    em::ChromeDeviceSettingsProto policy;
-    EXPECT_TRUE(policy.ParseFromString(policy_data.policy_value()));
-    EXPECT_TRUE(policy.has_allow_new_users());
-    EXPECT_TRUE(policy.allow_new_users().allow_new_users());
-  }
-}
-
-TEST_F(FakeAuthPolicyClientTest, WaitForServiceToBeAvailableCalled) {
-  // Start waiting for service before starting the client.
-  WaitForServiceToBeAvailable();
-  WaitForServiceToBeAvailable();
-  authpolicy_client()->SetStarted(true);
-  WaitForServiceToBeAvailable();
-
-  // Wait for the future to catch all three callbacks.
-  EXPECT_TRUE(service_available_future_.Take());
-  EXPECT_TRUE(service_available_future_.Take());
-  EXPECT_TRUE(service_available_future_.Take());
-}
-
-}  // namespace ash
diff --git a/chromeos/ash/components/dbus/upstart/BUILD.gn b/chromeos/ash/components/dbus/upstart/BUILD.gn
index 442d27cb..5e5f48b 100644
--- a/chromeos/ash/components/dbus/upstart/BUILD.gn
+++ b/chromeos/ash/components/dbus/upstart/BUILD.gn
@@ -11,8 +11,6 @@
 
   deps = [
     "//base",
-    "//chromeos/ash/components/dbus/authpolicy",
-    "//chromeos/ash/components/dbus/authpolicy:authpolicy_proto",
     "//chromeos/ash/components/dbus/kerberos",
     "//chromeos/ash/components/dbus/media_analytics",
     "//chromeos/dbus/common",
diff --git a/chromeos/ash/components/dbus/upstart/fake_upstart_client.cc b/chromeos/ash/components/dbus/upstart/fake_upstart_client.cc
index 8ffba44e..9c779b26 100644
--- a/chromeos/ash/components/dbus/upstart/fake_upstart_client.cc
+++ b/chromeos/ash/components/dbus/upstart/fake_upstart_client.cc
@@ -7,7 +7,6 @@
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/task/single_thread_task_runner.h"
-#include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h"
 #include "chromeos/ash/components/dbus/kerberos/fake_kerberos_client.h"
 #include "chromeos/ash/components/dbus/kerberos/kerberos_client.h"
 #include "chromeos/ash/components/dbus/media_analytics/fake_media_analytics_client.h"
@@ -62,16 +61,6 @@
       FROM_HERE, base::BindOnce(std::move(callback), result));
 }
 
-void FakeUpstartClient::StartAuthPolicyService() {
-  FakeAuthPolicyClient::Get()->SetStarted(true);
-}
-
-void FakeUpstartClient::RestartAuthPolicyService() {
-  DLOG_IF(WARNING, !FakeAuthPolicyClient::Get()->started())
-      << "Trying to restart authpolicyd which is not started";
-  FakeAuthPolicyClient::Get()->SetStarted(true);
-}
-
 void FakeUpstartClient::StartMediaAnalytics(
     const std::vector<std::string>& /* upstart_env */,
     chromeos::VoidDBusMethodCallback callback) {
diff --git a/chromeos/ash/components/dbus/upstart/fake_upstart_client.h b/chromeos/ash/components/dbus/upstart/fake_upstart_client.h
index e4b99224..4cb298a 100644
--- a/chromeos/ash/components/dbus/upstart/fake_upstart_client.h
+++ b/chromeos/ash/components/dbus/upstart/fake_upstart_client.h
@@ -39,8 +39,6 @@
   void StopJob(const std::string& job,
                const std::vector<std::string>& upstart_env,
                chromeos::VoidDBusMethodCallback callback) override;
-  void StartAuthPolicyService() override;
-  void RestartAuthPolicyService() override;
   void StartMediaAnalytics(const std::vector<std::string>& upstart_env,
                            chromeos::VoidDBusMethodCallback callback) override;
   void RestartMediaAnalytics(
diff --git a/chromeos/ash/components/dbus/upstart/upstart_client.cc b/chromeos/ash/components/dbus/upstart/upstart_client.cc
index 7c37c6d6..2ac377c 100644
--- a/chromeos/ash/components/dbus/upstart/upstart_client.cc
+++ b/chromeos/ash/components/dbus/upstart/upstart_client.cc
@@ -27,7 +27,6 @@
 constexpr char kStopMethod[] = "Stop";
 
 constexpr char kUpstartJobsPath[] = "/com/ubuntu/Upstart/jobs/";
-constexpr char kAuthPolicyJob[] = "authpolicyd";
 constexpr char kMediaAnalyticsJob[] = "rtanalytics";
 // "wilco_5fdtc_5fdispatcher" below refers to the "wilco_dtc_dispatcher" upstart
 // job. Upstart escapes characters that aren't valid in D-Bus object paths
@@ -75,14 +74,6 @@
                                  std::move(callback)));
   }
 
-  void StartAuthPolicyService() override {
-    StartJob(kAuthPolicyJob, {}, base::DoNothing());
-  }
-
-  void RestartAuthPolicyService() override {
-    CallJobMethod(kAuthPolicyJob, kRestartMethod, {}, base::DoNothing());
-  }
-
   void StartMediaAnalytics(const std::vector<std::string>& upstart_env,
                            chromeos::VoidDBusMethodCallback callback) override {
     StartJob(kMediaAnalyticsJob, upstart_env, std::move(callback));
diff --git a/chromeos/ash/components/dbus/upstart/upstart_client.h b/chromeos/ash/components/dbus/upstart/upstart_client.h
index 0b658c0e..cf71d98 100644
--- a/chromeos/ash/components/dbus/upstart/upstart_client.h
+++ b/chromeos/ash/components/dbus/upstart/upstart_client.h
@@ -80,12 +80,6 @@
                        const std::vector<std::string>& upstart_env,
                        chromeos::VoidDBusMethodCallback callback) = 0;
 
-  // Starts authpolicyd.
-  virtual void StartAuthPolicyService() = 0;
-
-  // Restarts authpolicyd.
-  virtual void RestartAuthPolicyService() = 0;
-
   // Starts the media analytics process.
   // |upstart_env|: List of upstart environment variables to be passed to the
   // upstart service.
diff --git a/chromeos/ash/components/login/auth/BUILD.gn b/chromeos/ash/components/login/auth/BUILD.gn
index 1cf7c987..e9205f06 100644
--- a/chromeos/ash/components/login/auth/BUILD.gn
+++ b/chromeos/ash/components/login/auth/BUILD.gn
@@ -19,8 +19,6 @@
     "//base",
     "//base:i18n",
     "//chromeos/ash/components/cryptohome",
-    "//chromeos/ash/components/dbus/authpolicy",
-    "//chromeos/ash/components/dbus/authpolicy:authpolicy_proto",
     "//chromeos/ash/components/dbus/constants",
     "//chromeos/ash/components/dbus/cryptohome",
     "//chromeos/ash/components/dbus/cryptohome:cryptohome_proto",
@@ -135,8 +133,6 @@
     "//base",
     "//base:i18n",
     "//chromeos/ash/components/cryptohome",
-    "//chromeos/ash/components/dbus/authpolicy",
-    "//chromeos/ash/components/dbus/authpolicy:authpolicy_proto",
     "//chromeos/ash/components/dbus/cryptohome",
     "//chromeos/ash/components/dbus/cryptohome:cryptohome_proto",
     "//chromeos/ash/components/dbus/userdataauth",
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
index fbff1a02..ad14e5d 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
@@ -31,7 +31,25 @@
     const std::string& auth_token,
     bool enabled,
     base::OnceCallback<void(mojom::ConfigureResult)> callback) {
-  DCHECK(features::IsCryptohomeRecoveryEnabled());
+  CHECK(features::IsCryptohomeRecoveryEnabled());
+
+  auth_factor_config_->IsEditable(
+      auth_token, mojom::AuthFactor::kRecovery,
+      base::BindOnce(&RecoveryFactorEditor::OnGetEditable,
+                     weak_factory_.GetWeakPtr(), auth_token, enabled,
+                     std::move(callback)));
+}
+
+void RecoveryFactorEditor::OnGetEditable(
+    const std::string& auth_token,
+    bool should_enable,
+    base::OnceCallback<void(mojom::ConfigureResult)> callback,
+    bool is_editable) {
+  if (!is_editable) {
+    LOG(ERROR) << "Recovery configuration not editable";
+    std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
+    return;
+  }
 
   const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
   auto* user_context_ptr =
@@ -46,7 +64,7 @@
       user_context_ptr->GetAuthFactorsConfiguration().HasConfiguredFactor(
           cryptohome::AuthFactorType::kRecovery);
 
-  if (enabled == currently_enabled) {
+  if (should_enable == currently_enabled) {
     std::move(callback).Run(mojom::ConfigureResult::kSuccess);
     return;
   }
@@ -57,7 +75,7 @@
       base::BindOnce(&RecoveryFactorEditor::OnRecoveryFactorConfigured,
                      weak_factory_.GetWeakPtr(), std::move(callback));
 
-  if (enabled) {
+  if (should_enable) {
     auth_factor_editor_.AddRecoveryFactor(std::move(user_context),
                                           std::move(on_configured_callback));
   } else {
@@ -102,27 +120,6 @@
   }
 
   const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-
-  PrefService* prefs = quick_unlock_storage_->GetPrefService(*user);
-  const PrefService::Preference* recovery_pref =
-      prefs->FindPreference(prefs::kRecoveryFactorBehavior);
-  const base::Value is_configured_value{
-      context->GetAuthFactorsConfiguration().HasConfiguredFactor(
-          cryptohome::AuthFactorType::kRecovery)};
-  // In case the recovery pref value is recommended to be what we would set it
-  // to, we do not set it. This means that we do not consider the user to have
-  // overridden it in this case.
-  // This way, RecoveryFactorEditor can also be used from places where the user
-  // has not explicitly opted in, e.g. during OOBE.
-  if (recovery_pref && recovery_pref->IsRecommended() &&
-      recovery_pref->GetValue() != nullptr) {
-    if (*recovery_pref->GetValue() != is_configured_value) {
-      prefs->Set(prefs::kRecoveryFactorBehavior, is_configured_value);
-    }
-  } else {
-    prefs->Set(prefs::kRecoveryFactorBehavior, is_configured_value);
-  }
-
   quick_unlock_storage_->SetUserContext(user, std::move(context));
 
   std::move(callback).Run(mojom::ConfigureResult::kSuccess);
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
index 5e6450b..e240bb65 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
@@ -31,11 +31,14 @@
                  base::OnceCallback<void(mojom::ConfigureResult)>) override;
 
  private:
+  void OnGetEditable(const std::string& auth_token,
+                     bool should_enable,
+                     base::OnceCallback<void(mojom::ConfigureResult)> callback,
+                     bool is_editable);
   void OnRecoveryFactorConfigured(
       base::OnceCallback<void(mojom::ConfigureResult)> callback,
       std::unique_ptr<UserContext> context,
       absl::optional<AuthenticationError> error);
-
   void OnGetAuthFactorsConfiguration(
       base::OnceCallback<void(mojom::ConfigureResult)> callback,
       std::unique_ptr<UserContext> context,
diff --git a/components/app_restore/restore_data.cc b/components/app_restore/restore_data.cc
index cf18eccd..d93cc9c 100644
--- a/components/app_restore/restore_data.cc
+++ b/components/app_restore/restore_data.cc
@@ -112,26 +112,27 @@
 }
 
 base::Value RestoreData::ConvertToValue() const {
-  base::Value restore_data_dict(base::Value::Type::DICT);
-  for (const auto& it : app_id_to_launch_list_) {
-    if (it.second.empty())
+  base::Value::Dict restore_data_dict;
+  for (const auto& [app_id, launch_list] : app_id_to_launch_list_) {
+    if (launch_list.empty()) {
       continue;
-
-    base::Value info_dict(base::Value::Type::DICT);
-    for (const auto& data : it.second) {
-      info_dict.SetKey(base::NumberToString(data.first),
-                       data.second->ConvertToValue());
     }
 
-    restore_data_dict.SetKey(it.first, std::move(info_dict));
+    base::Value::Dict info_dict;
+    for (const auto& [window_id, app_restore_data] : launch_list) {
+      info_dict.Set(base::NumberToString(window_id),
+                    app_restore_data->ConvertToValue());
+    }
+
+    restore_data_dict.Set(app_id, std::move(info_dict));
   }
 
   if (removing_desk_guid_.is_valid()) {
-    restore_data_dict.GetDict().Set(kRemovingDeskGuidKey,
-                                    removing_desk_guid_.AsLowercaseString());
+    restore_data_dict.Set(kRemovingDeskGuidKey,
+                          removing_desk_guid_.AsLowercaseString());
   }
 
-  return restore_data_dict;
+  return base::Value(std::move(restore_data_dict));
 }
 
 bool RestoreData::HasAppTypeBrowser() const {
diff --git a/components/attribution_reporting/features.cc b/components/attribution_reporting/features.cc
index 0972590..ac6f771 100644
--- a/components/attribution_reporting/features.cc
+++ b/components/attribution_reporting/features.cc
@@ -10,6 +10,6 @@
 
 BASE_FEATURE(kAttributionReportingNullAggregatableReports,
              "AttributionReportingNullAggregatableReports",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/trigger_registration_unittest.cc b/components/attribution_reporting/trigger_registration_unittest.cc
index 106ee02..cbbd3319 100644
--- a/components/attribution_reporting/trigger_registration_unittest.cc
+++ b/components/attribution_reporting/trigger_registration_unittest.cc
@@ -10,7 +10,6 @@
 
 #include "base/functional/function_ref.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/values_test_util.h"
 #include "base/types/expected.h"
 #include "base/values.h"
@@ -19,7 +18,6 @@
 #include "components/attribution_reporting/aggregatable_trigger_data.h"
 #include "components/attribution_reporting/aggregatable_values.h"
 #include "components/attribution_reporting/event_trigger_data.h"
-#include "components/attribution_reporting/features.h"
 #include "components/attribution_reporting/filters.h"
 #include "components/attribution_reporting/source_registration_time_config.mojom.h"
 #include "components/attribution_reporting/test_utils.h"
@@ -60,7 +58,10 @@
       {
           "empty",
           R"json({})json",
-          TriggerRegistration(),
+          TriggerRegistrationWith([](TriggerRegistration& r) {
+            r.source_registration_time_config =
+                mojom::SourceRegistrationTimeConfig::kExclude;
+          }),
       },
       {
           "filters_valid",
@@ -230,84 +231,6 @@
           base::unexpected(
               TriggerRegistrationError::kAggregatableDedupKeyWrongType),
       },
-  };
-
-  static constexpr char kTriggerRegistrationErrorMetric[] =
-      "Conversions.TriggerRegistrationError5";
-
-  for (const auto& test_case : kTestCases) {
-    base::HistogramTester histograms;
-
-    auto trigger = TriggerRegistration::Parse(test_case.json);
-    EXPECT_EQ(trigger, test_case.expected) << test_case.description;
-
-    if (trigger.has_value()) {
-      histograms.ExpectTotalCount(kTriggerRegistrationErrorMetric, 0);
-    } else {
-      histograms.ExpectUniqueSample(kTriggerRegistrationErrorMetric,
-                                    trigger.error(), 1);
-    }
-  }
-}
-
-TEST(TriggerRegistrationTest, ToJson) {
-  const struct {
-    TriggerRegistration input;
-    const char* expected_json;
-  } kTestCases[] = {
-      {
-          TriggerRegistration(),
-          R"json({
-            "aggregation_coordinator_identifier": "aws-cloud",
-            "debug_reporting": false
-          })json",
-      },
-      {
-          TriggerRegistrationWith([](TriggerRegistration& r) {
-            r.aggregatable_dedup_keys = {
-                AggregatableDedupKey(/*dedup_key=*/1, FilterPair())};
-            r.aggregatable_trigger_data = {AggregatableTriggerData()};
-            r.aggregatable_values = *AggregatableValues::Create({{"a", 2}});
-            r.debug_key = 3;
-            r.debug_reporting = true;
-            r.event_triggers = {EventTriggerData()};
-            r.filters.positive = FiltersDisjunction({{{"b", {}}}});
-            r.filters.negative = FiltersDisjunction({{{"c", {}}}});
-          }),
-          R"json({
-            "aggregation_coordinator_identifier": "aws-cloud",
-            "aggregatable_deduplication_keys": [{"deduplication_key":"1"}],
-            "aggregatable_trigger_data": [{"key_piece":"0x0"}],
-            "aggregatable_values": {"a": 2},
-            "debug_key": "3",
-            "debug_reporting": true,
-            "event_trigger_data": [{"priority":"0","trigger_data":"0"}],
-            "filters": [{"b": []}],
-            "not_filters": [{"c": []}]
-          })json",
-      },
-  };
-
-  for (const auto& test_case : kTestCases) {
-    EXPECT_THAT(test_case.input.ToJson(),
-                base::test::IsJson(test_case.expected_json));
-  }
-}
-
-TEST(TriggerRegistrationTest, ParseAggregatableSourceRegistrationTime) {
-  const struct {
-    const char* description;
-    const char* json;
-    base::expected<TriggerRegistration, TriggerRegistrationError> expected;
-  } kTestCases[] = {
-      {
-          "empty",
-          R"json({})json",
-          TriggerRegistrationWith([](TriggerRegistration& r) {
-            r.source_registration_time_config =
-                mojom::SourceRegistrationTimeConfig::kExclude;
-          }),
-      },
       {
           "aggregatable_source_registration_time_include",
           R"json({"aggregatable_source_registration_time":"include"})json",
@@ -341,9 +264,6 @@
   static constexpr char kTriggerRegistrationErrorMetric[] =
       "Conversions.TriggerRegistrationError5";
 
-  base::test::ScopedFeatureList scoped_feature_list(
-      kAttributionReportingNullAggregatableReports);
-
   for (const auto& test_case : kTestCases) {
     base::HistogramTester histograms;
 
@@ -359,7 +279,7 @@
   }
 }
 
-TEST(TriggerRegistrationTest, SerializeAggregatableSourceRegistrationTime) {
+TEST(TriggerRegistrationTest, ToJson) {
   const struct {
     TriggerRegistration input;
     const char* expected_json;
@@ -374,20 +294,33 @@
       },
       {
           TriggerRegistrationWith([](TriggerRegistration& r) {
+            r.aggregatable_dedup_keys = {
+                AggregatableDedupKey(/*dedup_key=*/1, FilterPair())};
+            r.aggregatable_trigger_data = {AggregatableTriggerData()};
+            r.aggregatable_values = *AggregatableValues::Create({{"a", 2}});
+            r.debug_key = 3;
+            r.debug_reporting = true;
+            r.event_triggers = {EventTriggerData()};
+            r.filters.positive = FiltersDisjunction({{{"b", {}}}});
+            r.filters.negative = FiltersDisjunction({{{"c", {}}}});
             r.source_registration_time_config =
                 mojom::SourceRegistrationTimeConfig::kExclude;
           }),
           R"json({
             "aggregatable_source_registration_time": "exclude",
             "aggregation_coordinator_identifier": "aws-cloud",
-            "debug_reporting": false,
+            "aggregatable_deduplication_keys": [{"deduplication_key":"1"}],
+            "aggregatable_trigger_data": [{"key_piece":"0x0"}],
+            "aggregatable_values": {"a": 2},
+            "debug_key": "3",
+            "debug_reporting": true,
+            "event_trigger_data": [{"priority":"0","trigger_data":"0"}],
+            "filters": [{"b": []}],
+            "not_filters": [{"c": []}]
           })json",
       },
   };
 
-  base::test::ScopedFeatureList scoped_feature_list(
-      kAttributionReportingNullAggregatableReports);
-
   for (const auto& test_case : kTestCases) {
     EXPECT_THAT(test_case.input.ToJson(),
                 base::test::IsJson(test_case.expected_json));
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 8176f0a05..5968a140 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -8,6 +8,7 @@
 
 #include <tuple>
 
+#include "base/check_deref.h"
 #include "base/command_line.h"
 #include "base/containers/cxx20_erase_set.h"
 #include "base/debug/alias.h"
@@ -25,6 +26,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/autofill/content/renderer/form_autofill_util.h"
+#include "components/autofill/content/renderer/form_cache.h"
 #include "components/autofill/content/renderer/form_tracker.h"
 #include "components/autofill/content/renderer/password_autofill_agent.h"
 #include "components/autofill/content/renderer/password_generation_agent.h"
@@ -118,27 +120,32 @@
 // https://wicg.github.io/nav-speculation/prerendering.html#prerendering-bcs-subsection
 class AutofillAgent::DeferringAutofillDriver : public mojom::AutofillDriver {
  public:
-  explicit DeferringAutofillDriver(AutofillAgent* agent) : agent_(agent) {}
+  explicit DeferringAutofillDriver(AutofillAgent* agent)
+      : agent_(CHECK_DEREF(agent)) {}
   ~DeferringAutofillDriver() override = default;
 
  private:
   template <typename F, typename... Args>
   void SendMsg(F fn, Args&&... args) {
-    DCHECK(!agent_->IsPrerendering());
-    mojom::AutofillDriver& autofill_driver = agent_->GetAutofillDriver();
-    DCHECK_NE(&autofill_driver, this);
-    (autofill_driver.*fn)(std::forward<Args>(args)...);
+    if (auto* autofill_driver = agent_.unsafe_autofill_driver()) {
+      DCHECK(!agent_.IsPrerendering());
+      DCHECK_NE(autofill_driver, this);
+      (autofill_driver->*fn)(std::forward<Args>(args)...);
+    }
   }
+
   template <typename F, typename... Args>
   void DeferMsg(F fn, Args... args) {
-    DCHECK(agent_->IsPrerendering());
-    agent_->render_frame()
-        ->GetWebFrame()
-        ->GetDocument()
-        .AddPostPrerenderingActivationStep(base::BindOnce(
-            &DeferringAutofillDriver::SendMsg<F, Args...>,
-            weak_ptr_factory_.GetWeakPtr(), fn, std::forward<Args>(args)...));
+    if (auto* render_frame = agent_.unsafe_render_frame()) {
+      DCHECK(agent_.IsPrerendering());
+      render_frame->GetWebFrame()
+          ->GetDocument()
+          .AddPostPrerenderingActivationStep(base::BindOnce(
+              &DeferringAutofillDriver::SendMsg<F, Args...>,
+              weak_ptr_factory_.GetWeakPtr(), fn, std::forward<Args>(args)...));
+    }
   }
+
   void SetFormToBeProbablySubmitted(
       const absl::optional<FormData>& form) override {
     DeferMsg(&mojom::AutofillDriver::SetFormToBeProbablySubmitted, form);
@@ -213,19 +220,18 @@
              field, old_value);
   }
 
-  AutofillAgent* agent_ = nullptr;
+  AutofillAgent& agent_;
   base::WeakPtrFactory<DeferringAutofillDriver> weak_ptr_factory_{this};
 };
 
 AutofillAgent::FocusStateNotifier::FocusStateNotifier(AutofillAgent* agent)
-    : agent_(agent) {}
+    : agent_(CHECK_DEREF(agent)) {}
 
 AutofillAgent::FocusStateNotifier::~FocusStateNotifier() = default;
 
 void AutofillAgent::FocusStateNotifier::FocusedInputChanged(
     const WebNode& node) {
   CHECK(!node.IsNull());
-
   FocusedFieldType new_focused_field_type = FocusedFieldType::kUnknown;
   FieldRendererId new_focused_field_id = FieldRendererId();
   if (auto form_control_element = node.DynamicTo<WebFormControlElement>();
@@ -261,7 +267,7 @@
   if (input_element.IsPasswordFieldForAutofill()) {
     return FocusedFieldType::kFillablePasswordField;
   }
-  if (agent_->password_autofill_agent_->IsUsernameInputField(input_element)) {
+  if (agent_.password_autofill_agent_->IsUsernameInputField(input_element)) {
     return FocusedFieldType::kFillableUsernameField;
   }
   return FocusedFieldType::kFillableNonSearchField;
@@ -278,8 +284,8 @@
   }
 
   // TODO(crbug.com/1425166): Move FocusedInputChanged to AutofillDriver.
-  agent_->GetPasswordManagerDriver().FocusedInputChanged(
-      new_focused_field_id, new_focused_field_type);
+  agent_.GetPasswordManagerDriver().FocusedInputChanged(new_focused_field_id,
+                                                        new_focused_field_type);
 
   focused_field_type_ = new_focused_field_type;
   focused_field_id_ = new_focused_field_id;
@@ -290,7 +296,7 @@
                              PasswordGenerationAgent* password_generation_agent,
                              blink::AssociatedInterfaceRegistry* registry)
     : content::RenderFrameObserver(render_frame),
-      form_cache_(render_frame->GetWebFrame()),
+      form_cache_(std::make_unique<FormCache>(render_frame->GetWebFrame())),
       password_autofill_agent_(password_autofill_agent),
       password_generation_agent_(password_generation_agent),
       query_node_autofill_state_(WebAutofillState::kNotFilled),
@@ -323,8 +329,10 @@
 void AutofillAgent::DidCommitProvisionalLoad(ui::PageTransition transition) {
   // Navigation to a new page or a page refresh.
   element_.Reset();
-
-  form_cache_.Reset();
+  form_cache_ =
+      unsafe_render_frame()
+          ? std::make_unique<FormCache>(unsafe_render_frame()->GetWebFrame())
+          : nullptr;
   ResetLastInteractedElements();
   OnFormNoLongerSubmittable();
   SendPotentiallySubmittedFormToBrowser();
@@ -335,18 +343,21 @@
 }
 
 void AutofillAgent::DidChangeScrollOffset() {
-  if (element_.IsNull())
+  if (element_.IsNull()) {
     return;
+  }
 
   if (!focus_requires_scroll_) {
     // Post a task here since scroll offset may change during layout.
-    // (https://crbug.com/804886)
+    // TODO(crbug.com/804886): Do not cancel other tasks and do not invalidate
+    // PasswordAutofillAgent::autofill_agent_.
     weak_ptr_factory_.InvalidateWeakPtrs();
-    render_frame()
-        ->GetTaskRunner(blink::TaskType::kInternalUserInteraction)
-        ->PostTask(FROM_HERE,
-                   base::BindOnce(&AutofillAgent::DidChangeScrollOffsetImpl,
-                                  weak_ptr_factory_.GetWeakPtr(), element_));
+    if (auto* render_frame = unsafe_render_frame()) {
+      render_frame->GetTaskRunner(blink::TaskType::kInternalUserInteraction)
+          ->PostTask(FROM_HERE,
+                     base::BindOnce(&AutofillAgent::DidChangeScrollOffsetImpl,
+                                    weak_ptr_factory_.GetWeakPtr(), element_));
+    }
   } else {
     HidePopup();
   }
@@ -359,7 +370,8 @@
     return;
   }
 
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   FormData form;
   FormFieldData field;
@@ -368,7 +380,9 @@
           static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS |
                                    GetExtractDatalistMask()),
           &form, &field)) {
-    GetAutofillDriver().TextFieldDidScroll(form, field, field.bounds);
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->TextFieldDidScroll(form, field, field.bounds);
+    }
   }
 
   // Ignore subsequent scroll offset changes.
@@ -381,7 +395,9 @@
   if (element.IsNull()) {
     // Focus moved away from the last interacted form (if any) to somewhere else
     // on the page.
-    GetAutofillDriver().FocusNoLongerOnForm(!last_interacted_form_.IsNull());
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->FocusNoLongerOnForm(!last_interacted_form_.IsNull());
+    }
     return;
   }
 
@@ -394,7 +410,9 @@
        last_interacted_form_ != form_control_element.Form())) {
     // The focused element is not part of the last interacted form (could be
     // in a different form).
-    GetAutofillDriver().FocusNoLongerOnForm(/*had_interacted_form=*/true);
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->FocusNoLongerOnForm(/*had_interacted_form=*/true);
+    }
     focus_moved_to_new_form = true;
   }
 
@@ -428,12 +446,18 @@
           static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS |
                                    GetExtractDatalistMask()),
           &form, &field)) {
-    GetAutofillDriver().FocusOnFormField(form, field, field.bounds);
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->FocusOnFormField(form, field, field.bounds);
+    }
   }
 }
 
+// AutofillAgent is deleted asynchronously because OnDestruct() may be
+// triggered by JavaScript, which in turn may be triggered by AutofillAgent.
 void AutofillAgent::OnDestruct() {
-  Shutdown();
+  receiver_.reset();
+  form_cache_ = nullptr;
+  weak_ptr_factory_.InvalidateWeakPtrs();
   base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
                                                                 this);
 }
@@ -445,7 +469,7 @@
 void AutofillAgent::FireHostSubmitEvents(const WebFormElement& form,
                                          bool known_success,
                                          SubmissionSource source) {
-  DCHECK(IsOwnedByFrame(form, render_frame()));
+  DCHECK(!unsafe_render_frame() || IsOwnedByFrame(form, unsafe_render_frame()));
 
   FormData form_data;
   if (!form_util::ExtractFormData(form, *field_data_manager_.get(), &form_data))
@@ -463,17 +487,14 @@
       !submitted_forms_.insert(form_data.unique_renderer_id).second) {
     return;
   }
-
-  GetAutofillDriver().FormSubmitted(form_data, known_success, source);
-}
-
-void AutofillAgent::Shutdown() {
-  receiver_.reset();
-  weak_ptr_factory_.InvalidateWeakPtrs();
+  if (auto* autofill_driver = unsafe_autofill_driver()) {
+    autofill_driver->FormSubmitted(form_data, known_success, source);
+  }
 }
 
 void AutofillAgent::TextFieldDidEndEditing(const WebInputElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   // Sometimes "blur" events are side effects of the password generation
   // handling the page. They should not affect any UI in the browser.
@@ -481,7 +502,9 @@
       password_generation_agent_->ShouldIgnoreBlur()) {
     return;
   }
-  GetAutofillDriver().DidEndTextFieldEditing();
+  if (auto* autofill_driver = unsafe_autofill_driver()) {
+    autofill_driver->DidEndTextFieldEditing();
+  }
   focus_state_notifier_.ResetFocus();
   if (password_generation_agent_)
     password_generation_agent_->DidEndTextFieldEditing(element);
@@ -498,7 +521,8 @@
 }
 
 void AutofillAgent::OnTextFieldDidChange(const WebInputElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   if (password_generation_agent_ &&
       password_generation_agent_->TextDidChangeInTextField(element)) {
@@ -521,14 +545,17 @@
           static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS |
                                    GetExtractDatalistMask()),
           &form, &field)) {
-    GetAutofillDriver().TextFieldDidChange(form, field, field.bounds,
-                                           AutofillTickClock::NowTicks());
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->TextFieldDidChange(form, field, field.bounds,
+                                          AutofillTickClock::NowTicks());
+    }
   }
 }
 
 void AutofillAgent::TextFieldDidReceiveKeyDown(const WebInputElement& element,
                                                const WebKeyboardEvent& event) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   if (event.windows_key_code == ui::VKEY_DOWN ||
       event.windows_key_code == ui::VKEY_UP) {
@@ -541,7 +568,8 @@
 }
 
 void AutofillAgent::OpenTextDataListChooser(const WebInputElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
   ShowSuggestions(element, {.autofill_on_empty_values = true});
 }
 
@@ -553,7 +581,8 @@
 // the last field. That is, if within one batch the options of different
 // fields changed, all but one of these events will be lost.
 void AutofillAgent::DataListOptionsChanged(const WebInputElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   if (element.GetDocument().IsNull() || !is_popup_possibly_visible_ ||
       !element.Focused()) {
@@ -583,8 +612,12 @@
 }
 
 void AutofillAgent::TriggerRefillIfNeeded(const FormData& form) {
+  if (!unsafe_render_frame()) {
+    return;
+  }
   WebFormElement updated_form_element = form_util::FindFormByUniqueRendererId(
-      render_frame()->GetWebFrame()->GetDocument(), form.unique_renderer_id);
+      unsafe_render_frame()->GetWebFrame()->GetDocument(),
+      form.unique_renderer_id);
   FormData updated_form_data;
   if (updated_form_element.IsNull()) {
     CollectFormlessElements(&updated_form_data);
@@ -593,8 +626,11 @@
                                &updated_form_data);
   }
   // Deep-compare forms, but don't take into account the fields' values.
-  if (!FormData::DeepEqual(form, updated_form_data))
-    GetAutofillDriver().FormsSeen({updated_form_data}, {});
+  if (!FormData::DeepEqual(form, updated_form_data)) {
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->FormsSeen({updated_form_data}, {});
+    }
+  }
 }
 
 // mojom::AutofillAgent:
@@ -615,7 +651,10 @@
       (element_.IsNull() || !element_.Focused() ||
        form_util::GetFormRendererId(form_util::GetOwningForm(element_)) !=
            form.unique_renderer_id)) {
-    WebDocument document = render_frame()->GetWebFrame()->GetDocument();
+    if (!unsafe_render_frame()) {
+      return;
+    }
+    WebDocument document = unsafe_render_frame()->GetWebFrame()->GetDocument();
     element_ = form_util::FindFormControlElementByUniqueRendererId(
         document, form.fields.front().unique_renderer_id);
   }
@@ -629,7 +668,9 @@
     query_node_autofill_state_ = element_.GetAutofillState();
     previewed_elements_ = form_util::FillOrPreviewForm(form, element_, action);
 
-    GetAutofillDriver().DidPreviewAutofillFormData();
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->DidPreviewAutofillFormData();
+    }
   } else {
     was_last_action_fill_ = true;
 
@@ -646,8 +687,10 @@
     // TODO(crbug.com/1198811): Inform the BrowserAutofillManager about the
     // fields that were actually filled. It's possible that the form has changed
     // since the time filling was triggered.
-    GetAutofillDriver().DidFillAutofillFormData(form,
-                                                AutofillTickClock::NowTicks());
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->DidFillAutofillFormData(form,
+                                               AutofillTickClock::NowTicks());
+    }
 
     TriggerRefillIfNeeded(form);
     SendPotentiallySubmittedFormToBrowser();
@@ -658,16 +701,19 @@
     const std::vector<FormDataPredictions>& forms) {
   bool attach_predictions_to_dom = base::FeatureList::IsEnabled(
       features::test::kAutofillShowTypePredictions);
+  if (!form_cache_) {
+    return;
+  }
   for (const auto& form : forms) {
-    form_cache_.ShowPredictions(form, attach_predictions_to_dom);
+    form_cache_->ShowPredictions(form, attach_predictions_to_dom);
   }
 }
 
 void AutofillAgent::ClearSection() {
-  if (element_.IsNull())
+  if (element_.IsNull() || !form_cache_) {
     return;
-
-  form_cache_.ClearSectionWithElement(element_);
+  }
+  form_cache_->ClearSectionWithElement(element_);
 }
 
 void AutofillAgent::ClearPreviewedForm() {
@@ -814,10 +860,10 @@
 }
 
 bool AutofillAgent::CollectFormlessElements(FormData* output) const {
-  if (render_frame() == nullptr || render_frame()->GetWebFrame() == nullptr)
+  if (!unsafe_render_frame()) {
     return false;
-
-  WebDocument document = render_frame()->GetWebFrame()->GetDocument();
+  }
+  WebDocument document = unsafe_render_frame()->GetWebFrame()->GetDocument();
 
   // Build up the FormData from the unowned elements. This logic mostly
   // mirrors the construction of the synthetic form in form_cache.cc, but
@@ -839,7 +885,8 @@
 
 void AutofillAgent::ShowSuggestions(const WebFormControlElement& element,
                                     const ShowSuggestionsOptions& options) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   if (!element.IsEnabled() || element.IsReadOnly())
     return;
@@ -921,17 +968,16 @@
 
 void AutofillAgent::SetFieldsEligibleForManualFilling(
     const std::vector<FieldRendererId>& fields) {
-  form_cache_.SetFieldsEligibleForManualFilling(fields);
+  if (!form_cache_) {
+    return;
+  }
+  form_cache_->SetFieldsEligibleForManualFilling(fields);
 }
 
 void AutofillAgent::QueryAutofillSuggestions(
     const WebFormControlElement& element,
     AutoselectFirstSuggestion autoselect_first_suggestion,
     FormElementWasClicked form_element_was_clicked) {
-  blink::WebLocalFrame* frame = element.GetDocument().GetFrame();
-  if (!frame)
-    return;
-
   DCHECK(!element.DynamicTo<WebInputElement>().IsNull() ||
          form_util::IsTextAreaElement(element));
 
@@ -969,15 +1015,18 @@
   }
 
   is_popup_possibly_visible_ = true;
-  GetAutofillDriver().AskForValuesToFill(form, field, field.bounds,
-                                         autoselect_first_suggestion,
-                                         form_element_was_clicked);
+  if (auto* autofill_driver = unsafe_autofill_driver()) {
+    autofill_driver->AskForValuesToFill(form, field, field.bounds,
+                                        autoselect_first_suggestion,
+                                        form_element_was_clicked);
+  }
 }
 
 void AutofillAgent::DoFillFieldWithValue(const std::u16string& value,
                                          blink::WebFormControlElement& element,
                                          WebAutofillState autofill_state) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   form_tracker_.set_ignore_control_changes(true);
 
@@ -993,7 +1042,7 @@
 
 void AutofillAgent::DoPreviewFieldWithValue(const std::u16string& value,
                                             WebInputElement& node) {
-  DCHECK(IsOwnedByFrame(node, render_frame()));
+  DCHECK(!unsafe_render_frame() || IsOwnedByFrame(node, unsafe_render_frame()));
 
   ClearPreviewedForm();
   query_node_autofill_state_ = element_.GetAutofillState();
@@ -1038,18 +1087,23 @@
 }
 
 void AutofillAgent::ProcessForms() {
+  if (!form_cache_) {
+    return;
+  }
   FormCache::UpdateFormCacheResult cache =
-      form_cache_.UpdateFormCache(field_data_manager_.get());
-
+      form_cache_->UpdateFormCache(field_data_manager_.get());
   if (!cache.updated_forms.empty() || !cache.removed_forms.empty()) {
-    GetAutofillDriver().FormsSeen(cache.updated_forms,
-                                  std::move(cache.removed_forms).extract());
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->FormsSeen(cache.updated_forms,
+                                 std::move(cache.removed_forms).extract());
+    }
   }
 }
 
 void AutofillAgent::HidePopup() {
-  if (!is_popup_possibly_visible_)
+  if (!is_popup_possibly_visible_) {
     return;
+  }
   is_popup_possibly_visible_ = false;
   is_generation_popup_possibly_visible_ = false;
 
@@ -1057,7 +1111,9 @@
   if (IsKeyboardAccessoryEnabled())
     return;
 
-  GetAutofillDriver().HidePopup();
+  if (auto* autofill_driver = unsafe_autofill_driver()) {
+    autofill_driver->HidePopup();
+  }
 }
 
 void AutofillAgent::DidAddOrRemoveFormRelatedElementsDynamically() {
@@ -1068,7 +1124,10 @@
 }
 
 void AutofillAgent::DidCompleteFocusChangeInFrame() {
-  WebDocument doc = render_frame()->GetWebFrame()->GetDocument();
+  if (!unsafe_render_frame()) {
+    return;
+  }
+  WebDocument doc = unsafe_render_frame()->GetWebFrame()->GetDocument();
   WebElement focused_element;
   if (!doc.IsNull())
     focused_element = doc.FocusedElement();
@@ -1110,7 +1169,8 @@
 // forms changed, all but one of these events will be lost.
 void AutofillAgent::SelectFieldOptionsChanged(
     const blink::WebFormControlElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
 
   if (!was_last_action_fill_ || element_.IsNull())
     return;
@@ -1126,8 +1186,9 @@
 
 void AutofillAgent::BatchSelectOptionChange(
     const blink::WebFormControlElement& element) {
-  if (element.GetDocument().IsNull())
+  if (element.GetDocument().IsNull()) {
     return;
+  }
 
   // Look for the form and field associated with the select element. If they are
   // found, notify the driver that the form was modified dynamically.
@@ -1136,7 +1197,9 @@
   if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(),
                                             &form, &field) &&
       !field.options.empty()) {
-    GetAutofillDriver().SelectFieldOptionsDidChange(form);
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->SelectFieldOptionsDidChange(form);
+    }
   }
 }
 
@@ -1151,19 +1214,19 @@
 }
 
 void AutofillAgent::FormElementReset(const WebFormElement& form) {
-  DCHECK(IsOwnedByFrame(form, render_frame()));
-
+  DCHECK(!unsafe_render_frame() || IsOwnedByFrame(form, unsafe_render_frame()));
   password_autofill_agent_->InformAboutFormClearing(form);
 }
 
 void AutofillAgent::PasswordFieldReset(const WebInputElement& element) {
-  DCHECK(IsOwnedByFrame(element, render_frame()));
-
+  DCHECK(!unsafe_render_frame() ||
+         IsOwnedByFrame(element, unsafe_render_frame()));
   password_autofill_agent_->InformAboutFieldClearing(element);
 }
 
 bool AutofillAgent::IsPrerendering() const {
-  return render_frame()->GetWebFrame()->GetDocument().IsPrerendering();
+  return unsafe_render_frame() &&
+         unsafe_render_frame()->GetWebFrame()->GetDocument().IsPrerendering();
 }
 
 void AutofillAgent::FormControlElementClicked(
@@ -1194,8 +1257,11 @@
 }
 
 void AutofillAgent::HandleFocusChangeComplete() {
+  if (!unsafe_render_frame()) {
+    return;
+  }
   WebElement focused_element =
-      render_frame()->GetWebFrame()->GetDocument().FocusedElement();
+      unsafe_render_frame()->GetWebFrame()->GetDocument().FocusedElement();
   // When using Talkback on Android, and possibly others, traversing to and
   // focusing a field will not register as a click. Thus, when screen readers
   // are used, treat the focused node as if it was the last clicked. Also check
@@ -1210,10 +1276,14 @@
     if (form_util::IsTextAreaElementOrTextInput(focused_form_control_element)) {
       FormControlElementClicked(focused_form_control_element);
     } else if (IsKeyboardAccessoryEnabled()) {
-      GetAutofillDriver().HidePopup();
+      if (auto* autofill_driver = unsafe_autofill_driver()) {
+        autofill_driver->HidePopup();
+      }
     }
   } else if (IsKeyboardAccessoryEnabled()) {
-    GetAutofillDriver().HidePopup();
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->HidePopup();
+    }
   }
 
   focused_node_was_last_clicked_ = false;
@@ -1230,7 +1300,6 @@
 void AutofillAgent::SendFocusedInputChangedNotificationToBrowser(
     const WebElement& node) {
   focus_state_notifier_.FocusedInputChanged(node);
-
   auto input_element = node.DynamicTo<WebInputElement>();
   if (!input_element.IsNull()) {
     field_data_manager_->UpdateFieldDataMapWithNullValue(
@@ -1241,21 +1310,23 @@
 
 void AutofillAgent::AjaxSucceeded() {
   form_tracker_.AjaxSucceeded();
-
   SendPotentiallySubmittedFormToBrowser();
 }
 
 void AutofillAgent::JavaScriptChangedAutofilledValue(
     const blink::WebFormControlElement& element,
     const blink::WebString& old_value) {
-  if (old_value == element.Value())
+  if (old_value == element.Value()) {
     return;
+  }
   FormData form;
   FormFieldData field;
   if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(),
                                             &form, &field)) {
-    GetAutofillDriver().JavaScriptChangedAutofilledValue(form, field,
-                                                         old_value.Utf16());
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->JavaScriptChangedAutofilledValue(form, field,
+                                                        old_value.Utf16());
+    }
   }
 }
 
@@ -1279,7 +1350,10 @@
       UpdateLastInteractedForm(element.Form());
     } else {
       // Remove visible elements.
-      WebDocument doc = render_frame()->GetWebFrame()->GetDocument();
+      if (!unsafe_render_frame()) {
+        return;
+      }
+      WebDocument doc = unsafe_render_frame()->GetWebFrame()->GetDocument();
       if (!doc.IsNull()) {
         base::EraseIf(
             formless_elements_user_edited_,
@@ -1311,8 +1385,10 @@
               static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS |
                                        GetExtractDatalistMask()),
               &form_data, &field)) {
-        GetAutofillDriver().SelectControlDidChange(form_data, field,
-                                                   field.bounds);
+        if (auto* autofill_driver = unsafe_autofill_driver()) {
+          autofill_driver->SelectControlDidChange(form_data, field,
+                                                  field.bounds);
+        }
       }
     }
   }
@@ -1331,8 +1407,7 @@
 }
 
 void AutofillAgent::OnFormSubmitted(const WebFormElement& form) {
-  DCHECK(IsOwnedByFrame(form, render_frame()));
-
+  DCHECK(!unsafe_render_frame() || IsOwnedByFrame(form, unsafe_render_frame()));
   // Fire the submission event here because WILL_SEND_SUBMIT_EVENT is skipped
   // if javascript calls submit() directly.
   FireHostSubmitEvents(form, /*known_success=*/false,
@@ -1343,11 +1418,14 @@
 }
 
 void AutofillAgent::OnInferredFormSubmission(SubmissionSource source) {
+  if (!unsafe_render_frame()) {
+    return;
+  }
   if (source == SubmissionSource::FRAME_DETACHED &&
-      render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
+      unsafe_render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
     // No op.
   } else if (source == SubmissionSource::SAME_DOCUMENT_NAVIGATION &&
-             !render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
+             !unsafe_render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
     // No op.
   } else if (source == SubmissionSource::FRAME_DETACHED) {
     // Should not access the frame because it is now detached. Instead, use
@@ -1387,10 +1465,11 @@
     } else if (provisionally_saved_form_.has_value()) {
       return absl::make_optional(provisionally_saved_form_.value());
     }
-  } else if (formless_elements_were_autofilled_ ||
-             (formless_elements_user_edited_.size() != 0 &&
+  } else if (auto* render_frame = unsafe_render_frame();
+             formless_elements_were_autofilled_ ||
+             (!formless_elements_user_edited_.empty() && render_frame &&
               !form_util::IsSomeControlElementVisible(
-                  render_frame()->GetWebFrame(),
+                  render_frame->GetWebFrame()->GetDocument(),
                   formless_elements_user_edited_))) {
     // we check if all the elements the user has interacted with are gone,
     // to decide if submission has occurred, and use the
@@ -1407,7 +1486,9 @@
 }
 
 void AutofillAgent::SendPotentiallySubmittedFormToBrowser() {
-  GetAutofillDriver().SetFormToBeProbablySubmitted(GetSubmittedForm());
+  if (auto* autofill_driver = unsafe_autofill_driver()) {
+    autofill_driver->SetFormToBeProbablySubmitted(GetSubmittedForm());
+  }
 }
 
 void AutofillAgent::ResetLastInteractedElements() {
@@ -1420,7 +1501,7 @@
 
 void AutofillAgent::UpdateLastInteractedForm(
     const blink::WebFormElement& form) {
-  DCHECK(IsOwnedByFrame(form, render_frame()));
+  DCHECK(!unsafe_render_frame() || IsOwnedByFrame(form, unsafe_render_frame()));
 
   last_interacted_form_ = form;
   provisionally_saved_form_ = absl::make_optional<FormData>();
@@ -1435,22 +1516,21 @@
   submitted_forms_.clear();
 }
 
-mojom::AutofillDriver& AutofillAgent::GetAutofillDriver() {
+mojom::AutofillDriver* AutofillAgent::unsafe_autofill_driver() {
   if (IsPrerendering()) {
     if (!deferring_autofill_driver_) {
       deferring_autofill_driver_ =
           std::make_unique<DeferringAutofillDriver>(this);
     }
-    return *deferring_autofill_driver_;
+    return deferring_autofill_driver_.get();
   }
 
   // Lazily bind this interface.
-  if (!autofill_driver_) {
-    render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
+  if (unsafe_render_frame() && !autofill_driver_) {
+    unsafe_render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
         &autofill_driver_);
   }
-
-  return *autofill_driver_;
+  return autofill_driver_.get();
 }
 
 mojom::PasswordManagerDriver& AutofillAgent::GetPasswordManagerDriver() {
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 0b20b0f..f233f48e 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -15,7 +15,6 @@
 #include "base/timer/timer.h"
 #include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
 #include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
-#include "components/autofill/content/renderer/form_cache.h"
 #include "components/autofill/content/renderer/form_tracker.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
@@ -39,6 +38,7 @@
 namespace autofill {
 
 struct FormData;
+class FormCache;
 class PasswordAutofillAgent;
 class PasswordGenerationAgent;
 class FieldDataManager;
@@ -50,6 +50,14 @@
 // communicates with exactly one ContentAutofillDriver throughout its entire
 // lifetime.
 //
+// AutofillAgent is deleted asynchronously because it may itself take action
+// that (via JavaScript) causes the associated RenderFrame's deletion.
+// AutofillAgent is pending deletion between OnDestruct() and ~AutofillAgent().
+// To handle this state, care must be taken to check for nullptrs:
+// - `unsafe_autofill_driver()`
+// - `unsafe_render_frame()`
+// - `form_cache_`
+//
 // This RenderFrame owns all forms and fields in the renderer-browser
 // communication:
 // - AutofillAgent may assume that forms and fields received in the
@@ -82,8 +90,10 @@
   void BindPendingReceiver(
       mojo::PendingAssociatedReceiver<mojom::AutofillAgent> pending_receiver);
 
-  // Callers should not store the returned value longer than a function scope.
-  mojom::AutofillDriver& GetAutofillDriver();
+  // Callers must not store the returned value longer than a function scope.
+  // unsafe_autofill_driver() is nullptr if unsafe_render_frame() is nullptr and
+  // the `autofill_driver_` has not been bound yet.
+  mojom::AutofillDriver* unsafe_autofill_driver();
   mojom::PasswordManagerDriver& GetPasswordManagerDriver();
 
   // mojom::AutofillAgent:
@@ -212,7 +222,7 @@
     FieldRendererId focused_field_id_;
     mojom::FocusedFieldType focused_field_type_ =
         mojom::FocusedFieldType::kUnknown;
-    AutofillAgent* agent_ = nullptr;
+    AutofillAgent& agent_;
   };
 
   // content::RenderFrameObserver:
@@ -223,6 +233,22 @@
   void AccessibilityModeChanged(const ui::AXMode& mode) override;
   void OnDestruct() override;
 
+  // The RenderFrame* is nullptr while the AutofillAgent is pending deletion,
+  // between OnDestruct() and ~AutofillAgent().
+  content::RenderFrame* unsafe_render_frame() const {
+    return content::RenderFrameObserver::render_frame();
+  }
+
+  // Use unsafe_render_frame() instead.
+  template <typename T = int>
+  content::RenderFrame* render_frame(T* = 0) const {
+    static_assert(
+        std::is_void_v<T>,
+        "Beware that the RenderFrame may become nullptr by OnDestruct() "
+        "because AutofillAgent destructs itself asynchronously. Use "
+        "unsafe_render_frame() instead and make test that it is non-nullptr.");
+  }
+
   // Fires Mojo messages for a given form submission.
   void FireHostSubmitEvents(const blink::WebFormElement& form,
                             bool known_success,
@@ -231,10 +257,6 @@
                             bool known_success,
                             mojom::SubmissionSource source);
 
-  // Shuts the AutofillAgent down on RenderFrame deletion. Safe to call multiple
-  // times.
-  void Shutdown();
-
   // blink::WebAutofillClient:
   void TextFieldDidEndEditing(const blink::WebInputElement& element) override;
   void TextFieldDidChange(const blink::WebFormControlElement& element) override;
@@ -336,9 +358,9 @@
   void BatchSelectOptionChange(const blink::WebFormControlElement& element);
   void BatchDataListOptionChange(const blink::WebFormControlElement& element);
 
-  // Formerly cached forms for all frames, now only caches forms for the current
-  // frame.
-  FormCache form_cache_;
+  // Contains the form of the document. Does not survive navigations and is
+  // reset when the AutofillAgent is pending deletion.
+  std::unique_ptr<FormCache> form_cache_;
 
   PasswordAutofillAgent* password_autofill_agent_;      // Weak reference.
   PasswordGenerationAgent* password_generation_agent_;  // Weak reference.
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 45ca31a4..48dbf28c 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -1947,12 +1947,8 @@
 }
 
 bool IsSomeControlElementVisible(
-    blink::WebLocalFrame* frame,
+    const blink::WebDocument& document,
     const std::set<FieldRendererId>& control_elements) {
-  WebDocument doc = frame->GetDocument();
-  if (doc.IsNull())
-    return false;
-
   // Returns true iff at least one element from |fields| is visible and there
   // exists an element in |control_elements| with the same field renderer id.
   // The average case time complexity is O(N log M), where N is the number of
@@ -1967,10 +1963,10 @@
                                     GetFieldRendererId(field));
             });
       };
-
-  return base::ranges::any_of(doc.Forms(), ContainsVisibleField,
-                              &WebFormElement::GetFormControlElements) ||
-         ContainsVisibleField(doc.UnassociatedFormControls());
+  return !document.IsNull() &&
+         (base::ranges::any_of(document.Forms(), ContainsVisibleField,
+                               &WebFormElement::GetFormControlElements) ||
+          ContainsVisibleField(document.UnassociatedFormControls()));
 }
 
 GURL GetCanonicalActionForForm(const WebFormElement& form) {
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index e0ddb676..fc64583 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -150,9 +150,10 @@
                      const FieldDataManager& field_data_manager,
                      FormData* data);
 
-// Returns true if at least one element from |control_elements| is visible.
+// Returns true if at least one element from |control_elements| is visible in
+// |document|.
 bool IsSomeControlElementVisible(
-    blink::WebLocalFrame* frame,
+    const blink::WebDocument& document,
     const std::set<FieldRendererId>& control_elements);
 
 // Helper functions to assist in getting the canonical form of the action and
diff --git a/components/autofill/content/renderer/form_cache.cc b/components/autofill/content/renderer/form_cache.cc
index acf9cfed..cca4451 100644
--- a/components/autofill/content/renderer/form_cache.cc
+++ b/components/autofill/content/renderer/form_cache.cc
@@ -216,15 +216,6 @@
   return r;
 }
 
-void FormCache::Reset() {
-  synthetic_form_ = FormData();
-  parsed_forms_.clear();
-  initial_select_values_.clear();
-  initial_selectmenu_values_.clear();
-  initial_checked_state_.clear();
-  fields_eligible_for_manual_filling_.clear();
-}
-
 void FormCache::ClearElement(WebFormControlElement& control_element,
                              const WebFormControlElement& trigger_element) {
   // Don't modify the value of disabled fields.
diff --git a/components/autofill/content/renderer/form_cache.h b/components/autofill/content/renderer/form_cache.h
index a1e94725..20dc6a83 100644
--- a/components/autofill/content/renderer/form_cache.h
+++ b/components/autofill/content/renderer/form_cache.h
@@ -85,9 +85,6 @@
   UpdateFormCacheResult UpdateFormCache(
       const FieldDataManager* field_data_manager);
 
-  // Resets the forms.
-  void Reset();
-
   // Clears the values of all input elements in the section of the form that
   // contains |element|.  Returns false if the form is not found.
   bool ClearSectionWithElement(const blink::WebFormControlElement& element);
diff --git a/components/autofill/content/renderer/form_tracker.cc b/components/autofill/content/renderer/form_tracker.cc
index 91a9667..2009b43 100644
--- a/components/autofill/content/renderer/form_tracker.cc
+++ b/components/autofill/content/renderer/form_tracker.cc
@@ -69,19 +69,24 @@
   if (input_element.IsNull())
     return;
 
+  if (!unsafe_render_frame()) {
+    return;
+  }
+
   // Disregard text changes that aren't caused by user gestures or pastes. Note
   // that pastes aren't necessarily user gestures because Blink's conception of
   // user gestures is centered around creating new windows/tabs.
   if (user_gesture_required_ &&
-      !render_frame()->GetWebFrame()->HasTransientUserActivation() &&
-      !render_frame()->IsPasting())
+      !unsafe_render_frame()->GetWebFrame()->HasTransientUserActivation() &&
+      !unsafe_render_frame()->IsPasting()) {
     return;
+  }
 
   // We post a task for doing the Autofill as the caret position is not set
   // properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and
   // it is needed to trigger autofill.
   weak_ptr_factory_.InvalidateWeakPtrs();
-  render_frame()
+  unsafe_render_frame()
       ->GetWebFrame()
       ->GetTaskRunner(blink::TaskType::kInternalUserInteraction)
       ->PostTask(FROM_HERE,
@@ -97,9 +102,13 @@
   if (ignore_control_changes_)
     return;
 
+  if (!unsafe_render_frame()) {
+    return;
+  }
+
   // Post a task to avoid processing select control change while it is changing.
   weak_ptr_factory_.InvalidateWeakPtrs();
-  render_frame()
+  unsafe_render_frame()
       ->GetWebFrame()
       ->GetTaskRunner(blink::TaskType::kInternalUserInteraction)
       ->PostTask(FROM_HERE, base::BindRepeating(
@@ -134,8 +143,9 @@
   // The frame or document could be null because this function is called
   // asynchronously.
   const blink::WebDocument& doc = element.GetDocument();
-  if (!render_frame() || doc.IsNull() || !doc.GetFrame())
+  if (!unsafe_render_frame() || doc.IsNull() || !doc.GetFrame()) {
     return;
+  }
 
   if (element.Form().IsNull()) {
     last_interacted_formless_element_ = element;
@@ -162,10 +172,14 @@
     const GURL& url,
     absl::optional<blink::WebNavigationType> navigation_type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_);
-  blink::WebLocalFrame* navigated_frame = render_frame()->GetWebFrame();
-  // Ony handle primary main frame.
-  if (!navigated_frame->IsOutermostMainFrame())
+  if (!unsafe_render_frame()) {
     return;
+  }
+  // Ony handle primary main frame.
+  if (!unsafe_render_frame() ||
+      !unsafe_render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
+    return;
+  }
 
   // Bug fix for crbug.com/368690. isProcessingUserGesture() is false when
   // the user is performing actions outside the page (e.g. typed url,
@@ -197,15 +211,16 @@
 
 void FormTracker::WillSubmitForm(const WebFormElement& form) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_);
-
   // A form submission may target a frame other than the frame that owns |form|.
   // The WillSubmitForm() event is only fired on the target frame's FormTracker
   // (provided that both have the same origin). In such a case, we ignore the
   // form submission event. If we didn't, we would send |form| to an
   // AutofillAgent and then to a ContentAutofillDriver etc. which haven't seen
   // this form before. See crbug.com/1240247#c13 for details.
-  if (!form_util::IsOwnedByFrame(form, render_frame()))
+  if (!unsafe_render_frame() ||
+      !form_util::IsOwnedByFrame(form, unsafe_render_frame())) {
     return;
+  }
 
   FireFormSubmitted(form);
 }
diff --git a/components/autofill/content/renderer/form_tracker.h b/components/autofill/content/renderer/form_tracker.h
index 248ccad5..86a2c64 100644
--- a/components/autofill/content/renderer/form_tracker.h
+++ b/components/autofill/content/renderer/form_tracker.h
@@ -64,7 +64,7 @@
     virtual ~Observer() {}
   };
 
-  FormTracker(content::RenderFrame* render_frame);
+  explicit FormTracker(content::RenderFrame* render_frame);
 
   FormTracker(const FormTracker&) = delete;
   FormTracker& operator=(const FormTracker&) = delete;
@@ -109,6 +109,22 @@
   void WillSubmitForm(const blink::WebFormElement& form) override;
   void OnDestruct() override;
 
+  // The RenderFrame* is nullptr while the AutofillAgent that owns this
+  // FormTracker is pending deletion, between OnDestruct() and ~FormTracker().
+  content::RenderFrame* unsafe_render_frame() const {
+    return content::RenderFrameObserver::render_frame();
+  }
+
+  // Use unsafe_render_frame() instead.
+  template <typename T = int>
+  content::RenderFrame* render_frame(T* = 0) const {
+    static_assert(
+        std::is_void_v<T>,
+        "Beware that the RenderFrame may become nullptr by OnDestruct() "
+        "because AutofillAgent destructs itself asynchronously. Use "
+        "unsafe_render_frame() instead and make test that it is non-nullptr.");
+  }
+
   // content::WebLocalFrameObserver:
   void OnFrameDetached() override;
   void WillSendSubmitEvent(const blink::WebFormElement& form) override;
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 6a798b4..f1d3bd3 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -1945,8 +1945,9 @@
 }
 
 void PasswordAutofillAgent::HidePopup() {
-  if (autofill_agent_)
-    autofill_agent_->GetAutofillDriver().HidePopup();
+  if (autofill_agent_ && autofill_agent_->unsafe_autofill_driver()) {
+    autofill_agent_->unsafe_autofill_driver()->HidePopup();
+  }
 }
 
 mojom::PasswordManagerDriver&
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 110ebc8..8b016ef 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -5,7 +5,6 @@
 #include "components/autofill/core/browser/form_structure.h"
 
 #include <stdint.h>
-#include <utility>
 
 #include <algorithm>
 #include <deque>
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index 6feed4a..ef60504 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -423,7 +423,7 @@
   void StopTheDedupeProcess() {
     personal_data_->pref_service_->SetInteger(
         prefs::kAutofillLastVersionDeduped,
-        atoi(version_info::GetVersionNumber().c_str()));
+        version_info::GetMajorVersionNumberAsInt());
   }
 
   void AddProfileToPersonalDataManager(const AutofillProfile& profile) {
diff --git a/components/autofill/core/browser/validation.cc b/components/autofill/core/browser/validation.cc
index fed08d3f..65cd6011 100644
--- a/components/autofill/core/browser/validation.cc
+++ b/components/autofill/core/browser/validation.cc
@@ -16,7 +16,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
-#include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/geo/phone_number_i18n.h"
 #include "components/autofill/core/browser/geo/state_names.h"
@@ -131,30 +130,6 @@
          base::ContainsOnlyChars(code, u"0123456789");
 }
 
-bool IsValidCreditCardNumberForBasicCardNetworks(
-    const std::u16string& text,
-    const std::set<std::string>& supported_basic_card_networks,
-    std::u16string* error_message) {
-  DCHECK(error_message);
-
-  // The type check is cheaper than the credit card number check.
-  const std::string basic_card_issuer_network =
-      data_util::GetPaymentRequestData(CreditCard::GetCardNetwork(text))
-          .basic_card_issuer_network;
-  if (!supported_basic_card_networks.count(basic_card_issuer_network)) {
-    *error_message = l10n_util::GetStringUTF16(
-        IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE);
-    return false;
-  }
-
-  if (IsValidCreditCardNumber(text))
-    return true;
-
-  *error_message = l10n_util::GetStringUTF16(
-      IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE);
-  return false;
-}
-
 bool IsValidEmailAddress(const std::u16string& text) {
   // E-Mail pattern as defined by the WhatWG. (4.10.7.1.5 E-Mail state)
   static constexpr char16_t kEmailPattern[] =
diff --git a/components/autofill/core/browser/validation.h b/components/autofill/core/browser/validation.h
index f0a676c..5505fd1 100644
--- a/components/autofill/core/browser/validation.h
+++ b/components/autofill/core/browser/validation.h
@@ -5,7 +5,6 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_VALIDATION_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_VALIDATION_H_
 
-#include <set>
 #include <string>
 
 #include "base/strings/string_piece_forward.h"
@@ -57,14 +56,6 @@
                                    const base::StringPiece card_network,
                                    CvcType cvc_type = CvcType::kRegularCvc);
 
-// Returns true if |text| is a supported card type and a valid credit card
-// number. |error_message| can't be null and will be filled with the appropriate
-// error message.
-bool IsValidCreditCardNumberForBasicCardNetworks(
-    const std::u16string& text,
-    const std::set<std::string>& supported_basic_card_networks,
-    std::u16string* error_message);
-
 // Returns true if |text| looks like a valid e-mail address.
 bool IsValidEmailAddress(const std::u16string& text);
 
diff --git a/components/autofill/core/browser/validation_unittest.cc b/components/autofill/core/browser/validation_unittest.cc
index 1e0b363..fe4719fb 100644
--- a/components/autofill/core/browser/validation_unittest.cc
+++ b/components/autofill/core/browser/validation_unittest.cc
@@ -377,108 +377,6 @@
             false,
             IDS_PAYMENTS_VALIDATION_INVALID_CREDIT_CARD_EXPIRATION_YEAR)));
 
-struct CCNumberCase {
-  CCNumberCase(const char16_t* value,
-               const std::set<std::string> supported_basic_card_networks,
-               bool expected_valid,
-               int expected_error_id)
-      : value(value),
-        supported_basic_card_networks(supported_basic_card_networks),
-        expected_valid(expected_valid),
-        expected_error_id(expected_error_id) {}
-  ~CCNumberCase() {}
-
-  const char16_t* const value;
-  const std::set<std::string> supported_basic_card_networks;
-  const bool expected_valid;
-  const int expected_error_id;
-};
-
-class AutofillCCNumberValidationTest
-    : public testing::TestWithParam<CCNumberCase> {};
-
-TEST_P(AutofillCCNumberValidationTest, IsValidCreditCardNumber) {
-  std::u16string error_message;
-  EXPECT_EQ(GetParam().expected_valid,
-            IsValidCreditCardNumberForBasicCardNetworks(
-                GetParam().value, GetParam().supported_basic_card_networks,
-                &error_message))
-      << "Failed to validate CC number " << base::UTF16ToUTF8(GetParam().value);
-  if (!GetParam().expected_valid) {
-    EXPECT_EQ(l10n_util::GetStringUTF16(GetParam().expected_error_id),
-              error_message);
-  }
-}
-
-static const std::set<std::string> kAllBasicCardNetworks{
-    "amex",       "discover", "diners", "elo",      "jcb",
-    "mastercard", "mir",      "troy",   "unionpay", "visa"};
-
-INSTANTIATE_TEST_SUITE_P(
-    CreditCardNumber,
-    AutofillCCNumberValidationTest,
-    testing::Values(
-        CCNumberCase(kValidNumbers[0], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[1], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[2], kAllBasicCardNetworks, true, 0),
-        // Generic card not supported.
-        CCNumberCase(kValidNumbers[3],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE),
-
-        CCNumberCase(kValidNumbers[4], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[5], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[6], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[7], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[8], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[9], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[10], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[11], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[12], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[13], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[14], kAllBasicCardNetworks, true, 0),
-        // Generic cards not supported.
-        CCNumberCase(kValidNumbers[15],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE),
-        CCNumberCase(kValidNumbers[16],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE),
-
-        CCNumberCase(kValidNumbers[17], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[18], kAllBasicCardNetworks, true, 0),
-        CCNumberCase(kValidNumbers[19], kAllBasicCardNetworks, true, 0),
-
-        CCNumberCase(kInvalidNumbers[0],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE),
-        CCNumberCase(kInvalidNumbers[1],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE),
-        CCNumberCase(kInvalidNumbers[2],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE),
-        CCNumberCase(kInvalidNumbers[3],
-                     kAllBasicCardNetworks,
-                     false,
-                     IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE),
-
-        // Valid numbers can still be invalid if the type is not supported.
-        CCNumberCase(kValidNumbers[10],  // Mastercard number.
-                     {"visa"},
-                     false,
-                     IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE),
-        CCNumberCase(kValidNumbers[12],  // Visa number.
-                     {"jcb", "diners", "unionpay", "mastercard"},
-                     false,
-                     IDS_PAYMENTS_VALIDATION_UNSUPPORTED_CREDIT_CARD_TYPE)));
-
 struct GetCvcLengthForCardTypeCase {
   GetCvcLengthForCardTypeCase(const char* card_network,
                               size_t expected_length,
diff --git a/components/browser_ui/widget/android/java/res/drawable/search_toolbar_modern_bg.xml b/components/browser_ui/widget/android/java/res/drawable/search_toolbar_modern_bg.xml
index a5e4b21..9d97ba6 100644
--- a/components/browser_ui/widget/android/java/res/drawable/search_toolbar_modern_bg.xml
+++ b/components/browser_ui/widget/android/java/res/drawable/search_toolbar_modern_bg.xml
@@ -12,9 +12,9 @@
         </shape>
     </item>
     <item
-        android:top="@dimen/search_toolbar_modern_bg_top_bottom_inset"
-        android:bottom="@dimen/search_toolbar_modern_bg_top_bottom_inset"
-        android:left="@dimen/search_toolbar_modern_bg_lateral_inset"
-        android:right="@dimen/search_toolbar_modern_bg_lateral_inset"
+        android:top="@dimen/search_toolbar_modern_bg_vertical_inset"
+        android:bottom="@dimen/search_toolbar_modern_bg_vertical_inset"
+        android:start="@dimen/search_toolbar_modern_bg_horizontal_inset"
+        android:end="@dimen/search_toolbar_modern_bg_horizontal_inset"
         android:drawable="@drawable/modern_toolbar_text_box_background" />
 </layer-list>
diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
index 17e8a9f..44c401a 100644
--- a/components/browser_ui/widget/android/java/res/values/dimens.xml
+++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
@@ -64,8 +64,8 @@
     <!-- SelectableListLayout dimensions -->
     <dimen name="selectable_list_action_bar_end_padding">6dp</dimen>
     <dimen name="toolbar_wide_display_start_offset">13dp</dimen>
-    <dimen name="search_toolbar_modern_bg_top_bottom_inset">8dp</dimen>
-    <dimen name="search_toolbar_modern_bg_lateral_inset">8dp</dimen>
+    <dimen name="search_toolbar_modern_bg_vertical_inset">8dp</dimen>
+    <dimen name="search_toolbar_modern_bg_horizontal_inset">8dp</dimen>
     <dimen name="selectable_list_toolbar_nav_button_start_offset">4dp</dimen>
     <dimen name="selectable_list_search_icon_end_padding">4dp</dimen>
     <dimen name="selectable_list_toolbar_height">56dp</dimen>
diff --git a/components/content_settings/core/browser/content_settings_pref_unittest.cc b/components/content_settings/core/browser/content_settings_pref_unittest.cc
index 65b7601..ecad0ac 100644
--- a/components/content_settings/core/browser/content_settings_pref_unittest.cc
+++ b/components/content_settings/core/browser/content_settings_pref_unittest.cc
@@ -52,17 +52,12 @@
 //       "tag": "...",
 //     }
 //   }
-base::Value CreateDummyContentSettingValue(base::StringPiece tag,
-                                           bool expired) {
-  base::Value setting(base::Value::Type::DICT);
-  setting.SetKey(kTagKey, base::Value(tag));
-
-  base::Value pref_value(base::Value::Type::DICT);
-  pref_value.SetKey(kLastModifiedKey, base::Value("13189876543210000"));
-  pref_value.SetKey(kSettingKey, std::move(setting));
-  pref_value.SetKey(kExpirationKey, expired ? base::Value("13189876543210001")
-                                            : base::Value("0"));
-  return pref_value;
+base::Value::Dict CreateDummyContentSettingValue(base::StringPiece tag,
+                                                 bool expired) {
+  return base::Value::Dict()
+      .Set(kSettingKey, base::Value::Dict().Set(kTagKey, tag))
+      .Set(kLastModifiedKey, "13189876543210000")
+      .Set(kExpirationKey, expired ? "13189876543210001" : "0");
 }
 
 // Given the JSON dictionary representing the "setting" stored under a content
@@ -116,17 +111,16 @@
       {kTestPatternCanonicalBeta, kTestPatternCanonicalBeta},
   };
 
-  base::Value original_pref_value(base::Value::Type::DICT);
+  base::Value::Dict original_pref_value;
   for (const auto* pattern : kTestOriginalPatterns) {
-    original_pref_value.SetKey(
+    original_pref_value.Set(
         pattern, CreateDummyContentSettingValue(pattern, /*expired=*/false));
   }
 
   TestingPrefServiceSimple prefs;
   prefs.registry()->RegisterDictionaryPref(kTestContentSettingPrefName);
-  prefs.SetUserPref(
-      kTestContentSettingPrefName,
-      base::Value::ToUniquePtrValue(std::move(original_pref_value)));
+  prefs.SetUserPref(kTestContentSettingPrefName,
+                    std::move(original_pref_value));
 
   PrefChangeRegistrar registrar;
   registrar.Init(&prefs);
@@ -181,21 +175,20 @@
 
   // Create two pre-existing entries, one that is expired and one that never
   // expires.
-  base::Value original_pref_value(base::Value::Type::DICT);
-  original_pref_value.SetKey(
+  base::Value::Dict original_pref_value;
+  original_pref_value.Set(
       kTestPatternCanonicalAlpha,
       CreateDummyContentSettingValue(kTestPatternCanonicalAlpha,
                                      /*expired=*/true));
-  original_pref_value.SetKey(
+  original_pref_value.Set(
       kTestPatternCanonicalBeta,
       CreateDummyContentSettingValue(kTestPatternCanonicalBeta,
                                      /*expired=*/false));
 
   TestingPrefServiceSimple prefs;
   prefs.registry()->RegisterDictionaryPref(kTestContentSettingPrefName);
-  prefs.SetUserPref(
-      kTestContentSettingPrefName,
-      base::Value::ToUniquePtrValue(std::move(original_pref_value)));
+  prefs.SetUserPref(kTestContentSettingPrefName,
+                    std::move(original_pref_value));
 
   PrefChangeRegistrar registrar;
   registrar.Init(&prefs);
@@ -238,25 +231,23 @@
 TEST(ContentSettingsPref, LegacyLastModifiedLoad) {
   constexpr char kPatternPair[] = "http://example.com,*";
 
-  base::Value original_pref_value(base::Value::Type::DICT);
+  base::Value::Dict original_pref_value;
   const base::Time last_modified =
       base::Time::FromInternalValue(13189876543210000);
 
   // Create a single entry using our old internal value for last_modified.
-  base::Value pref_value(base::Value::Type::DICT);
-  pref_value.SetKey(
-      kLastModifiedKey,
-      base::Value(base::NumberToString(last_modified.ToInternalValue())));
-  pref_value.SetKey(kSettingKey, base::Value(CONTENT_SETTING_BLOCK));
-  pref_value.SetKey(kExpirationKey, base::Value("0"));
+  base::Value::Dict pref_value;
+  pref_value.Set(kLastModifiedKey,
+                 base::NumberToString(last_modified.ToInternalValue()));
+  pref_value.Set(kSettingKey, CONTENT_SETTING_BLOCK);
+  pref_value.Set(kExpirationKey, "0");
 
-  original_pref_value.SetKey(kPatternPair, std::move(pref_value));
+  original_pref_value.Set(kPatternPair, std::move(pref_value));
 
   TestingPrefServiceSimple prefs;
   prefs.registry()->RegisterDictionaryPref(kTestContentSettingPrefName);
-  prefs.SetUserPref(
-      kTestContentSettingPrefName,
-      base::Value::ToUniquePtrValue(std::move(original_pref_value)));
+  prefs.SetUserPref(kTestContentSettingPrefName,
+                    std::move(original_pref_value));
 
   PrefChangeRegistrar registrar;
   registrar.Init(&prefs);
diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
index 86e97af..67f9610 100644
--- a/components/embedder_support/user_agent_utils.cc
+++ b/components/embedder_support/user_agent_utils.cc
@@ -230,11 +230,9 @@
 
 std::string GetVersionNumber(const UserAgentOptions& options) {
   // Force major version to 99.
-  if (ShouldForceMajorVersionToMinorPosition(options.force_major_to_minor))
-    return GetMajorInMinorVersionNumber();
-
-  const std::string& version_str = version_info::GetVersionNumber();
-  return version_str;
+  return ShouldForceMajorVersionToMinorPosition(options.force_major_to_minor)
+             ? GetMajorInMinorVersionNumber()
+             : version_info::GetVersionNumber();
 }
 
 const blink::UserAgentBrandList GetUserAgentBrandList(
@@ -353,19 +351,14 @@
     UserAgentReductionEnterprisePolicyState user_agent_reduction) {
   if (ShouldForceMajorVersionToMinorPosition(force_major_to_minor)) {
     // Force major version to 99 and major version to minor version position.
-    if (ShouldReduceUserAgentMinorVersion(user_agent_reduction)) {
-      return "Chrome/" + GetReducedMajorInMinorVersionNumber();
-    } else {
-      return "Chrome/" + GetMajorInMinorVersionNumber();
-    }
-  } else {
-    if (ShouldReduceUserAgentMinorVersion(user_agent_reduction)) {
-      return version_info::GetProductNameAndVersionForReducedUserAgent(
-          blink::features::kUserAgentFrozenBuildVersion.Get().data());
-    } else {
-      return version_info::GetProductNameAndVersionForUserAgent();
-    }
+    return "Chrome/" + (ShouldReduceUserAgentMinorVersion(user_agent_reduction)
+                            ? GetReducedMajorInMinorVersionNumber()
+                            : GetMajorInMinorVersionNumber());
   }
+  return ShouldReduceUserAgentMinorVersion(user_agent_reduction)
+             ? version_info::GetProductNameAndVersionForReducedUserAgent(
+                   blink::features::kUserAgentFrozenBuildVersion.Get())
+             : version_info::GetProductNameAndVersionForUserAgent();
 }
 
 // Internal function to handle return the full or "reduced" user agent string,
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 19e0619..adcf83a3 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -65,7 +65,6 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/widget/widget.h"
-#include "ui/views/window/caption_button_types.h"
 #include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/core/window_util.h"
 
@@ -242,22 +241,9 @@
 
   // Overridden from ash::CaptionButtonModel:
   bool IsVisible(views::CaptionButtonIcon icon) const override {
-    // TODO(b/276933044): Remove this workaround when ARC is uprevved.
-    if (icon == views::CaptionButtonIcon::CAPTION_BUTTON_ICON_FLOAT) {
-      return !(
-          visible_button_mask_ &
-          (1
-           << views::CaptionButtonIcon::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE));
-    }
     return visible_button_mask_ & (1 << icon);
   }
   bool IsEnabled(views::CaptionButtonIcon icon) const override {
-    if (icon == views::CaptionButtonIcon::CAPTION_BUTTON_ICON_FLOAT) {
-      return !(
-          enabled_button_mask_ &
-          (1
-           << views::CaptionButtonIcon::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE));
-    }
     return enabled_button_mask_ & (1 << icon);
   }
   bool InZoomMode() const override {
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index c382547..2cef495 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1394,22 +1394,12 @@
     shell_surface->SetFrameButtons(visible_buttons, 0);
     const chromeos::CaptionButtonModel* model = container->model();
     for (auto not_visible : kAllButtons) {
-      if (not_visible == views::CAPTION_BUTTON_ICON_FLOAT) {
-        // Float is dependent only on maximize/restore.
-        EXPECT_EQ(
-            !model->IsVisible(views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
-            model->IsVisible(views::CAPTION_BUTTON_ICON_FLOAT));
-      } else if (not_visible != visible) {
+      if (not_visible != visible) {
         EXPECT_FALSE(model->IsVisible(not_visible));
       }
     }
     EXPECT_TRUE(model->IsVisible(visible));
-    if (visible == views::CAPTION_BUTTON_ICON_FLOAT) {
-      EXPECT_EQ(!model->IsEnabled(views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
-                model->IsEnabled(views::CAPTION_BUTTON_ICON_FLOAT));
-    } else {
-      EXPECT_FALSE(model->IsEnabled(visible));
-    }
+    EXPECT_FALSE(model->IsEnabled(visible));
   }
 
   // Enable
@@ -1418,22 +1408,12 @@
     shell_surface->SetFrameButtons(kAllButtonMask, enabled_buttons);
     const chromeos::CaptionButtonModel* model = container->model();
     for (auto not_enabled : kAllButtons) {
-      if (not_enabled == views::CAPTION_BUTTON_ICON_FLOAT) {
-        // Float is dependent only on maximize/restore.
-        EXPECT_EQ(
-            !model->IsEnabled(views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
-            model->IsEnabled(views::CAPTION_BUTTON_ICON_FLOAT));
-      } else if (not_enabled != enabled) {
+      if (not_enabled != enabled) {
         EXPECT_FALSE(model->IsEnabled(not_enabled));
       }
     }
     EXPECT_TRUE(model->IsEnabled(enabled));
-    if (enabled == views::CAPTION_BUTTON_ICON_FLOAT) {
-      EXPECT_EQ(!model->IsVisible(views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
-                model->IsVisible(views::CAPTION_BUTTON_ICON_FLOAT));
-    } else {
-      EXPECT_TRUE(model->IsVisible(enabled));
-    }
+    EXPECT_TRUE(model->IsVisible(enabled));
   }
 
   // Zoom mode
@@ -2506,19 +2486,6 @@
   surface->Commit();
   EXPECT_FALSE(shell_surface->CanResize());
 
-  // Test that the float caption button is visible on unresizable apps.
-  EXPECT_TRUE(chromeos::wm::CanFloatWindow(
-      shell_surface->GetWidget()->GetNativeWindow()));
-  ash::NonClientFrameViewAsh* frame_view =
-      static_cast<ash::NonClientFrameViewAsh*>(
-          shell_surface->GetWidget()->non_client_view()->frame_view());
-  const chromeos::CaptionButtonModel* model =
-      static_cast<chromeos::HeaderView*>(frame_view->GetHeaderView())
-          ->caption_button_container()
-          ->model();
-  EXPECT_TRUE(model->IsVisible(views::CAPTION_BUTTON_ICON_FLOAT));
-  EXPECT_TRUE(model->IsEnabled(views::CAPTION_BUTTON_ICON_FLOAT));
-
   shell_surface->SetResizeLockType(
       ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE);
   surface->Commit();
diff --git a/components/feed/core/v2/view_demotion.h b/components/feed/core/v2/view_demotion.h
index f9b7248..8107441 100644
--- a/components/feed/core/v2/view_demotion.h
+++ b/components/feed/core/v2/view_demotion.h
@@ -4,6 +4,8 @@
 #ifndef COMPONENTS_FEED_CORE_V2_VIEW_DEMOTION_H_
 #define COMPONENTS_FEED_CORE_V2_VIEW_DEMOTION_H_
 
+#include <stdint.h>
+
 #include <iosfwd>
 #include <vector>
 #include "base/functional/callback_forward.h"
diff --git a/components/feedback/redaction_tool/pii_types.h b/components/feedback/redaction_tool/pii_types.h
index aa20143a..76792e30 100644
--- a/components/feedback/redaction_tool/pii_types.h
+++ b/components/feedback/redaction_tool/pii_types.h
@@ -70,7 +70,9 @@
   kEAP = 14,
   // Credit card numbers.
   kCreditCard = 15,
-  kMaxValue = kCreditCard,
+  // International Bank Account Numbers.
+  kIBAN = 16,
+  kMaxValue = kIBAN,
 };
 
 }  // namespace redaction
diff --git a/components/feedback/redaction_tool/redaction_tool.cc b/components/feedback/redaction_tool/redaction_tool.cc
index e352c32f..6d89294 100644
--- a/components/feedback/redaction_tool/redaction_tool.cc
+++ b/components/feedback/redaction_tool/redaction_tool.cc
@@ -37,6 +37,11 @@
 BASE_FEATURE(kEnableCreditCardRedaction,
              "EnableCreditCardRedaction",
              base::FEATURE_ENABLED_BY_DEFAULT);
+
+COMPONENT_EXPORT(REDACTION_TOOL)
+BASE_FEATURE(kEnableIbanRedaction,
+             "EnableIbanRedaction",
+             base::FEATURE_ENABLED_BY_DEFAULT);
 }  // namespace features
 
 namespace {
@@ -543,14 +548,11 @@
   UMA_HISTOGRAM_ENUMERATION("Feedback.RedactionTool.CreditCardMatch", step);
 }
 
-bool IsCreditCardRedactionEnabled() {
+bool IsFeatureEnabled(const base::Feature& feature) {
   return base::FeatureList::GetInstance()
-             ? base::FeatureList::IsEnabled(
-                   features::kEnableCreditCardRedaction)
-             : features::kEnableCreditCardRedaction.default_state ==
-                   base::FEATURE_ENABLED_BY_DEFAULT;
+             ? base::FeatureList::IsEnabled(feature)
+             : feature.default_state == base::FEATURE_ENABLED_BY_DEFAULT;
 }
-
 }  // namespace
 
 RedactionTool::RedactionTool(const char* const* first_party_extension_ids)
@@ -573,7 +575,7 @@
 
   std::map<PIIType, std::set<std::string>> detected;
 
-  if (IsCreditCardRedactionEnabled()) {
+  if (IsFeatureEnabled(features::kEnableCreditCardRedaction)) {
     RedactCreditCardNumbers(input, &detected);
   }
   RedactMACAddresses(input, &detected);
@@ -584,6 +586,9 @@
   // Do hashes last since they may appear in URLs and they also prevent us from
   // properly recognizing the Android storage paths.
   RedactHashes(input, &detected);
+  if (IsFeatureEnabled(features::kEnableIbanRedaction)) {
+    RedactIbans(input, &detected);
+  }
   return detected;
 }
 
@@ -605,7 +610,7 @@
   // well and the length could also match a MAC address. Since the credit card
   // check does additional validation against issuer length and Luhns checksum
   // the number of false positives should be lower when ordered like this.
-  if (IsCreditCardRedactionEnabled() &&
+  if (IsFeatureEnabled(features::kEnableCreditCardRedaction) &&
       pii_types_to_keep.find(PIIType::kCreditCard) == pii_types_to_keep.end()) {
     redacted = RedactCreditCardNumbers(std::move(redacted), nullptr);
   }
@@ -629,6 +634,10 @@
     // PIIType::kAndroidAppStoragePath and not PIIType::kStableIdentifier.
     redacted = RedactHashes(std::move(redacted), nullptr);
   }
+  if (IsFeatureEnabled(features::kEnableIbanRedaction) &&
+      pii_types_to_keep.find(PIIType::kIBAN) == pii_types_to_keep.end()) {
+    redacted = RedactIbans(std::move(redacted), nullptr);
+  }
   return redacted;
 }
 
@@ -917,6 +926,107 @@
   return result;
 }
 
+std::string RedactionTool::RedactIbans(
+    const std::string& input,
+    std::map<PIIType, std::set<std::string>>* detected) {
+  std::string result;
+  result.reserve(input.size());
+
+  RE2* iban_re = GetRegExp(
+      "((?:A[DELAOTZ]|B[AEFGHIJR]|C[HIMRVYZ]|D[EKOZ]|E[ES]|F[IOR]|G[BEILRT]|"
+      "H[RU]|I[ELRST]|JO|K[WZ]|L[BITUV]|M[CDEGKLRTUZ]|N[LO]|P[KLST]|QA|R[OS]|"
+      "S[AEIKMN]|T[NR]|UA|VG|XK)(?:\\d{2})[ -]?(?:[ "
+      "\\-A-Z0-9]){11,30})");
+
+  re2::StringPiece text(input);
+  re2::StringPiece skipped;
+  re2::StringPiece iban;
+  while (FindAndConsumeAndGetSkipped(&text, *iban_re, &skipped, &iban)) {
+    skipped.AppendToString(&result);
+    // Validation sequence as per [1].
+    //
+    // [1]
+    // https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
+
+    // Remove the separating characters.
+    std::string stripped;
+    base::RemoveChars(iban.as_string(), " -", &stripped);
+
+    if (const auto previous_iban = ibans_.find(stripped);
+        previous_iban != ibans_.end()) {
+      result += previous_iban->second;
+      continue;
+    }
+
+    // Since the logic later relies on the size of this string not changing use
+    // a lambda to initialize the constant.
+    const std::string numbers_only = [](base::StringPiece stripped) {
+      // Move the first 2 chars+digits to the back of the string.
+      constexpr size_t prefix_offset = 4;
+      std::string rearranged = std::string(stripped.substr(prefix_offset));
+      rearranged.append(stripped.substr(0, prefix_offset));
+
+      // Replace letters with two digits, where A = 10, B = 11, ..., Z = 35.
+      std::string tmp;
+      for (const char c : rearranged) {
+        if (base::IsAsciiDigit(c)) {
+          tmp.push_back(c);
+        } else {
+          const char based_char = c - 'A';
+          constexpr size_t iban_char_conversion_offset = 10;
+          tmp.append(base::NumberToString(static_cast<int>(based_char) +
+                                          iban_char_conversion_offset));
+        }
+      }
+      return tmp;
+    }(stripped);
+
+    // Calculate the remainder using chunks.
+    constexpr size_t chunk_size = 9;
+
+    std::string chunk;
+    chunk.reserve(chunk_size);
+
+    unsigned remainder = 0;
+
+    for (size_t remaining = numbers_only.size(); remaining > 0;) {
+      const size_t pos = numbers_only.size() - remaining;
+      const size_t next_chunk_size =
+          std::min(chunk_size - chunk.size(), remaining);
+
+      chunk.append(numbers_only.substr(pos, next_chunk_size));
+
+      const unsigned long chunk_number =
+          std::strtoul(chunk.c_str(), nullptr, 10);
+
+      remainder = chunk_number % 97;
+      chunk = base::NumberToString(remainder);
+
+      remaining -= next_chunk_size;
+    }
+
+    if (remainder != 1) {
+      iban.AppendToString(&result);
+      continue;
+    }
+
+    const auto& [it, success] = ibans_.emplace(
+        stripped, base::StrCat({"(IBAN: ",
+                                base::NumberToString(ibans_.size() + 1), ")"}));
+    result += it->second;
+
+    if (detected != nullptr) {
+      (*detected)[PIIType::kIBAN].insert(it->first);
+    }
+
+    RecordPIIRedactedHistogram(PIIType::kIBAN);
+  }
+
+  text.AppendToString(&result);
+
+  return result;
+}
+
 std::string RedactionTool::RedactAndKeepSelectedCustomPatterns(
     std::string input,
     const std::set<PIIType>& pii_types_to_keep) {
diff --git a/components/feedback/redaction_tool/redaction_tool.h b/components/feedback/redaction_tool/redaction_tool.h
index 5e734817..f35858b0 100644
--- a/components/feedback/redaction_tool/redaction_tool.h
+++ b/components/feedback/redaction_tool/redaction_tool.h
@@ -26,6 +26,9 @@
 namespace features {
 COMPONENT_EXPORT(REDACTION_TOOL)
 BASE_DECLARE_FEATURE(kEnableCreditCardRedaction);
+
+COMPONENT_EXPORT(REDACTION_TOOL)
+BASE_DECLARE_FEATURE(kEnableIbanRedaction);
 }  // namespace features
 
 struct CustomPatternWithAlias {
@@ -114,6 +117,11 @@
   std::string RedactCreditCardNumbers(
       const std::string& input,
       std::map<PIIType, std::set<std::string>>* detected);
+  // Redacts IBANs from |input| and returns the redacted string. Adds the
+  // redacted IBANs to |detected| under the |PIIType::kIBAN| if |detected| is
+  // not a nullptr.
+  std::string RedactIbans(const std::string& input,
+                          std::map<PIIType, std::set<std::string>>* detected);
 
   // Redacts PII sensitive data that matches |pattern| from |input| and returns
   // the redacted string. Keeps the PII data that belongs to PII type in
@@ -148,14 +156,14 @@
 
   // Map of MAC addresses discovered in redacted strings to redacted
   // representations. 11:22:33:44:55:66 gets redacted to
-  // [MAC OUI=11:22:33 IFACE=1], where the first three bytes (OUI) represent the
+  // (MAC OUI=11:22:33 IFACE=1), where the first three bytes (OUI) represent the
   // manufacturer. The IFACE value is incremented for each newly discovered MAC
   // address.
   std::map<std::string, std::string> mac_addresses_;
 
   // Map of hashes discovered in redacted strings to redacted representations.
   // Hexadecimal strings of length 32, 40 and 64 are considered to be hashes.
-  // 11223344556677889900aabbccddeeff gets redacted to <HASH:1122 1> where the
+  // 11223344556677889900aabbccddeeff gets redacted to (HASH:1122 1) where the
   // first 2 bytes of the hash are retained as-is and the value after that is
   // incremented for each newly discovered hash.
   std::map<std::string, std::string> hashes_;
@@ -164,6 +172,10 @@
   // the redacted representation.
   std::map<std::string, std::string> credit_cards_;
 
+  // Map of IBANs discovered in strings to their redacted representations. The
+  // key is stored without any separators.
+  std::map<std::string, std::string> ibans_;
+
   // Like MAC addresses, identifiers in custom patterns are redacted.
   // custom_patterns_with_context_["alias"] contains a map of original
   // identifier to redacted identifier for custom pattern with the given
diff --git a/components/feedback/redaction_tool/redaction_tool_unittest.cc b/components/feedback/redaction_tool/redaction_tool_unittest.cc
index e9043fed..e4c3e4fb 100644
--- a/components/feedback/redaction_tool/redaction_tool_unittest.cc
+++ b/components/feedback/redaction_tool/redaction_tool_unittest.cc
@@ -218,6 +218,12 @@
     // This is not a timestamp even though "ms" appears after the number.
     {"Use 4012888888881881 or moms creditcard",
      "Use (CREDITCARD: 1)or moms creditcard", PIIType::kCreditCard},
+    {"GB82 WEST 1234 5698 7654 32", "(IBAN: 1)", PIIType::kIBAN},
+    {"GB33BUKB20201555555555", "(IBAN: 2)", PIIType::kIBAN},
+    // Invalid check digits.
+    {"GB94BARC20201530093459", "GB94BARC20201530093459", PIIType::kNone},
+    // Country does not seem to support IBAN.
+    {"US64SVBKUS6S3300958879", "US64SVBKUS6S3300958879", PIIType::kNone},
 #if BUILDFLAG(IS_CHROMEOS_ASH)  // We only redact Android paths on Chrome OS.
     // Allowed android storage path.
     {"112K\t/home/root/deadbeef1234/android-data/data/system_de",
@@ -626,10 +632,10 @@
   }
   EXPECT_EQ(redaction_output, redactor_.Redact(redaction_input));
 
-  histogram_tester.ExpectBucketCount(kHistogramName, kRegexMatch, 11);
+  histogram_tester.ExpectBucketCount(kHistogramName, kRegexMatch, 12);
   histogram_tester.ExpectBucketCount(kHistogramName, kTimestamp, 2);
   histogram_tester.ExpectBucketCount(kHistogramName, kRepeatedChars, 1);
-  histogram_tester.ExpectBucketCount(kHistogramName, kDoesntValidate, 3);
+  histogram_tester.ExpectBucketCount(kHistogramName, kDoesntValidate, 4);
   histogram_tester.ExpectBucketCount(kHistogramName, kValidated, 5);
 }
 
@@ -792,6 +798,11 @@
          }},
         {PIIType::kCreditCard,
          {"4012888888881881", "5019717010103742", "5019717010103742787"}},
+    {
+      PIIType::kIBAN, {
+        "GB82WEST12345698765432", "GB33BUKB20201555555555"
+      }
+    }
   };
 
   EXPECT_EQ(pii_in_data, redactor_.Detect(redaction_input));
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index 82b69f1..728d07a 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -134,6 +134,7 @@
 #include "base/metrics/histogram_flattener.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_macros_local.h"
 #include "base/metrics/histogram_samples.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "base/process/process_handle.h"
@@ -186,6 +187,7 @@
   // base::HistogramFlattener:
   void RecordDelta(const base::HistogramBase& histogram,
                    const base::HistogramSamples& snapshot) override {
+    CHECK(histogram.HasFlags(base::HistogramBase::kUmaTargetedHistogramFlag));
     log_->RecordHistogramDelta(histogram.histogram_name(), snapshot);
   }
 
@@ -270,6 +272,10 @@
   DCHECK(client_);
   DCHECK(local_state_);
 
+  // Emit a local histogram, which should not be reported to servers. This is
+  // monitored from the serverside.
+  LOCAL_HISTOGRAM_BOOLEAN("UMA.LocalHistogram", true);
+
   bool create_logs_event_observer;
 #ifdef NDEBUG
   // For non-debug builds, we only create |logs_event_observer_| if the
@@ -320,6 +326,13 @@
           command_line->GetSwitchValuePath(switches::kExportUmaLogsToFile));
     }
   }
+
+  // Emit a local histogram, which should not be reported to servers. This is
+  // monitored from the serverside. Because this is emitted after closing the
+  // last log before shutdown, this sample should be retrieved by the persistent
+  // histograms system in a follow up session. This is to ensure independent
+  // logs do not include local histograms, a previously buggy behaviour.
+  LOCAL_HISTOGRAM_BOOLEAN("UMA.LocalHistogram", true);
 }
 
 void MetricsService::InitializeMetricsRecordingState() {
diff --git a/components/metrics/structured/BUILD.gn b/components/metrics/structured/BUILD.gn
index fbe7dbc..e5ed8592 100644
--- a/components/metrics/structured/BUILD.gn
+++ b/components/metrics/structured/BUILD.gn
@@ -24,6 +24,10 @@
     "structured_metrics_provider.h",
     "structured_metrics_recorder.cc",
     "structured_metrics_recorder.h",
+    "structured_metrics_scheduler.cc",
+    "structured_metrics_scheduler.h",
+    "structured_metrics_service.cc",
+    "structured_metrics_service.h",
   ]
 
   public_deps = [
@@ -239,6 +243,7 @@
     "persistent_proto_unittest.cc",
     "structured_metrics_provider_unittest.cc",
     "structured_metrics_recorder_unittest.cc",
+    "structured_metrics_service_unittest.cc",
   ]
 
   deps = [
@@ -249,7 +254,10 @@
     ":structured_metrics_validator",
     "//base",
     "//base/test:test_support",
+    "//components/metrics",
+    "//components/metrics:test_support",
     "//components/prefs",
+    "//components/prefs:test_support",
     "//testing/gtest",
   ]
 }
diff --git a/components/metrics/structured/external_metrics.cc b/components/metrics/structured/external_metrics.cc
index 32a86722..813fccba 100644
--- a/components/metrics/structured/external_metrics.cc
+++ b/components/metrics/structured/external_metrics.cc
@@ -21,8 +21,7 @@
 #include "components/metrics/structured/storage.pb.h"
 #include "components/metrics/structured/structured_metrics_features.h"
 
-namespace metrics {
-namespace structured {
+namespace metrics::structured {
 namespace {
 
 void FilterEvents(
@@ -80,7 +79,8 @@
 
 EventsProto ReadAndDeleteEvents(
     const base::FilePath& directory,
-    const base::flat_set<uint64_t>& disallowed_projects) {
+    const base::flat_set<uint64_t>& disallowed_projects,
+    bool recording_enabled) {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
   EventsProto result;
@@ -106,8 +106,8 @@
     // events.
     //
     // Events will be dropped in that case so that more recent events can be
-    // processed.
-    if (file_counter > GetFileLimitPerScan()) {
+    // processed. Events will be dropped if recording has been disabled.
+    if (!recording_enabled || file_counter > GetFileLimitPerScan()) {
       base::DeleteFile(path);
       continue;
     }
@@ -184,7 +184,7 @@
   task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&ReadAndDeleteEvents, events_directory_,
-                     disallowed_projects_),
+                     disallowed_projects_, recording_enabled_),
       base::BindOnce(callback_));
 }
 
@@ -209,5 +209,12 @@
   disallowed_projects_.insert(project_name_hash);
 }
 
-}  // namespace structured
-}  // namespace metrics
+void ExternalMetrics::EnableRecording() {
+  recording_enabled_ = true;
+}
+
+void ExternalMetrics::DisableRecording() {
+  recording_enabled_ = false;
+}
+
+}  // namespace metrics::structured
diff --git a/components/metrics/structured/external_metrics.h b/components/metrics/structured/external_metrics.h
index 6ef2ff5..cf3a859 100644
--- a/components/metrics/structured/external_metrics.h
+++ b/components/metrics/structured/external_metrics.h
@@ -13,8 +13,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 
-namespace metrics {
-namespace structured {
+namespace metrics::structured {
 
 class EventsProto;
 class ExternalMetricsTest;
@@ -42,6 +41,9 @@
   // Adds a project to the disallowed list for testing.
   void AddDisallowedProjectForTest(uint64_t project_name_hash);
 
+  void EnableRecording();
+  void DisableRecording();
+
  private:
   friend class ExternalMetricsTest;
 
@@ -52,6 +54,8 @@
   // Builds a cache of disallow projects from the Finch controlled variable.
   void CacheDisallowedProjectsSet();
 
+  bool recording_enabled_ = false;
+
   const base::FilePath events_directory_;
   const base::TimeDelta collection_interval_;
   MetricsCollectedCallback callback_;
@@ -64,7 +68,6 @@
   base::WeakPtrFactory<ExternalMetrics> weak_factory_{this};
 };
 
-}  // namespace structured
-}  // namespace metrics
+}  // namespace metrics::structured
 
 #endif  // COMPONENTS_METRICS_STRUCTURED_EXTERNAL_METRICS_H_
diff --git a/components/metrics/structured/external_metrics_unittest.cc b/components/metrics/structured/external_metrics_unittest.cc
index 3261236..177636b0 100644
--- a/components/metrics/structured/external_metrics_unittest.cc
+++ b/components/metrics/structured/external_metrics_unittest.cc
@@ -76,8 +76,15 @@
         temp_dir_.GetPath(), one_hour,
         base::BindRepeating(&ExternalMetricsTest::OnEventsCollected,
                             base::Unretained(this)));
+
+    // For most tests the recording needs to be enabled.
+    EnableRecording();
   }
 
+  void EnableRecording() { external_metrics_->EnableRecording(); }
+
+  void DisableRecording() { external_metrics_->DisableRecording(); }
+
   void CollectEvents() {
     external_metrics_->CollectEvents();
     Wait();
@@ -252,6 +259,24 @@
   ASSERT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
 }
 
+TEST_F(ExternalMetricsTest, DroppedEventsWhenDisabled) {
+  Init();
+  DisableRecording();
+
+  // Add 3 events with a project of 1 and 2.
+  WriteToDisk("first", MakeTestingProto({111}, 1));
+  WriteToDisk("second", MakeTestingProto({222}, 2));
+  WriteToDisk("third", MakeTestingProto({333}, 1));
+
+  CollectEvents();
+
+  // No events should have been collected.
+  ASSERT_EQ(proto_.value().uma_events().size(), 0);
+
+  // And the directory should be empty too.
+  ASSERT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
+}
+
 // TODO(crbug.com/1148168): Add a test for concurrent reading and writing here
 // once we know the specifics of how the lock in cros is performed.
 
diff --git a/components/metrics/structured/reporting/structured_metrics_reporting_service.cc b/components/metrics/structured/reporting/structured_metrics_reporting_service.cc
index 6ddc2aa..ff84cf28 100644
--- a/components/metrics/structured/reporting/structured_metrics_reporting_service.cc
+++ b/components/metrics/structured/reporting/structured_metrics_reporting_service.cc
@@ -28,10 +28,21 @@
                  client->GetUploadSigningKey(),
                  /* logs_event_manager=*/nullptr) {}
 
+void StructuredMetricsReportingService::StoreLog(
+    const std::string& serialized_log,
+    metrics::MetricsLogsEventManager::CreateReason reason) {
+  LogMetadata metadata;
+  log_store_.StoreLog(serialized_log, metadata, reason);
+}
+
 metrics::LogStore* StructuredMetricsReportingService::log_store() {
   return &log_store_;
 }
 
+void StructuredMetricsReportingService::Purge() {
+  log_store_.Purge();
+}
+
 // Getters for MetricsLogUploader parameters.
 GURL StructuredMetricsReportingService::GetUploadUrl() const {
   return client()->GetMetricsServerUrl();
@@ -55,4 +66,5 @@
     PrefRegistrySimple* registry) {
   registry->RegisterListPref(prefs::kLogStoreName);
 }
+
 }  // namespace metrics::structured::reporting
diff --git a/components/metrics/structured/reporting/structured_metrics_reporting_service.h b/components/metrics/structured/reporting/structured_metrics_reporting_service.h
index b6bf4e5..42c91ea9 100644
--- a/components/metrics/structured/reporting/structured_metrics_reporting_service.h
+++ b/components/metrics/structured/reporting/structured_metrics_reporting_service.h
@@ -31,12 +31,17 @@
                                     PrefService* local_state,
                                     const StorageLimits& storage_limits);
 
-  static void RegisterPrefs(PrefRegistrySimple* registry);
+  void StoreLog(const std::string& serialized_log,
+                metrics::MetricsLogsEventManager::CreateReason reason);
 
- private:
   // metrics::ReportingService:
   metrics::LogStore* log_store() override;
 
+  void Purge();
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
   // Getters for MetricsLogUploader parameters.
   GURL GetUploadUrl() const override;
   GURL GetInsecureUploadUrl() const override;
diff --git a/components/metrics/structured/structured_metrics_features.cc b/components/metrics/structured/structured_metrics_features.cc
index e7fdbd20..57e7564a 100644
--- a/components/metrics/structured/structured_metrics_features.cc
+++ b/components/metrics/structured/structured_metrics_features.cc
@@ -4,8 +4,6 @@
 
 #include "components/metrics/structured/structured_metrics_features.h"
 
-#include "base/metrics/field_trial_params.h"
-
 namespace metrics::structured {
 
 BASE_FEATURE(kStructuredMetrics,
@@ -37,6 +35,24 @@
 constexpr base::FeatureParam<std::string> kDisallowedProjectsParam{
     &kStructuredMetrics, "disabled_projects", ""};
 
+constexpr base::FeatureParam<int> kMinLogQueueCount{
+    &kEnabledStructuredMetricsService, "min_log_queue_count", 10};
+
+constexpr base::FeatureParam<int> kMinLogQueueSizeBytes{
+    &kEnabledStructuredMetricsService, "min_log_queue_size_bytes",
+    300 * 1024 * 1024  // 300 KiB
+};
+
+constexpr base::FeatureParam<int> kMaxLogSizeBytes{
+    &kEnabledStructuredMetricsService, "max_log_size_bytes",
+    1024 * 1024 * 1024  // 1 MiB
+};
+
+constexpr base::FeatureParam<int> kUploadTimeInSeconds{
+    &kEnabledStructuredMetricsService, "upload_time_in_seconds",
+    40 * 60  // 40 minutes
+};
+
 bool IsIndependentMetricsUploadEnabled() {
   return base::GetFieldTrialParamByFeatureAsBool(
       kStructuredMetrics, "enable_independent_metrics_upload", true);
@@ -54,4 +70,8 @@
   return kDisallowedProjectsParam.Get();
 }
 
+int GetUploadInterval() {
+  return kUploadTimeInSeconds.Get();
+}
+
 }  // namespace metrics::structured
diff --git a/components/metrics/structured/structured_metrics_features.h b/components/metrics/structured/structured_metrics_features.h
index bd921ff..1c55b220 100644
--- a/components/metrics/structured/structured_metrics_features.h
+++ b/components/metrics/structured/structured_metrics_features.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_FEATURES_H_
 
 #include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
 
 namespace metrics::structured {
 
@@ -24,6 +25,15 @@
 // provider.
 BASE_DECLARE_FEATURE(kEnabledStructuredMetricsService);
 
+// Controls the minimum number of logs to be stored.
+extern const base::FeatureParam<int> kMinLogQueueCount;
+
+// Controls the minimum size of all logs that can be stored in bytes.
+extern const base::FeatureParam<int> kMinLogQueueSizeBytes;
+
+// Controls the maximum size of a single log in bytes.
+extern const base::FeatureParam<int> kMaxLogSizeBytes;
+
 // TODO(crbug.com/1148168): This is a temporary switch to revert structured
 // metrics upload to its old behaviour. Old behaviour:
 // - all metrics are uploaded in the main UMA upload
@@ -50,6 +60,9 @@
 // recorded.
 std::string GetDisabledProjects();
 
+// Retrieves the Structured Metrics upload interval (defaults to 40 minutes).
+int GetUploadInterval();
+
 }  // namespace metrics::structured
 
 #endif  // COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_FEATURES_H_
diff --git a/components/metrics/structured/structured_metrics_recorder.cc b/components/metrics/structured/structured_metrics_recorder.cc
index dc8bd3f71..3644110 100644
--- a/components/metrics/structured/structured_metrics_recorder.cc
+++ b/components/metrics/structured/structured_metrics_recorder.cc
@@ -76,6 +76,9 @@
   DCHECK(base::CurrentUIThread::IsSet());
   // Enable recording only if structured metrics' feature flag is enabled.
   recording_enabled_ = base::FeatureList::IsEnabled(kStructuredMetrics);
+  if (external_metrics_.get() != nullptr) {
+    external_metrics_->EnableRecording();
+  }
   if (recording_enabled_) {
     CacheDisallowedProjectsSet();
   }
@@ -84,6 +87,9 @@
 void StructuredMetricsRecorder::DisableRecording() {
   DCHECK(base::CurrentUIThread::IsSet());
   recording_enabled_ = false;
+  if (external_metrics_.get() != nullptr) {
+    external_metrics_->DisableRecording();
+  }
   disallowed_projects_.clear();
 }
 
@@ -196,8 +202,8 @@
 
   // We do not handle multiprofile, instead initializing with the state stored
   // in the first logged-in user's cryptohome. So if a second profile is added
-  // we should ignore it. All init state beyond |InitState::kUninitialized| mean
-  // a profile has already been added.
+  // we should ignore it. All init state beyond |InitState::kUninitialized|
+  // mean a profile has already been added.
   if (init_state_ != InitState::kUninitialized) {
     return;
   }
@@ -227,6 +233,10 @@
           &StructuredMetricsRecorder::OnExternalMetricsCollected,
           weak_factory_.GetWeakPtr()));
 
+  if (recording_enabled_) {
+    external_metrics_->EnableRecording();
+  }
+
   // See DisableRecording for more information.
   if (purge_state_on_init_) {
     Purge();
@@ -244,8 +254,8 @@
     LogEventRecordingState(EventRecordingState::kRecordingDisabled);
     return;
   } else if (init_state_ != InitState::kInitialized) {
-    // If keys have not loaded yet, then hold the data in memory until the keys
-    // have been loaded.
+    // If keys have not loaded yet, then hold the data in memory until the
+    // keys have been loaded.
     LogEventRecordingState(EventRecordingState::kProviderUninitialized);
     RecordEventBeforeInitialization(event);
     return;
@@ -269,8 +279,8 @@
   DCHECK(device_key_data_->is_initialized());
 
   // |project_name_hash| could store its keys in either the profile or device
-  // key data, so check both. As they cannot both contain the same name hash, at
-  // most one will return a non-nullopt value.
+  // key data, so check both. As they cannot both contain the same name hash,
+  // at most one will return a non-nullopt value.
   absl::optional<int> profile_day =
       profile_key_data_->LastKeyRotation(project_name_hash);
   absl::optional<int> device_day =
@@ -419,9 +429,9 @@
     case IdScope::kPerDevice:
       // For event sequence, use the profile key for now to hash strings.
       //
-      // TODO(crbug/1399632): Event sequence is considered a structured metrics
-      // project. Once the client supports device/profile split of events like
-      // structured metrics, remove this.
+      // TODO(crbug/1399632): Event sequence is considered a structured
+      // metrics project. Once the client supports device/profile split of
+      // events like structured metrics, remove this.
       if (project_validator->event_type() ==
           StructuredEventProto_EventType_SEQUENCE) {
         key_data = profile_key_data_.get();
@@ -453,8 +463,8 @@
       break;
   }
 
-  // Set the event type. Do this with a switch statement to catch when the event
-  // type is UNKNOWN or uninitialized.
+  // Set the event type. Do this with a switch statement to catch when the
+  // event type is UNKNOWN or uninitialized.
   switch (project_validator->event_type()) {
     case StructuredEventProto_EventType_REGULAR:
     case StructuredEventProto_EventType_RAW_STRING:
@@ -473,14 +483,14 @@
     const std::string& metric_name = metric.first;
     const Event::MetricValue& metric_value = metric.second;
 
-    // Validate that both name and metric type are valid structured metrics. If
-    // a metric is invalid, then ignore the metric so that other valid metrics
-    // are added to the proto.
+    // Validate that both name and metric type are valid structured metrics.
+    // If a metric is invalid, then ignore the metric so that other valid
+    // metrics are added to the proto.
     absl::optional<EventValidator::MetricMetadata> metadata =
         event_validator->GetMetricMetadata(metric_name);
 
-    // Checks that the metrics defined are valid. If not valid, then the metric
-    // will be ignored.
+    // Checks that the metrics defined are valid. If not valid, then the
+    // metric will be ignored.
     bool is_valid =
         metadata.has_value() && metadata->metric_type == metric_value.type;
     DCHECK(is_valid);
diff --git a/components/metrics/structured/structured_metrics_recorder.h b/components/metrics/structured/structured_metrics_recorder.h
index 23eb33b..e77469e 100644
--- a/components/metrics/structured/structured_metrics_recorder.h
+++ b/components/metrics/structured/structured_metrics_recorder.h
@@ -95,6 +95,7 @@
   friend class StructuredMetricsRecorderHwidTest;
   friend class TestStructuredMetricsRecorder;
   friend class TestStructuredMetricsProvider;
+  friend class StructuredMetricsServiceTest;
 
   // files that are asynchronously read from disk at startup. When all files
   // have been read, the provider has been initialized.
diff --git a/components/metrics/structured/structured_metrics_recorder_unittest.cc b/components/metrics/structured/structured_metrics_recorder_unittest.cc
index e2ca4c81..7796b82 100644
--- a/components/metrics/structured/structured_metrics_recorder_unittest.cc
+++ b/components/metrics/structured/structured_metrics_recorder_unittest.cc
@@ -860,8 +860,8 @@
   recorder_ = std::make_unique<TestStructuredMetricsRecorder>(
       system_profile_provider_.get());
   OnProfileAdded(TempDirPath());
-  OnRecordingEnabled();
   SetExternalMetricsDirForTest(events_dir);
+  OnRecordingEnabled();
   task_environment_.AdvanceClock(base::Hours(10));
   Wait();
   EXPECT_EQ(GetUMAEventMetrics().events_size(), 3);
@@ -880,8 +880,8 @@
   recorder_ = std::make_unique<TestStructuredMetricsRecorder>(
       system_profile_provider_.get());
   OnProfileAdded(TempDirPath());
-  OnRecordingDisabled();
   SetExternalMetricsDirForTest(events_dir);
+  OnRecordingDisabled();
   task_environment_.AdvanceClock(base::Hours(10));
   Wait();
   EXPECT_EQ(GetUMAEventMetrics().events_size(), 0);
diff --git a/components/metrics/structured/structured_metrics_scheduler.cc b/components/metrics/structured/structured_metrics_scheduler.cc
new file mode 100644
index 0000000..fed3374
--- /dev/null
+++ b/components/metrics/structured/structured_metrics_scheduler.cc
@@ -0,0 +1,18 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/structured/structured_metrics_scheduler.h"
+
+namespace metrics::structured {
+StructuredMetricsScheduler::StructuredMetricsScheduler(
+    const base::RepeatingClosure& rotation_callback,
+    const base::RepeatingCallback<base::TimeDelta(void)>& interval_callback,
+    bool fast_startup_for_testing)
+    : metrics::MetricsRotationScheduler(rotation_callback,
+                                        interval_callback,
+                                        fast_startup_for_testing) {}
+
+StructuredMetricsScheduler::~StructuredMetricsScheduler() = default;
+
+}  // namespace metrics::structured
diff --git a/components/metrics/structured/structured_metrics_scheduler.h b/components/metrics/structured/structured_metrics_scheduler.h
new file mode 100644
index 0000000..e255aa2
--- /dev/null
+++ b/components/metrics/structured/structured_metrics_scheduler.h
@@ -0,0 +1,34 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SCHEDULER_H_
+#define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SCHEDULER_H_
+
+#include "base/time/time.h"
+#include "components/metrics/metrics_rotation_scheduler.h"
+
+namespace metrics::structured {
+
+// Schedulers a periodic rotation of logs and initiates a log upload to the
+// reporting service.
+class StructuredMetricsScheduler : public metrics::MetricsRotationScheduler {
+ public:
+  // Creates StructuredMetricsScheduler object with the given
+  // |rotation_callback| callback to call when log rotation should happen and
+  // |interval_callback| to determine the interval between rotations in steady
+  // state.
+  StructuredMetricsScheduler(
+      const base::RepeatingClosure& rotation_callback,
+      const base::RepeatingCallback<base::TimeDelta(void)>& interval_callback,
+      bool fast_startup_for_testing);
+
+  StructuredMetricsScheduler(const StructuredMetricsScheduler&) = delete;
+  StructuredMetricsScheduler& operator=(const StructuredMetricsScheduler&) =
+      delete;
+
+  ~StructuredMetricsScheduler() override;
+};
+}  // namespace metrics::structured
+
+#endif  // COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SCHEDULER_H_
diff --git a/components/metrics/structured/structured_metrics_service.cc b/components/metrics/structured/structured_metrics_service.cc
new file mode 100644
index 0000000..1b7ef09
--- /dev/null
+++ b/components/metrics/structured/structured_metrics_service.cc
@@ -0,0 +1,158 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/structured/structured_metrics_service.h"
+
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/structured/reporting/structured_metrics_reporting_service.h"
+#include "components/metrics/structured/structured_metrics_features.h"
+
+namespace metrics::structured {
+
+StructuredMetricsService::StructuredMetricsService(
+    base::raw_ptr<MetricsProvider> system_profile_provider,
+    MetricsServiceClient* client,
+    PrefService* local_state)
+    : StructuredMetricsService(client,
+                               local_state,
+                               std::make_unique<StructuredMetricsRecorder>(
+                                   system_profile_provider)) {}
+
+StructuredMetricsService::~StructuredMetricsService() = default;
+
+void StructuredMetricsService::EnableRecording() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!initialize_complete_) {
+    Initialize();
+  }
+  recorder_->EnableRecording();
+}
+
+void StructuredMetricsService::DisableRecording() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  recorder_->DisableRecording();
+}
+
+void StructuredMetricsService::EnableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!reporting_active()) {
+    scheduler_->Start();
+  }
+  reporting_service_->EnableReporting();
+}
+
+void StructuredMetricsService::DisableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  reporting_service_->DisableReporting();
+  scheduler_->Stop();
+}
+
+void StructuredMetricsService::Flush(
+    metrics::MetricsLogsEventManager::CreateReason reason) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  BuildAndStoreLog(reason);
+  reporting_service_->log_store()->TrimAndPersistUnsentLogs(true);
+}
+
+void StructuredMetricsService::Purge() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  recorder_->Purge();
+  reporting_service_->Purge();
+}
+
+StructuredMetricsService::StructuredMetricsService(
+    MetricsServiceClient* client,
+    PrefService* local_state,
+    std::unique_ptr<StructuredMetricsRecorder> recorder)
+    : recorder_(std::move(recorder)), client_(client) {
+  DCHECK(client);
+  DCHECK(local_state);
+
+  // Setup the reporting service.
+  const reporting::StorageLimits storage_limits = GetLogStoreLimits();
+
+  reporting_service_ =
+      std::make_unique<reporting::StructuredMetricsReportingService>(
+          client, local_state, storage_limits);
+
+  reporting_service_->Initialize();
+
+  // Setup the log rotation scheduler.
+  base::RepeatingClosure rotate_callback = base::BindRepeating(
+      &StructuredMetricsService::RotateLogsAndSend, weak_factory_.GetWeakPtr());
+  base::RepeatingCallback<base::TimeDelta(void)> get_upload_interval_callback =
+      base::BindRepeating(&StructuredMetricsService::GetUploadTimeInterval,
+                          base::Unretained(this));
+
+  const bool fast_startup_for_test = client->ShouldStartUpFastForTesting();
+  scheduler_ = std::make_unique<StructuredMetricsScheduler>(
+      rotate_callback, get_upload_interval_callback, fast_startup_for_test);
+}
+
+base::TimeDelta StructuredMetricsService::GetUploadTimeInterval() {
+  return base::Seconds(GetUploadInterval());
+}
+
+void StructuredMetricsService::RotateLogsAndSend() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (recorder_->events()->non_uma_events_size() == 0) {
+    return;
+  }
+
+  if (!reporting_service_->log_store()->has_unsent_logs()) {
+    BuildAndStoreLog(metrics::MetricsLogsEventManager::CreateReason::kPeriodic);
+  }
+  reporting_service_->Start();
+  scheduler_->RotationFinished();
+}
+
+void StructuredMetricsService::BuildAndStoreLog(
+    metrics::MetricsLogsEventManager::CreateReason reason) {
+  ChromeUserMetricsExtension uma_proto;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  InitializeUmaProto(uma_proto);
+  recorder_->ProvideEventMetrics(uma_proto);
+  const std::string serialized_log = SerializeLog(uma_proto);
+  reporting_service_->StoreLog(serialized_log, reason);
+}
+
+void StructuredMetricsService::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!initialize_complete_);
+
+  initialize_complete_ = true;
+
+  // Notifies the scheduler that it is ready to start creating logs.
+  scheduler_->InitTaskComplete();
+}
+
+void StructuredMetricsService::InitializeUmaProto(
+    ChromeUserMetricsExtension& uma_proto) {
+  const int32_t product = client_->GetProduct();
+  if (product != uma_proto.product()) {
+    uma_proto.set_product(product);
+  }
+}
+
+// static:
+std::string StructuredMetricsService::SerializeLog(
+    const ChromeUserMetricsExtension& uma_proto) {
+  std::string log_data;
+  DCHECK(uma_proto.SerializeToString(&log_data));
+  return log_data;
+}
+
+void StructuredMetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
+  reporting::StructuredMetricsReportingService::RegisterPrefs(registry);
+}
+
+reporting::StorageLimits StructuredMetricsService::GetLogStoreLimits() {
+  return reporting::StorageLimits{
+      .min_log_queue_count = static_cast<size_t>(kMinLogQueueCount.Get()),
+      .min_log_queue_size = static_cast<size_t>(kMinLogQueueSizeBytes.Get()),
+      .max_log_size = static_cast<size_t>(kMaxLogSizeBytes.Get()),
+  };
+}
+
+}  // namespace metrics::structured
diff --git a/components/metrics/structured/structured_metrics_service.h b/components/metrics/structured/structured_metrics_service.h
new file mode 100644
index 0000000..cb60d4f
--- /dev/null
+++ b/components/metrics/structured/structured_metrics_service.h
@@ -0,0 +1,105 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SERVICE_H_
+#define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SERVICE_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "components/metrics/structured/reporting/structured_metrics_reporting_service.h"
+#include "components/metrics/structured/structured_metrics_recorder.h"
+#include "components/metrics/structured/structured_metrics_scheduler.h"
+
+FORWARD_DECLARE_TEST(StructuredMetricsServiceTest, RotateLogs);
+
+class PrefRegistrySimple;
+
+namespace metrics::structured {
+
+// The Structured Metrics Service is responsible for collecting and uploading
+// Structured Metric events.
+class StructuredMetricsService final {
+ public:
+  StructuredMetricsService(
+      base::raw_ptr<MetricsProvider> system_profile_provider,
+      MetricsServiceClient* client,
+      PrefService* local_state);
+
+  ~StructuredMetricsService();
+
+  StructuredMetricsService(const StructuredMetricsService&) = delete;
+  StructuredMetricsService& operator=(StructuredMetricsService&) = delete;
+
+  void EnableRecording();
+  void DisableRecording();
+
+  void EnableReporting();
+  void DisableReporting();
+
+  // Flushes any event currently in the recorder to prefs.
+  void Flush(metrics::MetricsLogsEventManager::CreateReason reason);
+
+  // Clears all event and log data.
+  void Purge();
+
+  bool reporting_active() const {
+    return reporting_service_->reporting_active();
+  }
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  friend class StructuredMetricsServiceTest;
+  FRIEND_TEST_ALL_PREFIXES(StructuredMetricsServiceTest, RotateLogs);
+
+  StructuredMetricsService(MetricsServiceClient* client,
+                           PrefService* local_state,
+                           std::unique_ptr<StructuredMetricsRecorder> recorder);
+
+  // Callback function to get the upload interval.
+  base::TimeDelta GetUploadTimeInterval();
+
+  // Creates a new log and sends any currently stages logs.
+  void RotateLogsAndSend();
+
+  // Collects the events from the recorder and builds a new log.
+  void BuildAndStoreLog(metrics::MetricsLogsEventManager::CreateReason reason);
+
+  // Starts the initialization process for |this|.
+  void Initialize();
+
+  // Fills out the UMA proto to be sent.
+  void InitializeUmaProto(ChromeUserMetricsExtension& uma_proto);
+
+  // Helper function to serialize a ChromeUserMetricsExtension proto.
+  static std::string SerializeLog(const ChromeUserMetricsExtension& uma_proto);
+
+  // Retrieves the storage parameters to control the reporting service.
+  static reporting::StorageLimits GetLogStoreLimits();
+
+  // Manages on-device recording of events.
+  std::unique_ptr<StructuredMetricsRecorder> recorder_;
+
+  // Service for uploading completed logs.
+  std::unique_ptr<reporting::StructuredMetricsReportingService>
+      reporting_service_;
+
+  // Schedules when logs will be created.
+  std::unique_ptr<StructuredMetricsScheduler> scheduler_;
+
+  // Marks that initialization has completed.
+  bool initialize_complete_ = false;
+
+  // The metrics client |this| is service is associated.
+  base::raw_ptr<MetricsServiceClient> client_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<StructuredMetricsService> weak_factory_{this};
+};
+
+}  // namespace metrics::structured
+
+#endif  // COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_SERVICE_H_
diff --git a/components/metrics/structured/structured_metrics_service_unittest.cc b/components/metrics/structured/structured_metrics_service_unittest.cc
new file mode 100644
index 0000000..4a328a97
--- /dev/null
+++ b/components/metrics/structured/structured_metrics_service_unittest.cc
@@ -0,0 +1,264 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/structured/structured_metrics_service.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "components/metrics/log_decoder.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/structured/recorder.h"
+#include "components/metrics/structured/reporting/structured_metrics_reporting_service.h"
+#include "components/metrics/structured/structured_events.h"
+#include "components/metrics/structured/structured_metrics_features.h"
+#include "components/metrics/structured/structured_metrics_prefs.h"
+#include "components/metrics/structured/structured_metrics_recorder.h"
+#include "components/metrics/test/test_metrics_service_client.h"
+#include "components/metrics/unsent_log_store.h"
+#include "components/metrics/unsent_log_store_metrics_impl.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics::structured {
+namespace {
+
+using events::v2::test_project_one::TestEventOne;
+using events::v2::test_project_six::TestEventSeven;
+
+// The name hash of "TestProjectOne".
+constexpr uint64_t kProjectOneHash = UINT64_C(16881314472396226433);
+// The name hash of "TestProjectThree".
+constexpr uint64_t kProjectThreeHash = UINT64_C(10860358748803291132);
+
+class TestRecorder : public StructuredMetricsClient::RecordingDelegate {
+ public:
+  TestRecorder() = default;
+  TestRecorder(const TestRecorder& recorder) = delete;
+  TestRecorder& operator=(const TestRecorder& recorder) = delete;
+  ~TestRecorder() override = default;
+
+  void RecordEvent(Event&& event) override {
+    Recorder::GetInstance()->RecordEvent(std::move(event));
+  }
+
+  bool IsReadyToRecord() const override { return true; }
+};
+
+class TestSystemProfileProvider : public metrics::MetricsProvider {
+ public:
+  TestSystemProfileProvider() = default;
+  TestSystemProfileProvider(const TestSystemProfileProvider& recorder) = delete;
+  TestSystemProfileProvider& operator=(
+      const TestSystemProfileProvider& recorder) = delete;
+  ~TestSystemProfileProvider() override = default;
+
+  void ProvideSystemProfileMetrics(
+      metrics::SystemProfileProto* proto) override {}
+};
+
+}  // namespace
+
+class StructuredMetricsServiceTest : public testing::Test {
+ public:
+  StructuredMetricsServiceTest() {
+    reporting::StructuredMetricsReportingService::RegisterPrefs(
+        prefs_.registry());
+
+    Recorder::GetInstance()->SetUiTaskRunner(
+        task_environment_.GetMainThreadTaskRunner());
+    StructuredMetricsClient::Get()->SetDelegate(&test_recorder_);
+  }
+
+  void SetUp() override {
+    feature_list_.InitWithFeatures({kEnabledStructuredMetricsService}, {});
+
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    WriteTestingDeviceKeys();
+
+    system_profile_provider_ = std::make_unique<TestSystemProfileProvider>();
+
+    WriteTestingProfileKeys();
+  }
+
+  void Init() {
+    auto recorder = std::unique_ptr<StructuredMetricsRecorder>(
+        new StructuredMetricsRecorder(DeviceKeyFilePath(), base::Seconds(0),
+                                      system_profile_provider_.get()));
+    recorder->OnProfileAdded(temp_dir_.GetPath());
+    service_ = std::unique_ptr<StructuredMetricsService>(
+        new StructuredMetricsService(&client_, &prefs_, std::move(recorder)));
+    Wait();
+  }
+
+  void EnableRecording() { service_->EnableRecording(); }
+  void EnableReporting() { service_->EnableReporting(); }
+
+  void DisableRecording() { service_->DisableRecording(); }
+  void DisableReporting() { service_->DisableReporting(); }
+
+  base::FilePath ProfileKeyFilePath() {
+    return temp_dir_.GetPath().Append("structured_metrics").Append("keys");
+  }
+
+  base::FilePath DeviceKeyFilePath() {
+    return temp_dir_.GetPath()
+        .Append("structured_metrics")
+        .Append("device_keys");
+  }
+
+  void WriteTestingProfileKeys() {
+    const int today = (base::Time::Now() - base::Time::UnixEpoch()).InDays();
+
+    KeyDataProto proto;
+    KeyProto& key_one = (*proto.mutable_keys())[kProjectOneHash];
+    key_one.set_key("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+    key_one.set_last_rotation(today);
+    key_one.set_rotation_period(90);
+
+    KeyProto& key_three = (*proto.mutable_keys())[kProjectThreeHash];
+    key_three.set_key("cccccccccccccccccccccccccccccccc");
+    key_three.set_last_rotation(today);
+    key_three.set_rotation_period(90);
+
+    base::CreateDirectory(ProfileKeyFilePath().DirName());
+    ASSERT_TRUE(
+        base::WriteFile(ProfileKeyFilePath(), proto.SerializeAsString()));
+    Wait();
+  }
+
+  void WriteTestingDeviceKeys() {
+    base::CreateDirectory(DeviceKeyFilePath().DirName());
+    ASSERT_TRUE(base::WriteFile(DeviceKeyFilePath(),
+                                KeyDataProto().SerializeAsString()));
+    Wait();
+  }
+
+  int GetPersistedLogCount() {
+    return prefs_.GetList(prefs::kLogStoreName).size();
+  }
+
+  ChromeUserMetricsExtension GetPersistedLog() {
+    EXPECT_THAT(GetPersistedLogCount(), 1);
+    metrics::UnsentLogStore result_unsent_log_store(
+        std::make_unique<UnsentLogStoreMetricsImpl>(), &prefs_,
+        prefs::kLogStoreName, /*metadata_pref_name=*/nullptr,
+        /*min_log_count=*/3, /*min_log_bytes=*/1000,
+        /*max_log_size=*/0,
+        /*signing_key=*/std::string(),
+        /*logs_event_manager=*/nullptr);
+
+    result_unsent_log_store.LoadPersistedUnsentLogs();
+    result_unsent_log_store.StageNextLog();
+
+    ChromeUserMetricsExtension uma_proto;
+    EXPECT_TRUE(metrics::DecodeLogDataToProto(
+        result_unsent_log_store.staged_log(), &uma_proto));
+    return uma_proto;
+  }
+
+  StructuredMetricsService& service() { return *service_.get(); }
+
+  void Wait() { task_environment_.RunUntilIdle(); }
+
+  void AdvanceClock(int hours) {
+    task_environment_.AdvanceClock(base::Hours(hours));
+  }
+
+ protected:
+  std::unique_ptr<StructuredMetricsService> service_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  metrics::TestMetricsServiceClient client_;
+  TestingPrefServiceSimple prefs_;
+
+  std::unique_ptr<TestSystemProfileProvider> system_profile_provider_;
+  TestRecorder test_recorder_;
+  base::ScopedTempDir temp_dir_;
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI,
+      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED,
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(StructuredMetricsServiceTest, PurgeInMemory) {
+  Init();
+
+  EnableRecording();
+  EnableReporting();
+
+  TestEventOne().SetTestMetricTwo(1).Record();
+  TestEventSeven().SetTestMetricSeven(1.0).Record();
+
+  service_->Purge();
+  service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
+
+  const auto uma_proto = GetPersistedLog();
+  EXPECT_THAT(uma_proto.structured_data().events().size(), 0);
+}
+
+TEST_F(StructuredMetricsServiceTest, PurgePersisted) {
+  Init();
+
+  EnableRecording();
+  EnableReporting();
+
+  TestEventOne().SetTestMetricTwo(1).Record();
+  TestEventSeven().SetTestMetricSeven(1.0).Record();
+
+  service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
+
+  service_->Purge();
+
+  // Need to make sure there is a log to read.
+  service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
+
+  const auto uma_proto = GetPersistedLog();
+  EXPECT_THAT(uma_proto.structured_data().events().size(), 0);
+}
+
+TEST_F(StructuredMetricsServiceTest, RotateLogs) {
+  Init();
+
+  EnableRecording();
+  EnableReporting();
+
+  TestEventOne().SetTestMetricTwo(1).Record();
+  TestEventSeven().SetTestMetricSeven(1).Record();
+
+  service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
+
+  const auto uma_proto = GetPersistedLog();
+  EXPECT_THAT(uma_proto.structured_data().events().size(), 2);
+}
+
+TEST_F(StructuredMetricsServiceTest, DoesNotRecordWhenRecordingDisabled) {
+  Init();
+  EnableRecording();
+  EnableReporting();
+
+  TestEventOne().SetTestMetricTwo(1).Record();
+  TestEventSeven().SetTestMetricSeven(1).Record();
+
+  DisableRecording();
+
+  TestEventOne().SetTestMetricTwo(1).Record();
+  TestEventSeven().SetTestMetricSeven(1).Record();
+
+  EnableRecording();
+
+  service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown);
+
+  const auto uma_proto = GetPersistedLog();
+  EXPECT_THAT(uma_proto.structured_data().events().size(), 2);
+}
+
+}  // namespace metrics::structured
diff --git a/components/minidump_uploader/rewrite_minidumps_as_mimes.cc b/components/minidump_uploader/rewrite_minidumps_as_mimes.cc
index d749d12..04cc43d8 100644
--- a/components/minidump_uploader/rewrite_minidumps_as_mimes.cc
+++ b/components/minidump_uploader/rewrite_minidumps_as_mimes.cc
@@ -230,9 +230,8 @@
   crashpad::HTTPMultipartBuilder builder;
   builder.SetFormData("version", version_number);
   builder.SetFormData("product", "Chrome_Android");
-  std::string channel =
-      version_info::GetChannelString(version_info::android::GetChannel());
-  builder.SetFormData("channel", channel);
+  builder.SetFormData("channel", version_info::GetChannelString(
+                                     version_info::android::GetChannel()));
   if (!build_id.empty()) {
     builder.SetFormData("elf_build_id", build_id);
   }
diff --git a/components/optimization_guide/core/prediction_model_store.cc b/components/optimization_guide/core/prediction_model_store.cc
index 2f87e2d..d0b4e0b 100644
--- a/components/optimization_guide/core/prediction_model_store.cc
+++ b/components/optimization_guide/core/prediction_model_store.cc
@@ -396,12 +396,18 @@
   DCHECK(local_state_);
   for (const auto& expired_model_dir :
        ModelStoreMetadataEntryUpdater::PurgeAllInactiveMetadata(local_state_)) {
-    DCHECK(!expired_model_dir.IsAbsolute());
+    // Backward compatibility: Model dirs were absolute in the earlier versions,
+    // and it was only in experiment. The latest versions use relative paths.
+    DCHECK(!expired_model_dir.IsAbsolute() ||
+           base_store_dir_.IsParent(expired_model_dir));
+    base::FilePath absolute_model_dir =
+        expired_model_dir.IsAbsolute()
+            ? expired_model_dir
+            : base_store_dir_.Append(expired_model_dir);
     // This is called at startup. So no need to schedule the deletion of the
     // model dirs, and instead can be deleted immediately.
     background_task_runner_->PostTask(
-        FROM_HERE, base::GetDeletePathRecursivelyCallback(
-                       base_store_dir_.Append(expired_model_dir)));
+        FROM_HERE, base::GetDeletePathRecursivelyCallback(absolute_model_dir));
   }
 }
 
diff --git a/components/permissions/notification_permission_review_service.cc b/components/permissions/notification_permission_review_service.cc
index c97de29..5220c75 100644
--- a/components/permissions/notification_permission_review_service.cc
+++ b/components/permissions/notification_permission_review_service.cc
@@ -99,11 +99,32 @@
 
 NotificationPermissionsReviewService::NotificationPermissionsReviewService(
     HostContentSettingsMap* hcsm)
-    : hcsm_(hcsm) {}
+    : hcsm_(hcsm) {
+  content_settings_observation_.Observe(hcsm);
+}
 
 NotificationPermissionsReviewService::~NotificationPermissionsReviewService() =
     default;
 
+void NotificationPermissionsReviewService::OnContentSettingChanged(
+    const ContentSettingsPattern& primary_pattern,
+    const ContentSettingsPattern& secondary_pattern,
+    ContentSettingsTypeSet content_type_set) {
+  if (!content_type_set.Contains(ContentSettingsType::NOTIFICATIONS)) {
+    return;
+  }
+  // Sites on the notification permission review blocklist are sites where the
+  // notification permission is ALLOW and the user has indicated the site should
+  // not be suggested again in the module for revocation. A change in the
+  // notification permission for such a site (e.g. by the user or by
+  // resetting permissions) is considered to be a signal that the site should
+  // not longer be ignored, in case the permission is allowed again in the
+  // future. Setting ContentSetting to ALLOW when it already is ALLOW will not
+  // trigger this function.
+  RemovePatternFromNotificationPermissionReviewBlocklist(primary_pattern,
+                                                         secondary_pattern);
+}
+
 void NotificationPermissionsReviewService::Shutdown() {}
 
 std::vector<NotificationPermissions>
diff --git a/components/permissions/notification_permission_review_service.h b/components/permissions/notification_permission_review_service.h
index 47edcc2..0120049 100644
--- a/components/permissions/notification_permission_review_service.h
+++ b/components/permissions/notification_permission_review_service.h
@@ -7,6 +7,8 @@
 
 #include <vector>
 
+#include "base/scoped_observation.h"
+#include "components/content_settings/core/browser/content_settings_observer.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -27,7 +29,8 @@
 // This class provides data for "Review Notification Permissions" module in site
 // settings notification page. This module shows the domains that send a lot of
 // notification, but have low engagement.
-class NotificationPermissionsReviewService : public KeyedService {
+class NotificationPermissionsReviewService : public KeyedService,
+                                             public content_settings::Observer {
  public:
   explicit NotificationPermissionsReviewService(HostContentSettingsMap* hcsm);
 
@@ -38,6 +41,12 @@
 
   ~NotificationPermissionsReviewService() override;
 
+  // content_settings::Observer implementation.
+  void OnContentSettingChanged(
+      const ContentSettingsPattern& primary_pattern,
+      const ContentSettingsPattern& secondary_pattern,
+      ContentSettingsTypeSet content_type_set) override;
+
   // KeyedService implementation.
   void Shutdown() override;
 
@@ -60,6 +69,10 @@
  private:
   // Used to update the notification permissions per URL.
   const scoped_refptr<HostContentSettingsMap> hcsm_;
+
+  // Observer to watch for content settings changed.
+  base::ScopedObservation<HostContentSettingsMap, content_settings::Observer>
+      content_settings_observation_{this};
 };
 
 }  // namespace permissions
diff --git a/components/permissions/unused_site_permissions_service.cc b/components/permissions/unused_site_permissions_service.cc
index 1185ec4..5ee0bac2 100644
--- a/components/permissions/unused_site_permissions_service.cc
+++ b/components/permissions/unused_site_permissions_service.cc
@@ -138,10 +138,36 @@
     HostContentSettingsMap* hcsm)
     : hcsm_(hcsm), clock_(base::DefaultClock::GetInstance()) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  content_settings_observation_.Observe(hcsm);
 }
 
 UnusedSitePermissionsService::~UnusedSitePermissionsService() = default;
 
+void UnusedSitePermissionsService::OnContentSettingChanged(
+    const ContentSettingsPattern& primary_pattern,
+    const ContentSettingsPattern& secondary_pattern,
+    ContentSettingsTypeSet content_type_set) {
+  if (content_type_set.Contains(
+          ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS)) {
+    return;
+  }
+
+  // When permissions change for a pattern it is either (1) through resetting
+  // permissions, e.g. in page info or site settings, (2) user modifying
+  // permissions manually, or (3) through the auto-revocation that this module
+  // performs. In (1) and (2) the pattern should no longer be shown to the user.
+  // 1: After resetting permissions the browser state should be in a
+  //    state as if the permission had never been granted.
+  // 2: The user is actively engaging with the permissions of the site, so it is
+  //    no longer considered an unused site for the purposes of this module.
+  //    This includes the case where unrelated permissions to the revoked ones
+  //    are changed.
+  // 3: Current logic ensures this does not happen for sites that already have
+  //    revoked permissions. This module revokes permissions in an all-or-none
+  //    fashion.
+  DeletePatternFromRevokedPermissionList(primary_pattern, secondary_pattern);
+}
+
 void UnusedSitePermissionsService::Shutdown() {
   update_timer_.Stop();
 }
@@ -193,9 +219,8 @@
   IgnoreOriginForAutoRevocation(origin);
 
   // Remove origin from revoked permissions list.
-  hcsm_->SetWebsiteSettingCustomScope(
-      info.primary_pattern, info.secondary_pattern,
-      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, {});
+  DeletePatternFromRevokedPermissionList(info.primary_pattern,
+                                         info.secondary_pattern);
 
   // Record the days elapsed from auto-revocation to regrant.
   base::Time revoked_time =
@@ -229,10 +254,9 @@
       ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, &settings);
 
   for (const auto& revoked_permissions : settings) {
-    hcsm_->SetWebsiteSettingCustomScope(
+    DeletePatternFromRevokedPermissionList(
         revoked_permissions.primary_pattern,
-        revoked_permissions.secondary_pattern,
-        ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, {});
+        revoked_permissions.secondary_pattern);
   }
 }
 
@@ -305,6 +329,14 @@
   }
 }
 
+void UnusedSitePermissionsService::DeletePatternFromRevokedPermissionList(
+    const ContentSettingsPattern& primary_pattern,
+    const ContentSettingsPattern& secondary_pattern) {
+  hcsm_->SetWebsiteSettingCustomScope(
+      primary_pattern, secondary_pattern,
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, {});
+}
+
 void UnusedSitePermissionsService::RevokeUnusedPermissions() {
   if (!base::FeatureList::IsEnabled(
           content_settings::features::kSafetyCheckUnusedSitePermissions)) {
diff --git a/components/permissions/unused_site_permissions_service.h b/components/permissions/unused_site_permissions_service.h
index 20175b0..f7d11da 100644
--- a/components/permissions/unused_site_permissions_service.h
+++ b/components/permissions/unused_site_permissions_service.h
@@ -11,9 +11,11 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "components/content_settings/core/browser/content_settings_observer.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -37,7 +39,8 @@
 // on navigations and clears them periodically.
 class UnusedSitePermissionsService
     : public KeyedService,
-      public base::SupportsWeakPtr<UnusedSitePermissionsService> {
+      public base::SupportsWeakPtr<UnusedSitePermissionsService>,
+      public content_settings::Observer {
  public:
   struct ContentSettingEntry {
     ContentSettingsType type;
@@ -73,6 +76,12 @@
 
   ~UnusedSitePermissionsService() override;
 
+  // content_settings::Observer implementation.
+  void OnContentSettingChanged(
+      const ContentSettingsPattern& primary_pattern,
+      const ContentSettingsPattern& secondary_pattern,
+      ContentSettingsTypeSet content_type_set) override;
+
   // KeyedService implementation.
   void Shutdown() override;
 
@@ -132,6 +141,12 @@
   void OnUnusedPermissionsMapRetrieved(base::OnceClosure callback,
                                        UnusedPermissionMap map);
 
+  // Removes a pattern from the list of revoked permissions so that the entry is
+  // no longer shown to the user. Does not affect permissions themselves.
+  void DeletePatternFromRevokedPermissionList(
+      const ContentSettingsPattern& primary_pattern,
+      const ContentSettingsPattern& secondary_pattern);
+
   // Revokes permissions that belong to sites that were last visited over 60
   // days ago.
   void RevokeUnusedPermissions();
@@ -151,6 +166,10 @@
 
   const scoped_refptr<HostContentSettingsMap> hcsm_;
 
+  // Observer to watch for content settings changed.
+  base::ScopedObservation<HostContentSettingsMap, content_settings::Observer>
+      content_settings_observation_{this};
+
   raw_ptr<base::Clock> clock_;
 };
 
diff --git a/components/permissions/unused_site_permissions_service_unittest.cc b/components/permissions/unused_site_permissions_service_unittest.cc
index 6e85d0a..a63ac56 100644
--- a/components/permissions/unused_site_permissions_service_unittest.cc
+++ b/components/permissions/unused_site_permissions_service_unittest.cc
@@ -313,9 +313,26 @@
   EXPECT_EQ(service()->GetTrackedUnusedPermissionsForTesting().size(), 1u);
   EXPECT_EQ(service()->GetTrackedUnusedPermissionsForTesting()[0].type,
             ContentSettingsType::MEDIASTREAM_CAMERA);
+}
 
-  // Travel through time for 20 days.
-  clock()->Advance(base::Days(20));
+TEST_F(UnusedSitePermissionsServiceTest, ClearRevokedPermissionsListAfter30d) {
+  base::test::ScopedFeatureList scoped_feature;
+  scoped_feature.InitAndEnableFeature(
+      content_settings::features::kSafetyCheckUnusedSitePermissions);
+
+  const GURL url("https://example1.com");
+  const content_settings::ContentSettingConstraints constraint{
+      .track_last_visit_for_autoexpiration = true};
+
+  hcsm()->SetContentSettingDefaultScope(
+      url, url, ContentSettingsType::MEDIASTREAM_CAMERA,
+      ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+  hcsm()->SetContentSettingDefaultScope(
+      url, url, ContentSettingsType::GEOLOCATION,
+      ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+
+  // Travel through time for 70 days.
+  clock()->Advance(base::Days(70));
 
   // Both GEOLOCATION and MEDIASTREAM_CAMERA permissions should be on the
   // revoked permissions list as they are granted more than 60 days before.
@@ -628,4 +645,41 @@
       "Settings.SafetyCheck.UnusedSitePermissionsAllowAgainDays", 14, 1);
 }
 
+TEST_F(UnusedSitePermissionsServiceTest,
+       RemoveSiteFromRevokedPermissionsListOnPermissionChange) {
+  const GURL url1 = GURL("https://example1.com:443");
+  const GURL url2 = GURL("https://example2.com:443");
+  const ContentSettingsType type = ContentSettingsType::GEOLOCATION;
+
+  base::Value::Dict dict = base::Value::Dict();
+  base::Value::List permission_type_list = base::Value::List();
+  permission_type_list.Append(static_cast<int32_t>(type));
+  dict.Set(kRevokedKey, base::Value::List(std::move(permission_type_list)));
+
+  // Add url1 and url2 to revoked permissions list.
+  hcsm()->SetWebsiteSettingDefaultScope(
+      url1, url1, ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      base::Value(dict.Clone()));
+  hcsm()->SetWebsiteSettingDefaultScope(
+      url2, url2, ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      base::Value(dict.Clone()));
+
+  ContentSettingsForOneType revoked_permissions_list;
+  hcsm()->GetSettingsForOneType(
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      &revoked_permissions_list);
+
+  EXPECT_EQ(2U, revoked_permissions_list.size());
+
+  // For a site where permissions have been revoked, granting a revoked
+  // permission again will remove the site from the list.
+  hcsm()->SetContentSettingDefaultScope(
+      url1, GURL(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  // Check there is only url2 in revoked permissions list.
+  hcsm()->GetSettingsForOneType(
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      &revoked_permissions_list);
+  EXPECT_EQ(1U, revoked_permissions_list.size());
+}
+
 }  // namespace permissions
diff --git a/components/policy/core/browser/webui/json_generation.cc b/components/policy/core/browser/webui/json_generation.cc
index 3770525..2710214 100644
--- a/components/policy/core/browser/webui/json_generation.cc
+++ b/components/policy/core/browser/webui/json_generation.cc
@@ -7,7 +7,7 @@
 #include <memory>
 
 #include "base/json/json_writer.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/strcat.h"
 #include "base/values.h"
 #include "components/policy/core/browser/policy_conversions.h"
 #include "components/policy/core/browser/policy_conversions_client.h"
@@ -44,15 +44,13 @@
   base::Value::Dict chrome_metadata;
   chrome_metadata.Set("application", params.application_name);
 
-  std::string version = base::StringPrintf(
-      "%s (%s)%s %s%s", version_info::GetVersionNumber().c_str(),
-      l10n_util::GetStringUTF8(version_info::IsOfficialBuild()
-                                   ? IDS_VERSION_UI_OFFICIAL
-                                   : IDS_VERSION_UI_UNOFFICIAL)
-          .c_str(),
-      (params.channel_name.empty() ? "" : " " + params.channel_name).c_str(),
-      params.processor_variation.c_str(),
-      params.cohort_name ? params.cohort_name->c_str() : "");
+  std::string version = base::StrCat(
+      {version_info::GetVersionNumber(), " (",
+       l10n_util::GetStringUTF8(version_info::IsOfficialBuild()
+                                    ? IDS_VERSION_UI_OFFICIAL
+                                    : IDS_VERSION_UI_UNOFFICIAL),
+       ") ", params.channel_name, params.channel_name.empty() ? "" : " ",
+       params.processor_variation, params.cohort_name.value_or(std::string())});
 
   chrome_metadata.Set(kChromeMetadataVersionKey, version);
 
diff --git a/components/privacy_sandbox/canonical_topic.cc b/components/privacy_sandbox/canonical_topic.cc
index c569519b..8626a6a5 100644
--- a/components/privacy_sandbox/canonical_topic.cc
+++ b/components/privacy_sandbox/canonical_topic.cc
@@ -53,10 +53,9 @@
 }
 
 base::Value CanonicalTopic::ToValue() const {
-  base::Value value(base::Value::Type::DICT);
-  value.SetKey(kTopicId, base::Value(topic_id_.value()));
-  value.SetKey(kTaxonomyVersion, base::Value(taxonomy_version_));
-  return value;
+  return base::Value(base::Value::Dict()
+                         .Set(kTopicId, topic_id_.value())
+                         .Set(kTaxonomyVersion, taxonomy_version_));
 }
 
 /*static*/ absl::optional<CanonicalTopic> CanonicalTopic::FromValue(
diff --git a/components/privacy_sandbox/canonical_topic_unittest.cc b/components/privacy_sandbox/canonical_topic_unittest.cc
index bf4a53a..39892c0 100644
--- a/components/privacy_sandbox/canonical_topic_unittest.cc
+++ b/components/privacy_sandbox/canonical_topic_unittest.cc
@@ -84,9 +84,10 @@
   EXPECT_TRUE(converted_topic);
   EXPECT_EQ(test_topic, *converted_topic);
 
-  base::Value invalid_value(base::Value::Type::DICT);
-  invalid_value.SetKey("unrelated", base::Value("unrelated"));
-  converted_topic = CanonicalTopic::FromValue(invalid_value);
+  base::Value::Dict invalid_value;
+  invalid_value.Set("unrelated", "unrelated");
+  converted_topic =
+      CanonicalTopic::FromValue(base::Value(std::move(invalid_value)));
   EXPECT_FALSE(converted_topic);
 }
 
diff --git a/components/privacy_sandbox_strings.grdp b/components/privacy_sandbox_strings.grdp
index be84523..8708883 100644
--- a/components/privacy_sandbox_strings.grdp
+++ b/components/privacy_sandbox_strings.grdp
@@ -128,7 +128,7 @@
   </message>
   <!-- Note that this string differs slightly on Android, which instead uses `privacy_sandbox_m1_notice_restricted_description_1_android` -->
   <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1" desc="First description in the restricted notice">
-    Chrome now shares only very limited information between sites, such as when an ad was shown to you, to help sites measure the performance of ads.
+    We’re launching a new ad privacy feature called ad measurement. Chrome shares only very limited information between sites, such as when an ad was shown to you, to help sites measure the performance of ads.
   </message>
   <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_2" desc="Second description in the restricted notice"  formatter_data="android_java">
     Learn more about how Google protects your data in our Privacy Policy.
diff --git a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1.png.sha1 b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1.png.sha1
index 1bceea2ef..46fd2475 100644
--- a/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1.png.sha1
+++ b/components/privacy_sandbox_strings_grdp/IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1.png.sha1
@@ -1 +1 @@
-61289788d2a7ad658e9bbb28d3be091c2c10967f
\ No newline at end of file
+75636300fcf477b4c6d5323d7120b2166dc26ad4
\ No newline at end of file
diff --git a/components/quirks/quirks_client.cc b/components/quirks/quirks_client.cc
index 9f9d04b..afab6b6 100644
--- a/components/quirks/quirks_client.cc
+++ b/components/quirks/quirks_client.cc
@@ -72,7 +72,7 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // URL of icc file on Quirks Server.
-  int major_version = atoi(version_info::GetVersionNumber().c_str());
+  int major_version = version_info::GetMajorVersionNumberAsInt();
   std::string url = base::StringPrintf(
       kQuirksUrlFormat, IdToHexString(product_id_).c_str(), major_version);
 
diff --git a/components/safe_browsing/android/remote_database_manager.cc b/components/safe_browsing/android/remote_database_manager.cc
index 8571673..1778e9e 100644
--- a/components/safe_browsing/android/remote_database_manager.cc
+++ b/components/safe_browsing/android/remote_database_manager.cc
@@ -358,10 +358,12 @@
   return is_match ? AsyncMatch::MATCH : AsyncMatch::NO_MATCH;
 }
 
-bool RemoteSafeBrowsingDatabaseManager::MatchDownloadAllowlistUrl(
-    const GURL& url) {
+void RemoteSafeBrowsingDatabaseManager::MatchDownloadAllowlistUrl(
+    const GURL& url,
+    base::OnceCallback<void(bool)> callback) {
   NOTREACHED();
-  return true;
+  sb_task_runner()->PostTask(FROM_HERE,
+                             base::BindOnce(std::move(callback), true));
 }
 
 safe_browsing::ThreatSource RemoteSafeBrowsingDatabaseManager::GetThreatSource()
diff --git a/components/safe_browsing/android/remote_database_manager.h b/components/safe_browsing/android/remote_database_manager.h
index 020bd297..2c786c0 100644
--- a/components/safe_browsing/android/remote_database_manager.h
+++ b/components/safe_browsing/android/remote_database_manager.h
@@ -75,7 +75,9 @@
       const GURL& url,
       const std::string& metric_variation) override;
   bool CheckUrlForSubresourceFilter(const GURL& url, Client* client) override;
-  bool MatchDownloadAllowlistUrl(const GURL& url) override;
+  void MatchDownloadAllowlistUrl(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) override;
   safe_browsing::ThreatSource GetThreatSource() const override;
   bool IsDownloadProtectionEnabled() const override;
   void StartOnSBThread(
diff --git a/components/safe_browsing/core/browser/db/database_manager.h b/components/safe_browsing/core/browser/db/database_manager.h
index 1517a18..3b1a52a 100644
--- a/components/safe_browsing/core/browser/db/database_manager.h
+++ b/components/safe_browsing/core/browser/db/database_manager.h
@@ -199,10 +199,12 @@
   //
 
   // Check if the |url| matches any of the full-length hashes from the download
-  // allowlist.  Returns true if there was a match and false otherwise. To make
-  // sure we are conservative we will return true if an error occurs.  This
-  // method must be called on the IO thread.
-  virtual bool MatchDownloadAllowlistUrl(const GURL& url) = 0;
+  // allowlist. Runs `callback` asynchronously with true if there was a match
+  // and false otherwise. To make sure we are conservative we will return true
+  // if an error occurs.  This method must be called on the IO thread.
+  virtual void MatchDownloadAllowlistUrl(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) = 0;
 
   //
   // Methods to check the config of the DatabaseManager.
diff --git a/components/safe_browsing/core/browser/db/test_database_manager.cc b/components/safe_browsing/core/browser/db/test_database_manager.cc
index deadcb9..039f940 100644
--- a/components/safe_browsing/core/browser/db/test_database_manager.cc
+++ b/components/safe_browsing/core/browser/db/test_database_manager.cc
@@ -90,10 +90,11 @@
   return AsyncMatch::MATCH;
 }
 
-bool TestSafeBrowsingDatabaseManager::MatchDownloadAllowlistUrl(
-    const GURL& url) {
+void TestSafeBrowsingDatabaseManager::MatchDownloadAllowlistUrl(
+    const GURL& url,
+    base::OnceCallback<void(bool)> callback) {
   NOTIMPLEMENTED();
-  return true;
+  std::move(callback).Run(true);
 }
 
 safe_browsing::ThreatSource TestSafeBrowsingDatabaseManager::GetThreatSource()
diff --git a/components/safe_browsing/core/browser/db/test_database_manager.h b/components/safe_browsing/core/browser/db/test_database_manager.h
index 32af1bc3..88526ac 100644
--- a/components/safe_browsing/core/browser/db/test_database_manager.h
+++ b/components/safe_browsing/core/browser/db/test_database_manager.h
@@ -47,7 +47,9 @@
       const GURL& url,
       const std::string& metric_variation) override;
   bool CheckUrlForSubresourceFilter(const GURL& url, Client* client) override;
-  bool MatchDownloadAllowlistUrl(const GURL& url) override;
+  void MatchDownloadAllowlistUrl(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) override;
   safe_browsing::ThreatSource GetThreatSource() const override;
   bool IsDownloadProtectionEnabled() const override;
   void StartOnSBThread(
diff --git a/components/safe_browsing/core/browser/db/v4_local_database_manager.cc b/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
index 855d363..49b9da4 100644
--- a/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
+++ b/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
@@ -576,7 +576,9 @@
   return HandleAllowlistCheck(std::move(check), /*allow_async_check=*/true);
 }
 
-bool V4LocalDatabaseManager::MatchDownloadAllowlistUrl(const GURL& url) {
+void V4LocalDatabaseManager::MatchDownloadAllowlistUrl(
+    const GURL& url,
+    base::OnceCallback<void(bool)> callback) {
   DCHECK(sb_task_runner()->RunsTasksInCurrentSequence());
 
   StoresToCheck stores_to_check({GetUrlCsdDownloadAllowlistId()});
@@ -584,10 +586,12 @@
   if (!AreAllStoresAvailableNow(stores_to_check) || !CanCheckUrl(url)) {
     // Fail close: Allowlist nothing. This may generate download-protection
     // pings for allowlisted domains, but that's fine.
-    return false;
+    sb_task_runner()->PostTask(FROM_HERE,
+                               base::BindOnce(std::move(callback), false));
+    return;
   }
 
-  return HandleUrlSynchronously(url, stores_to_check);
+  HandleUrl(url, stores_to_check, std::move(callback));
 }
 
 ThreatSource V4LocalDatabaseManager::GetThreatSource() const {
@@ -895,9 +899,10 @@
   }
 }
 
-bool V4LocalDatabaseManager::HandleUrlSynchronously(
+void V4LocalDatabaseManager::HandleUrl(
     const GURL& url,
-    const StoresToCheck& stores_to_check) {
+    const StoresToCheck& stores_to_check,
+    base::OnceCallback<void(bool)> callback) {
   DCHECK(sb_task_runner()->RunsTasksInCurrentSequence());
 
   std::unique_ptr<PendingCheck> check = std::make_unique<PendingCheck>(
@@ -905,7 +910,8 @@
       std::vector<GURL>(1, url),
       MechanismExperimentHashDatabaseCache::kNoExperiment);
 
-  return GetPrefixMatches(check);
+  sb_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), GetPrefixMatches(check)));
 }
 
 void V4LocalDatabaseManager::OnFullHashResponse(
diff --git a/components/safe_browsing/core/browser/db/v4_local_database_manager.h b/components/safe_browsing/core/browser/db/v4_local_database_manager.h
index b8da89be..002f69a9 100644
--- a/components/safe_browsing/core/browser/db/v4_local_database_manager.h
+++ b/components/safe_browsing/core/browser/db/v4_local_database_manager.h
@@ -83,7 +83,9 @@
       const GURL& url,
       const std::string& metric_variation) override;
   bool CheckUrlForSubresourceFilter(const GURL& url, Client* client) override;
-  bool MatchDownloadAllowlistUrl(const GURL& url) override;
+  void MatchDownloadAllowlistUrl(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) override;
   safe_browsing::ThreatSource GetThreatSource() const override;
   bool IsDownloadProtectionEnabled() const override;
 
@@ -299,10 +301,10 @@
   void ScheduleFullHashCheck(std::unique_ptr<PendingCheck> check);
 
   // Checks |stores_to_check| in database synchronously for hash prefixes
-  // matching the full hashes for |url|. See |HandleHashSynchronously| for
-  // details.
-  bool HandleUrlSynchronously(const GURL& url,
-                              const StoresToCheck& stores_to_check);
+  // matching the full hashes for |url|.
+  void HandleUrl(const GURL& url,
+                 const StoresToCheck& stores_to_check,
+                 base::OnceCallback<void(bool)> callback);
 
   // Called when the |v4_get_hash_protocol_manager_| has the full hash response
   // available for the URL that we requested. It determines the severest
diff --git a/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc b/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
index bb278f4..b7f18810 100644
--- a/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
+++ b/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/task_environment.h"
@@ -1101,14 +1102,35 @@
 
   ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
   // Verify it defaults to false when DB is not available.
-  EXPECT_FALSE(v4_local_database_manager_->MatchDownloadAllowlistUrl(good_url));
+  bool result = false;
+  base::RunLoop run_loop1;
+  v4_local_database_manager_->MatchDownloadAllowlistUrl(
+      good_url, base::BindLambdaForTesting([&](bool value) {
+        result = value;
+        run_loop1.Quit();
+      }));
+  run_loop1.Run();
+  EXPECT_FALSE(result);
 
   ReplaceV4Database(store_and_hash_prefixes, true /* available */);
   // Not allowlisted.
-  EXPECT_FALSE(
-      v4_local_database_manager_->MatchDownloadAllowlistUrl(other_url));
+  base::RunLoop run_loop2;
+  v4_local_database_manager_->MatchDownloadAllowlistUrl(
+      other_url, base::BindLambdaForTesting([&](bool value) {
+        result = value;
+        run_loop2.Quit();
+      }));
+  run_loop2.Run();
+  EXPECT_FALSE(result);
   // Allowlisted.
-  EXPECT_TRUE(v4_local_database_manager_->MatchDownloadAllowlistUrl(good_url));
+  base::RunLoop run_loop3;
+  v4_local_database_manager_->MatchDownloadAllowlistUrl(
+      good_url, base::BindLambdaForTesting([&](bool value) {
+        result = value;
+        run_loop3.Quit();
+      }));
+  run_loop3.Run();
+  EXPECT_TRUE(result);
 
   EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
       v4_local_database_manager_));
diff --git a/components/safe_browsing/core/browser/user_population.cc b/components/safe_browsing/core/browser/user_population.cc
index c6ad200..2cf3c8a 100644
--- a/components/safe_browsing/core/browser/user_population.cc
+++ b/components/safe_browsing/core/browser/user_population.cc
@@ -5,6 +5,7 @@
 #include "components/safe_browsing/core/browser/user_population.h"
 
 #include "base/feature_list.h"
+#include "base/strings/strcat.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
@@ -57,8 +58,8 @@
       GetProfileManagementStatus(browser_policy_connector));
 
   std::string user_agent =
-      version_info::GetProductNameAndVersionForUserAgent() + "/" +
-      version_info::GetOSType();
+      base::StrCat({version_info::GetProductNameAndVersionForUserAgent(), "/",
+                    version_info::GetOSType()});
   population.set_user_agent(user_agent);
 
   if (num_profiles)
diff --git a/components/safe_browsing/core/browser/user_population_unittest.cc b/components/safe_browsing/core/browser/user_population_unittest.cc
index 1abb2b4e..928db1c 100644
--- a/components/safe_browsing/core/browser/user_population_unittest.cc
+++ b/components/safe_browsing/core/browser/user_population_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/safe_browsing/core/browser/user_population.h"
 
 #include "base/feature_list.h"
+#include "base/strings/strcat.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "components/safe_browsing/buildflags.h"
@@ -158,8 +159,8 @@
   base::test::TaskEnvironment task_environment;
   auto pref_service = CreatePrefService();
   std::string user_agent =
-      version_info::GetProductNameAndVersionForUserAgent() + "/" +
-      version_info::GetOSType();
+      base::StrCat({version_info::GetProductNameAndVersionForUserAgent(), "/",
+                    version_info::GetOSType()});
   ChromeUserPopulation population =
       GetUserPopulation(pref_service.get(), false, false, false, false, nullptr,
                         absl::optional<size_t>(), absl::optional<size_t>(),
diff --git a/components/search/ntp_features.cc b/components/search/ntp_features.cc
index e1fec3b..1f2eec6 100644
--- a/components/search/ntp_features.cc
+++ b/components/search/ntp_features.cc
@@ -28,13 +28,13 @@
 // on NTP Customize Chrome background change.
 BASE_FEATURE(kCustomizeChromeColorExtraction,
              "CustomizeChromeColorExtraction",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // If enabled, Customize Chrome will be an option in the Unified Side Panel
 // when on the New Tab Page.
 BASE_FEATURE(kCustomizeChromeSidePanel,
              "CustomizeChromeSidePanel",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Forces a dark Google logo for a specific subset of Chrome Web Store themes
 // (see crbug.com/1329552). This is enabled by default to allow finch to disable
diff --git a/components/security_interstitials/core/https_only_mode_allowlist.cc b/components/security_interstitials/core/https_only_mode_allowlist.cc
index ff7f88dc..1fe38600 100644
--- a/components/security_interstitials/core/https_only_mode_allowlist.cc
+++ b/components/security_interstitials/core/https_only_mode_allowlist.cc
@@ -51,12 +51,11 @@
   // directly storing a string value.
   GURL url = GetSecureGURLForHost(host);
   base::Time expiration_time = clock_->Now() + expiration_timeout_;
-  auto dict = std::make_unique<base::Value>(base::Value::Type::DICT);
-  dict->SetKey(kHTTPAllowlistExpirationTimeKey,
-               base::TimeToValue(expiration_time));
+  base::Value::Dict dict;
+  dict.Set(kHTTPAllowlistExpirationTimeKey, base::TimeToValue(expiration_time));
   host_content_settings_map_->SetWebsiteSettingDefaultScope(
       url, GURL(), ContentSettingsType::HTTP_ALLOWED,
-      base::Value::FromUniquePtrValue(std::move(dict)));
+      base::Value(std::move(dict)));
 }
 
 bool HttpsOnlyModeAllowlist::IsHttpAllowedForHost(
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.cc b/components/segmentation_platform/internal/execution/model_executor_impl.cc
index b9d98e0..5e4a31fa 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.cc
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.cc
@@ -16,6 +16,7 @@
 #include "components/segmentation_platform/internal/segmentation_ukm_helper.h"
 #include "components/segmentation_platform/internal/stats.h"
 #include "components/segmentation_platform/public/model_provider.h"
+#include "components/segmentation_platform/public/proto/model_metadata.pb.h"
 #include "components/segmentation_platform/public/proto/segmentation_platform.pb.h"
 #include "third_party/perfetto/include/perfetto/tracing/track.h"
 
@@ -210,6 +211,8 @@
         model_metadata.signal_storage_length() *
         metadata_utils::GetTimeUnit(model_metadata);
     if (state->segment_info.model_version() &&
+        state->segment_info.model_source() ==
+            proto::ModelSource::SERVER_MODEL_SOURCE &&
         SegmentationUkmHelper::AllowedToUploadData(signal_storage_length,
                                                    clock_)) {
       if (state->upload_tensors) {
diff --git a/components/segmentation_platform/internal/segmentation_ukm_helper.cc b/components/segmentation_platform/internal/segmentation_ukm_helper.cc
index bad53f8..486d2f74 100644
--- a/components/segmentation_platform/internal/segmentation_ukm_helper.cc
+++ b/components/segmentation_platform/internal/segmentation_ukm_helper.cc
@@ -6,6 +6,7 @@
 
 #include "base/bit_cast.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/time/clock.h"
@@ -101,7 +102,17 @@
     &Segmentation_ModelExecution::SetActualResult4,
     &Segmentation_ModelExecution::SetActualResult5,
     &Segmentation_ModelExecution::SetActualResult6};
-}  // namespace
+
+// 1 out of 100 model execution will be reported.
+const int kDefaultModelExecutionSamplingRate = 100;
+
+int GetModelExecutionSamplingRate() {
+  return base::GetFieldTrialParamByFeatureAsInt(
+      segmentation_platform::features::
+          kSegmentationPlatformModelExecutionSampling,
+      segmentation_platform::kModelExecutionSamplingRateKey,
+      kDefaultModelExecutionSamplingRate);
+}
 
 // Helper method to add model prediction results to UKM log.
 void AddPredictionResultToUkmModelExecution(
@@ -113,6 +124,7 @@
     (SegmentationUkmHelper::FloatToInt64(results[i]));
   }
 }
+}  // namespace
 
 namespace segmentation_platform {
 
@@ -144,6 +156,8 @@
         SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT,
         SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_CHROME_START_ANDROID_V2};
   }
+  sampling_rate_ = GetModelExecutionSamplingRate();
+  DCHECK_GE(sampling_rate_, 0);
 }
 
 ukm::SourceId SegmentationUkmHelper::RecordModelExecutionResult(
@@ -152,6 +166,14 @@
     const ModelProvider::Request& input_tensor,
     const std::vector<float>& results) {
   ukm::SourceId source_id = ukm::NoURLSourceId();
+  // Do some sampling before sending out UKM.
+  if (sampling_rate_ == 0) {
+    return source_id;
+  }
+
+  if (base::RandInt(1, sampling_rate_) > 1) {
+    return source_id;
+  }
   ukm::builders::Segmentation_ModelExecution execution_result(source_id);
 
   // Add inputs to ukm message.
diff --git a/components/segmentation_platform/internal/segmentation_ukm_helper.h b/components/segmentation_platform/internal/segmentation_ukm_helper.h
index 53c986ec..478efdaa 100644
--- a/components/segmentation_platform/internal/segmentation_ukm_helper.h
+++ b/components/segmentation_platform/internal/segmentation_ukm_helper.h
@@ -90,6 +90,7 @@
   SegmentationUkmHelper();
   ~SegmentationUkmHelper();
 
+  int sampling_rate_;
   base::flat_set<SegmentId> allowed_segment_ids_;
 };
 
diff --git a/components/segmentation_platform/internal/segmentation_ukm_helper_unittest.cc b/components/segmentation_platform/internal/segmentation_ukm_helper_unittest.cc
index 8f243a8..bc69dd5 100644
--- a/components/segmentation_platform/internal/segmentation_ukm_helper_unittest.cc
+++ b/components/segmentation_platform/internal/segmentation_ukm_helper_unittest.cc
@@ -7,6 +7,7 @@
 #include <cmath>
 
 #include "base/bit_cast.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
@@ -107,6 +108,14 @@
     EXPECT_EQ(0u, test_recorder_.GetEntriesByName(entry_name).size());
   }
 
+  void SetSamplingRate(int sampling_rate) {
+    feature_list_.InitAndEnableFeatureWithParameters(
+        features::kSegmentationPlatformModelExecutionSampling,
+        {{kModelExecutionSamplingRateKey,
+          base::NumberToString(sampling_rate)}});
+    InitializeUkmHelper();
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_;
   ukm::TestAutoSetUkmRecorder test_recorder_;
@@ -115,6 +124,7 @@
 
 // Tests that basic execution results recording works properly.
 TEST_F(SegmentationUkmHelperTest, TestExecutionResultReporting) {
+  SetSamplingRate(1);
   // Allow results for OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB to be recorded.
   ModelProvider::Request input_tensors = {0.1, 0.7, 0.8, 0.5};
   SegmentationUkmHelper::GetInstance()->RecordModelExecutionResult(
@@ -141,6 +151,18 @@
                    });
 }
 
+// Tests that execution results recording are disabled if sampling rate is 0.
+TEST_F(SegmentationUkmHelperTest,
+       TestExecutionResultReportingwithZeroSampling) {
+  SetSamplingRate(0);
+  // Allow results for OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB to be recorded.
+  ModelProvider::Request input_tensors = {0.1, 0.7, 0.8, 0.5};
+  EXPECT_EQ(SegmentationUkmHelper::GetInstance()->RecordModelExecutionResult(
+                proto::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB, 101,
+                input_tensors, {0.6, 0.3}),
+            ukm::NoURLSourceId());
+}
+
 // Tests that the training data collection recording works properly.
 TEST_F(SegmentationUkmHelperTest, TestTrainingDataCollectionReporting) {
   ModelProvider::Request input_tensors = {0.1};
@@ -232,6 +254,7 @@
 
 // Tests that there are too many input tensors to record.
 TEST_F(SegmentationUkmHelperTest, TooManyInputTensors) {
+  SetSamplingRate(1);
   base::HistogramTester tester;
   std::string histogram_name(
       "SegmentationPlatform.StructuredMetrics.TooManyTensors.Count");
diff --git a/components/segmentation_platform/public/constants.h b/components/segmentation_platform/public/constants.h
index 527b591..0d11f60 100644
--- a/components/segmentation_platform/public/constants.h
+++ b/components/segmentation_platform/public/constants.h
@@ -95,11 +95,6 @@
 const char kTabResumptionClassifierKey[] = "tab_resupmtion_classifier";
 const char kTabResumptionClassifierUmaName[] = "TabResumptionClassifier";
 
-// The key provide a list of segment IDs, separated by commas, whose ML model
-// execution results are allowed to be uploaded through UKM.
-const char kSegmentIdsAllowedForReportingKey[] =
-    "segment_ids_allowed_for_reporting";
-
 // Config parameter name specified in experiment configs. Any experiment config
 // or feature can include this param and segmentation will enable the config for
 // storing cached results.
@@ -170,6 +165,9 @@
 const char kContextualPageActionModelInputPriceTracking[] = "can_track_price";
 const char kContextualPageActionModelInputReaderMode[] = "has_reader_mode";
 
+// Finch parameter key for sampling rate of the model execution results.
+constexpr char kModelExecutionSamplingRateKey[] =
+    "model_execution_sampling_rate";
 }  // namespace segmentation_platform
 
 #endif  // COMPONENTS_SEGMENTATION_PLATFORM_PUBLIC_CONSTANTS_H_
diff --git a/components/segmentation_platform/public/features.cc b/components/segmentation_platform/public/features.cc
index 9ad4120..51a23a3 100644
--- a/components/segmentation_platform/public/features.cc
+++ b/components/segmentation_platform/public/features.cc
@@ -83,4 +83,8 @@
 BASE_FEATURE(kSegmentationPlatformTabletProductivityUser,
              "SegmentationPlatformTabletProductivityUser",
              base::FEATURE_ENABLED_BY_DEFAULT);
+
+BASE_FEATURE(kSegmentationPlatformModelExecutionSampling,
+             "SegmentationPlatformModelExecutionSampling",
+             base::FEATURE_ENABLED_BY_DEFAULT);
 }  // namespace segmentation_platform::features
diff --git a/components/segmentation_platform/public/features.h b/components/segmentation_platform/public/features.h
index 225e2d0..4133d73 100644
--- a/components/segmentation_platform/public/features.h
+++ b/components/segmentation_platform/public/features.h
@@ -65,6 +65,8 @@
 // Feature flag for enabling tablet productivity user segment.
 BASE_DECLARE_FEATURE(kSegmentationPlatformTabletProductivityUser);
 
+// Feature flag for enabling model execution report sampling.
+BASE_DECLARE_FEATURE(kSegmentationPlatformModelExecutionSampling);
 }  // namespace segmentation_platform::features
 
 #endif  // COMPONENTS_SEGMENTATION_PLATFORM_PUBLIC_FEATURES_H_
diff --git a/components/supervised_user/core/browser/kids_external_fetcher.cc b/components/supervised_user/core/browser/kids_external_fetcher.cc
index ae6b609..2e8c2fd5 100644
--- a/components/supervised_user/core/browser/kids_external_fetcher.cc
+++ b/components/supervised_user/core/browser/kids_external_fetcher.cc
@@ -41,6 +41,7 @@
 using ::base::JoinString;
 using ::base::StrCat;
 using ::base::StringPiece;
+using ::base::StringPrintf;
 using ::base::TimeDelta;
 using ::base::TimeTicks;
 using ::base::UmaHistogramEnumeration;
@@ -73,15 +74,16 @@
   return loader.ResponseInfo()->headers->response_code();
 }
 
-std::string CreateAuthorizationHeader(StringPiece access_token) {
+std::string CreateAuthorizationHeader(
+    const signin::AccessTokenInfo& access_token_info) {
   // Do not use StringPiece with StringPrintf, see crbug/1444165
-  return base::JoinString({supervised_user::kAuthorizationHeader, access_token},
-                          " ");
+  return base::StrCat(
+      {supervised_user::kAuthorizationHeader, " ", access_token_info.token});
 }
 
 // TODO(b/276898959): Support payload for POST requests.
 std::unique_ptr<network::SimpleURLLoader> InitializeSimpleUrlLoader(
-    StringPiece access_token,
+    const signin::AccessTokenInfo access_token_info,
     const supervised_user::FetcherConfig& fetcher_config,
     const GURL& url) {
   std::unique_ptr<ResourceRequest> resource_request =
@@ -89,8 +91,9 @@
   resource_request->url = url;
   resource_request->method = fetcher_config.GetHttpMethod();
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-  resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
-                                      CreateAuthorizationHeader(access_token));
+  resource_request->headers.SetHeader(
+      net::HttpRequestHeaders::kAuthorization,
+      CreateAuthorizationHeader(access_token_info));
   std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
       network::SimpleURLLoader::Create(std::move(resource_request),
                                        fetcher_config.traffic_annotation());
@@ -155,11 +158,11 @@
     std::move(callback).Run(status, std::move(response));
   }
 
-  std::string GetMetricKey(base::StringPiece metric_id) const {
+  std::string GetMetricKey(StringPiece metric_id) const {
     return JoinString({config_.histogram_basename, metric_id}, ".");
   }
-  std::string GetMetricKey(base::StringPiece metric_id,
-                           base::StringPiece metric_suffix) const {
+  std::string GetMetricKey(StringPiece metric_id,
+                           StringPiece metric_suffix) const {
     return JoinString({config_.histogram_basename, metric_id, metric_suffix},
                       ".");
   }
@@ -187,7 +190,7 @@
 
     // TODO(b/276898959): add optional payload for POST requests.
     simple_url_loader_ = InitializeSimpleUrlLoader(
-        access_token.value().token, config_,
+        access_token.value(), config_,
         supervised_user::CreateRequestUrl<Request>(config_));
 
     simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
@@ -299,13 +302,11 @@
     case KidsExternalFetcherStatus::OK:
       return "KidsExternalFetcherStatus::OK";
     case KidsExternalFetcherStatus::GOOGLE_SERVICE_AUTH_ERROR:
-      return base::StrCat(
-          {"KidsExternalFetcherStatus::GOOGLE_SERVICE_AUTH_ERROR: ",
-           google_service_auth_error().ToString()});
+      return StrCat({"KidsExternalFetcherStatus::GOOGLE_SERVICE_AUTH_ERROR: ",
+                     google_service_auth_error().ToString()});
     case KidsExternalFetcherStatus::NET_OR_HTTP_ERROR:
-      return base::StringPrintf(
-          "KidsExternalFetcherStatus::NET_OR_HTTP_ERROR: %d",
-          net_or_http_error_code_.value());
+      return StringPrintf("KidsExternalFetcherStatus::NET_OR_HTTP_ERROR: %d",
+                          net_or_http_error_code_.value());
     case KidsExternalFetcherStatus::INVALID_RESPONSE:
       return "KidsExternalFetcherStatus::INVALID_RESPONSE";
     case KidsExternalFetcherStatus::DATA_ERROR:
diff --git a/components/sync/base/sync_util.cc b/components/sync/base/sync_util.cc
index d3ba4ce..e636023 100644
--- a/components/sync/base/sync_util.cc
+++ b/components/sync/base/sync_util.cc
@@ -6,6 +6,7 @@
 
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringize_macros.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -54,18 +55,14 @@
 
 std::string FormatUserAgentForSync(const std::string& system,
                                    version_info::Channel channel) {
-  std::string product = STRINGIZE(SYNC_USER_AGENT_PRODUCT);
-  std::string user_agent;
-  user_agent = product + " ";
-  user_agent += system;
-  user_agent += version_info::GetVersionNumber();
-  user_agent += " (" + version_info::GetLastChange() + ")";
-  if (!version_info::IsOfficialBuild()) {
-    user_agent += "-devel";
-  } else {
-    user_agent += " channel(" + version_info::GetChannelString(channel) + ")";
-  }
-  return user_agent;
+  constexpr base::StringPiece kProduct = STRINGIZE(SYNC_USER_AGENT_PRODUCT);
+  return base::StrCat(
+      {kProduct, " ", system, version_info::GetVersionNumber(), " (",
+       version_info::GetLastChange(), ")",
+       version_info::IsOfficialBuild()
+           ? base::StrCat(
+                 {" channel(", version_info::GetChannelString(channel), ")"})
+           : std::string("-devel")});
 }
 
 }  // namespace internal
diff --git a/components/sync/driver/sync_internals_util.cc b/components/sync/driver/sync_internals_util.cc
index 492419b6..c53c29d 100644
--- a/components/sync/driver/sync_internals_util.cc
+++ b/components/sync/driver/sync_internals_util.cc
@@ -10,6 +10,7 @@
 
 #include "base/i18n/time_formatting.h"
 #include "base/notreached.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -223,9 +224,10 @@
   } else {
     version_modifier = " " + version_modifier;
   }
-  return version_info::GetProductName() + " " + version_info::GetOSType() +
-         " " + version_info::GetVersionNumber() + " (" +
-         version_info::GetLastChange() + ")" + version_modifier;
+  return base::StrCat({version_info::GetProductName(), " ",
+                       version_info::GetOSType(), " ",
+                       version_info::GetVersionNumber(), " (",
+                       version_info::GetLastChange(), ")", version_modifier});
 }
 
 std::string GetTimeStr(base::Time time,
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index 225a45c..02a8aa4 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -1453,7 +1453,7 @@
   VISIT_REP(icon_infos);
   VISIT(user_page_ordinal);
   VISIT(user_launch_ordinal);
-  VISIT(manifest_id);
+  VISIT(relative_manifest_id);
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::WifiConfigurationSpecifics::
diff --git a/components/sync/protocol/web_app_specifics.proto b/components/sync/protocol/web_app_specifics.proto
index fb42044..79578ff 100644
--- a/components/sync/protocol/web_app_specifics.proto
+++ b/components/sync/protocol/web_app_specifics.proto
@@ -66,6 +66,14 @@
   // |user_page_ordinal| page.
   optional string user_launch_ordinal = 8;
 
-  // Used to store id specified in the manifest.
-  optional string manifest_id = 9;
+  // Used to store id specified in the manifest. This is a path that is relative
+  // to the start_url, similar to how the id field is parsed in
+  // https://www.w3.org/TR/appmanifest/#id-member, except this field does not
+  // include a scheme or origin. This is only the path after the origin,
+  // excluding the first "/".
+  // Note: If this field is not set, then the manifest_id is generated using the
+  // start_url in GenerateManifestIdFromStartUrlOnly. This is different than if
+  // this is set to "", which means the manifest_id will be set to the origin of
+  // the start_url.
+  optional string relative_manifest_id = 9;
 }
diff --git a/components/sync_device_info/BUILD.gn b/components/sync_device_info/BUILD.gn
index a5dd5cb..f8530dd2 100644
--- a/components/sync_device_info/BUILD.gn
+++ b/components/sync_device_info/BUILD.gn
@@ -65,6 +65,9 @@
     deps += [ "//chromeos/constants" ]
   }
 
+  if (is_apple) {
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
   if (is_ios) {
     sources += [ "local_device_info_util_ios.mm" ]
   }
diff --git a/components/sync_device_info/local_device_info_util_ios.mm b/components/sync_device_info/local_device_info_util_ios.mm
index b84620c4..b4096a8 100644
--- a/components/sync_device_info/local_device_info_util_ios.mm
+++ b/components/sync_device_info/local_device_info_util_ios.mm
@@ -8,10 +8,14 @@
 
 #include "base/strings/sys_string_conversions.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace syncer {
 
 std::string GetPersonalizableDeviceNameInternal() {
-  return base::SysNSStringToUTF8([[UIDevice currentDevice] name]);
+  return base::SysNSStringToUTF8(UIDevice.currentDevice.name);
 }
 
 }  // namespace syncer
diff --git a/components/sync_device_info/local_device_info_util_mac.mm b/components/sync_device_info/local_device_info_util_mac.mm
index 8d1ba40f..faf94bf 100644
--- a/components/sync_device_info/local_device_info_util_mac.mm
+++ b/components/sync_device_info/local_device_info_util_mac.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 <SystemConfiguration/SCDynamicStoreCopySpecific.h>
+#import <SystemConfiguration/SystemConfiguration.h>
 #include <stddef.h>
 #include <sys/sysctl.h>
 
@@ -12,6 +12,10 @@
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace syncer {
 
 // Returns the Hardware model name, without trailing numbers, if
@@ -20,30 +24,34 @@
 // model, this simply returns "Unknown".
 std::string GetPersonalizableDeviceNameInternal() {
   // Do not use NSHost currentHost, as it's very slow. http://crbug.com/138570
-  SCDynamicStoreContext context = {0, NULL, NULL, NULL};
-  base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
-      kCFAllocatorDefault, CFSTR("chrome_sync"), NULL, &context));
+  SCDynamicStoreContext context = {0};
+  base::ScopedCFTypeRef<SCDynamicStoreRef> store(
+      SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("chrome_sync"),
+                           /*callout=*/nullptr, &context));
   base::ScopedCFTypeRef<CFStringRef> machine_name(
-      SCDynamicStoreCopyLocalHostName(store.get()));
-  if (machine_name.get())
-    return base::SysCFStringRefToUTF8(machine_name.get());
+      SCDynamicStoreCopyLocalHostName(store));
+  if (machine_name) {
+    return base::SysCFStringRefToUTF8(machine_name);
+  }
 
   // Fall back to get computer name.
   base::ScopedCFTypeRef<CFStringRef> computer_name(
-      SCDynamicStoreCopyComputerName(store.get(), NULL));
-  if (computer_name.get())
-    return base::SysCFStringRefToUTF8(computer_name.get());
+      SCDynamicStoreCopyComputerName(store, /*nameEncoding=*/nullptr));
+  if (computer_name) {
+    return base::SysCFStringRefToUTF8(computer_name);
+  }
 
   // If all else fails, return to using a slightly nicer version of the
   // hardware model.
-  char modelBuffer[256];
-  size_t length = sizeof(modelBuffer);
-  if (!sysctlbyname("hw.model", modelBuffer, &length, NULL, 0)) {
+  char model_buffer[256];
+  size_t length = sizeof(model_buffer);
+  if (!sysctlbyname("hw.model", model_buffer, &length, nullptr, 0)) {
     for (size_t i = 0; i < length; i++) {
-      if (base::IsAsciiDigit(modelBuffer[i]))
-        return std::string(modelBuffer, 0, i);
+      if (base::IsAsciiDigit(model_buffer[i])) {
+        return std::string(model_buffer, 0, i);
+      }
     }
-    return std::string(modelBuffer, 0, length);
+    return std::string(model_buffer, 0, length);
   }
   return "Unknown";
 }
diff --git a/components/version_info/version_info.cc b/components/version_info/version_info.cc
index 9b1f5e4..081f81fe 100644
--- a/components/version_info/version_info.cc
+++ b/components/version_info/version_info.cc
@@ -15,10 +15,8 @@
 
 const std::string GetProductNameAndVersionForReducedUserAgent(
     const std::string& build_version) {
-  std::string product_and_version;
-  base::StrAppend(&product_and_version, {"Chrome/", GetMajorVersionNumber(),
-                                         ".0.", build_version, ".0"});
-  return product_and_version;
+  return base::StrCat(
+      {"Chrome/", GetMajorVersionNumber(), ".0.", build_version, ".0"});
 }
 
 int GetMajorVersionNumberAsInt() {
diff --git a/components/version_info/version_info.h b/components/version_info/version_info.h
index ffa554e..fa40b5a 100644
--- a/components/version_info/version_info.h
+++ b/components/version_info/version_info.h
@@ -39,7 +39,7 @@
 // Returns the product name and version information for the User-Agent header,
 // in the format: Chrome/<major_version>.<minor_version>.<build>.<patch>.
 constexpr std::string GetProductNameAndVersionForUserAgent() {
-  return "Chrome/" + GetVersionNumber();
+  return "Chrome/" PRODUCT_VERSION;
 }
 
 // Returns the major component (aka the milestone) of the version as an int,
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 416df84..d2547eb6 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -122,13 +122,6 @@
 BASE_FEATURE(kWebViewNewInvalidateHeuristic,
              "WebViewNewInvalidateHeuristic",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-// Historically media on android hardcoded SRGB color space because of lack of
-// color space support in surface control. This controls if we want to use real
-// color space in DisplayCompositor.
-BASE_FEATURE(kUseRealVideoColorSpaceForDisplay,
-             "UseRealVideoColorSpaceForDisplay",
-             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
 
 BASE_FEATURE(kDrawPredictedInkPoint,
@@ -357,18 +350,6 @@
 #endif
 }
 
-#if BUILDFLAG(IS_ANDROID)
-bool UseRealVideoColorSpaceForDisplay() {
-  // We need Android S for proper color space support in SurfaceControl.
-  if (base::android::BuildInfo::GetInstance()->sdk_int() <
-      base::android::SdkVersion::SDK_VERSION_S)
-    return false;
-
-  return base::FeatureList::IsEnabled(
-      features::kUseRealVideoColorSpaceForDisplay);
-}
-#endif
-
 // Used by Viz to determine if viz::DisplayScheduler should dynamically adjust
 // its frame deadline. Returns the percentile of historic draw times to base the
 // deadline on. Or absl::nullopt if the feature is disabled.
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 663ae73..dbcd38d 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -97,9 +97,6 @@
 VIZ_COMMON_EXPORT std::string InkPredictor();
 VIZ_COMMON_EXPORT bool ShouldUsePlatformDelegatedInk();
 VIZ_COMMON_EXPORT bool UseSurfaceLayerForVideo();
-#if BUILDFLAG(IS_ANDROID)
-VIZ_COMMON_EXPORT bool UseRealVideoColorSpaceForDisplay();
-#endif
 VIZ_COMMON_EXPORT absl::optional<double> IsDynamicSchedulerEnabledForDraw();
 VIZ_COMMON_EXPORT absl::optional<double> IsDynamicSchedulerEnabledForClients();
 VIZ_COMMON_EXPORT int MaxOverlaysConsidered();
diff --git a/components/viz/common/yuv_readback_unittest.cc b/components/viz/common/yuv_readback_unittest.cc
index 2f95f9f..9c6d7e6 100644
--- a/components/viz/common/yuv_readback_unittest.cc
+++ b/components/viz/common/yuv_readback_unittest.cc
@@ -98,11 +98,11 @@
         << json_data;
 
     CHECK(parsed_json->is_list());
-    for (const base::Value& dict : parsed_json->GetList()) {
-      CHECK(dict.is_dict());
-      const std::string* name = dict.FindStringPath("name");
+    for (const base::Value& entry : parsed_json->GetList()) {
+      const auto& dict = entry.GetDict();
+      const std::string* name = dict.FindString("name");
       CHECK(name);
-      const std::string* trace_type = dict.FindStringPath("ph");
+      const std::string* trace_type = dict.FindString("ph");
       CHECK(trace_type);
       // Count all except END traces, as they come in BEGIN/END pairs.
       if (*trace_type != "E" && *trace_type != "e")
diff --git a/components/viz/service/display/overlay_processor_surface_control.cc b/components/viz/service/display/overlay_processor_surface_control.cc
index 8d80e04..54cd3ce4 100644
--- a/components/viz/service/display/overlay_processor_surface_control.cc
+++ b/components/viz/service/display/overlay_processor_surface_control.cc
@@ -6,9 +6,11 @@
 
 #include <memory>
 
+#include "base/android/build_info.h"
 #include "cc/base/math_util.h"
 #include "components/viz/common/features.h"
 #include "components/viz/service/display/overlay_strategy_underlay.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/android/android_surface_control_compat.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/overlay_transform_utils.h"
@@ -32,9 +34,7 @@
 
 }  // namespace
 
-OverlayProcessorSurfaceControl::OverlayProcessorSurfaceControl()
-    : OverlayProcessorUsingStrategy(),
-      use_real_color_space_(features::UseRealVideoColorSpaceForDisplay()) {
+OverlayProcessorSurfaceControl::OverlayProcessorSurfaceControl() {
   // Android webview never sets |frame_sequence_number_| for the overlay
   // processor. Android Chrome does set this variable because it does call draw.
   // However, it also may not update this variable when displaying an overlay.
@@ -66,18 +66,17 @@
   DCHECK(!candidates->empty());
 
   for (auto& candidate : *candidates) {
-    // If we're going to use real color space from media codec, we should check
-    // if it's supported.
-    if (use_real_color_space_) {
-      if (!gfx::SurfaceControl::SupportsColorSpace(candidate.color_space)) {
-        candidate.overlay_handled = false;
-        return;
-      }
-    } else {
-      candidate.color_space = gfx::ColorSpace::CreateSRGB();
+    if (auto override_color_space = GetOverrideColorSpace()) {
+      candidate.color_space = override_color_space.value();
       candidate.hdr_metadata.reset();
     }
 
+    // Check if the ColorSpace is supported
+    if (!gfx::SurfaceControl::SupportsColorSpace(candidate.color_space)) {
+      candidate.overlay_handled = false;
+      return;
+    }
+
     // Check if screen rotation matches.
     if (absl::get<gfx::OverlayTransform>(candidate.transform) !=
         display_transform_) {
@@ -169,4 +168,19 @@
   viewport_size_ = viewport_size;
 }
 
+absl::optional<gfx::ColorSpace>
+OverlayProcessorSurfaceControl::GetOverrideColorSpace() {
+  // Historically, android media was hardcoding color space to srgb and it
+  // wasn't possible to overlay with arbitrary colorspace on pre-S devices, so
+  // we keep old behaviour there.
+  static bool is_older_than_s =
+      base::android::BuildInfo::GetInstance()->sdk_int() <
+      base::android::SdkVersion::SDK_VERSION_S;
+  if (is_older_than_s) {
+    return gfx::ColorSpace::CreateSRGB();
+  }
+
+  return absl::nullopt;
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display/overlay_processor_surface_control.h b/components/viz/service/display/overlay_processor_surface_control.h
index ac3a35b05..f45adb6 100644
--- a/components/viz/service/display/overlay_processor_surface_control.h
+++ b/components/viz/service/display/overlay_processor_surface_control.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_PROCESSOR_SURFACE_CONTROL_H_
 
 #include "components/viz/service/display/overlay_processor_using_strategy.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace viz {
 
@@ -16,6 +17,8 @@
   OverlayProcessorSurfaceControl();
   ~OverlayProcessorSurfaceControl() override;
 
+  static absl::optional<gfx::ColorSpace> GetOverrideColorSpace();
+
   bool IsOverlaySupported() const override;
 
   bool NeedsSurfaceDamageRectList() const override;
@@ -32,9 +35,6 @@
       const OverlayCandidate& overlay) const override;
 
  private:
-  // Historically, android media was hardcoding color space to srgb. This
-  // indicates that we going to use real one.
-  const bool use_real_color_space_;
   gfx::OverlayTransform display_transform_ = gfx::OVERLAY_TRANSFORM_NONE;
   gfx::Size viewport_size_;
 };
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 6dfc8e1..7cd0dc9 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -93,6 +93,10 @@
 #include "ui/gfx/geometry/transform_util.h"
 #include "ui/gfx/gpu_fence_handle.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "components/viz/service/display/overlay_processor_surface_control.h"
+#endif
+
 namespace viz {
 
 namespace {
@@ -848,11 +852,6 @@
         number_of_buffers);
   }
 #endif
-
-#if OS_ANDROID
-  use_real_color_space_for_stream_video_ =
-      features::UseRealVideoColorSpaceForDisplay();
-#endif
 }
 
 SkiaRenderer::~SkiaRenderer() = default;
@@ -2404,11 +2403,19 @@
     override_color_space = CurrentRenderPassSkColorSpace();
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-  // Force SRGB color space if we don't want real color space from media
-  // decoder.
-  if (!use_real_color_space_for_stream_video_ && quad->is_stream_video) {
-    override_color_space = SkColorSpace::MakeSRGB();
+#if BUILDFLAG(IS_ANDROID)
+  if (quad->is_stream_video) {
+    // If overlay processor would override color space, override it here to to
+    // avoid color changes during promotion.
+    if (auto overlay_color_space =
+            OverlayProcessorSurfaceControl::GetOverrideColorSpace()) {
+      override_color_space = overlay_color_space->ToSkColorSpace();
+    }
   }
+#else
+  // Only on android stream video can be composited.
+  CHECK(!quad->is_stream_video);
+#endif
 
   ScopedSkImageBuilder builder(
       this, quad->resource_id(), /*maybe_concurrent_reads=*/true,
diff --git a/components/viz/service/display/skia_renderer.h b/components/viz/service/display/skia_renderer.h
index 03bd3b7f..7f1f0154 100644
--- a/components/viz/service/display/skia_renderer.h
+++ b/components/viz/service/display/skia_renderer.h
@@ -492,7 +492,6 @@
 
   bool UsingSkiaForDelegatedInk() const;
   uint32_t debug_tint_modulate_count_ = 0;
-  bool use_real_color_space_for_stream_video_ = false;
 
   // Used to get mailboxes for the root render pass when
   // capabilities().renderer_allocates_images = true.
diff --git a/components/webapps/browser/BUILD.gn b/components/webapps/browser/BUILD.gn
index 3426962..1b9862f 100644
--- a/components/webapps/browser/BUILD.gn
+++ b/components/webapps/browser/BUILD.gn
@@ -39,6 +39,8 @@
     "installable/installable_params.h",
     "installable/installable_task_queue.cc",
     "installable/installable_task_queue.h",
+    "installable/ml_installability_promoter.cc",
+    "installable/ml_installability_promoter.h",
     "pwa_install_path_tracker.cc",
     "pwa_install_path_tracker.h",
     "webapps_client.cc",
diff --git a/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc b/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
index a33909e..5963f34 100644
--- a/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
+++ b/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
@@ -122,6 +122,7 @@
   manifest->name = base::ASCIIToUTF16(kDefaultManifestName);
   manifest->short_name = base::ASCIIToUTF16(kDefaultManifestShortName);
   manifest->start_url = GURL(kDefaultStartUrl);
+  manifest->id = GURL(kDefaultStartUrl);
   manifest->display = kDefaultManifestDisplayMode;
 
   blink::Manifest::ImageResource primary_icon;
diff --git a/components/webapps/browser/android/shortcut_info_unittest.cc b/components/webapps/browser/android/shortcut_info_unittest.cc
index cfab37a..da39032 100644
--- a/components/webapps/browser/android/shortcut_info_unittest.cc
+++ b/components/webapps/browser/android/shortcut_info_unittest.cc
@@ -271,7 +271,7 @@
 
 TEST_F(ShortcutInfoTest, ManifestIdGenerated) {
   manifest_.start_url = GURL("https://new.com/start");
-  manifest_.id = u"new_id";
+  manifest_.id = GURL("https://new.com/new_id");
 
   info_.UpdateFromManifest(manifest_);
 
@@ -280,7 +280,6 @@
 
 TEST_F(ShortcutInfoTest, ManifestIdFallback) {
   manifest_.start_url = GURL("https://new.com/start");
-  manifest_.id = absl::nullopt;
 
   info_.UpdateFromManifest(manifest_);
 
diff --git a/components/webapps/browser/android/webapps_utils_unittest.cc b/components/webapps/browser/android/webapps_utils_unittest.cc
index 4bb95ea8..dd07f2c 100644
--- a/components/webapps/browser/android/webapps_utils_unittest.cc
+++ b/components/webapps/browser/android/webapps_utils_unittest.cc
@@ -19,6 +19,7 @@
   manifest->name = u"foo";
   manifest->short_name = u"bar";
   manifest->start_url = GURL("http://example.com");
+  manifest->id = manifest->start_url;
   manifest->display = blink::mojom::DisplayMode::kStandalone;
 
   blink::Manifest::ImageResource icon;
@@ -41,6 +42,7 @@
 
   blink::mojom::ManifestPtr manifest = GetValidManifest();
   manifest->start_url = kUrlWithPassword;
+  manifest->id = kUrlWithPassword;
   EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(*manifest));
 
   manifest = GetValidManifest();
diff --git a/components/webapps/browser/banners/app_banner_manager.cc b/components/webapps/browser/banners/app_banner_manager.cc
index 403784f..d2bbb33 100644
--- a/components/webapps/browser/banners/app_banner_manager.cc
+++ b/components/webapps/browser/banners/app_banner_manager.cc
@@ -28,6 +28,7 @@
 #include "components/webapps/browser/installable/installable_data.h"
 #include "components/webapps/browser/installable/installable_manager.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
+#include "components/webapps/browser/installable/ml_installability_promoter.h"
 #include "components/webapps/browser/webapps_client.h"
 #include "components/webapps/common/switches.h"
 #include "content/public/browser/back_forward_cache.h"
@@ -180,6 +181,8 @@
 void AppBannerManager::RequestAppBanner(const GURL& validated_url) {
   DCHECK_EQ(State::INACTIVE, state_);
 
+  ml_promoter_->StartGatheringMetricsForFrameUrl(validated_url);
+
   UpdateState(State::ACTIVE);
   if (ShouldBypassEngagementChecks())
     status_reporter_ = std::make_unique<ConsoleStatusReporter>(web_contents());
@@ -248,9 +251,11 @@
       SiteEngagementObserver(site_engagement::SiteEngagementService::Get(
           web_contents->GetBrowserContext())),
       manager_(InstallableManager::FromWebContents(web_contents)),
+      ml_promoter_(MLInstallabilityPromoter::FromWebContents(web_contents)),
       manifest_(blink::mojom::Manifest::New()),
       status_reporter_(std::make_unique<NullStatusReporter>()) {
   DCHECK(manager_);
+  CHECK(ml_promoter_);
 
   AppBannerSettingsHelper::UpdateFromFieldTrial();
 }
diff --git a/components/webapps/browser/banners/app_banner_manager.h b/components/webapps/browser/banners/app_banner_manager.h
index 1d349f4..9e3cd38d 100644
--- a/components/webapps/browser/banners/app_banner_manager.h
+++ b/components/webapps/browser/banners/app_banner_manager.h
@@ -15,6 +15,7 @@
 #include "components/site_engagement/content/site_engagement_observer.h"
 #include "components/webapps/browser/installable/installable_logging.h"
 #include "components/webapps/browser/installable/installable_params.h"
+#include "components/webapps/browser/installable/ml_installability_promoter.h"
 #include "components/webapps/browser/pwa_install_path_tracker.h"
 #include "content/public/browser/media_player_id.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -459,6 +460,10 @@
   // Fetches the data required to display a banner for the current page.
   raw_ptr<InstallableManager, DanglingUntriaged> manager_;
 
+  // Measures site UKMs once the AppBannerManager triggers a pipeline and
+  // triggers a ML model to promote installability of an app.
+  raw_ptr<MLInstallabilityPromoter, DanglingUntriaged> ml_promoter_;
+
   // The manifest object. This is never null, it will instead be an empty
   // manifest so callers don't have to worry about null checks.
   blink::mojom::ManifestPtr manifest_;
diff --git a/components/webapps/browser/installable/installable_manager.cc b/components/webapps/browser/installable/installable_manager.cc
index 308317e..c882554 100644
--- a/components/webapps/browser/installable/installable_manager.cc
+++ b/components/webapps/browser/installable/installable_manager.cc
@@ -688,6 +688,9 @@
   if (!manifest.start_url.is_valid()) {
     valid_manifest_->errors.push_back(START_URL_NOT_VALID);
     is_valid = false;
+  } else {
+    // If the start_url is valid, the id must be valid.
+    CHECK(manifest.id.is_valid());
   }
 
   if ((!manifest.name || manifest.name->empty()) &&
diff --git a/components/webapps/browser/installable/installable_manager_unittest.cc b/components/webapps/browser/installable/installable_manager_unittest.cc
index 972b756..756e446 100644
--- a/components/webapps/browser/installable/installable_manager_unittest.cc
+++ b/components/webapps/browser/installable/installable_manager_unittest.cc
@@ -29,6 +29,7 @@
     manifest->name = u"foo";
     manifest->short_name = u"bar";
     manifest->start_url = GURL("http://example.com");
+    manifest->id = manifest->start_url;
     manifest->display = blink::mojom::DisplayMode::kStandalone;
 
     blink::Manifest::ImageResource primary_icon;
@@ -108,10 +109,12 @@
   blink::mojom::ManifestPtr manifest = GetValidManifest();
 
   manifest->start_url = GURL();
+  manifest->id = GURL();
   EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
 
   manifest->start_url = GURL("/");
+  manifest->id = GURL("/");
   EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
 }
diff --git a/components/webapps/browser/installable/ml_installability_promoter.cc b/components/webapps/browser/installable/ml_installability_promoter.cc
new file mode 100644
index 0000000..d0645f60
--- /dev/null
+++ b/components/webapps/browser/installable/ml_installability_promoter.cc
@@ -0,0 +1,30 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/webapps/browser/installable/ml_installability_promoter.h"
+
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "url/gurl.h"
+
+namespace webapps {
+
+MLInstallabilityPromoter::~MLInstallabilityPromoter() = default;
+
+void MLInstallabilityPromoter::StartGatheringMetricsForFrameUrl(
+    const GURL& url) {
+  // TODO(b/279521783): Start gathering UKMs from here. Assign the input url to
+  // frame_url if a ML model is not already running.
+}
+
+MLInstallabilityPromoter::MLInstallabilityPromoter(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents),
+      content::WebContentsUserData<MLInstallabilityPromoter>(*web_contents),
+      frame_url_(web_contents->GetLastCommittedURL()) {}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(MLInstallabilityPromoter);
+
+}  // namespace webapps
diff --git a/components/webapps/browser/installable/ml_installability_promoter.h b/components/webapps/browser/installable/ml_installability_promoter.h
new file mode 100644
index 0000000..90057f4
--- /dev/null
+++ b/components/webapps/browser/installable/ml_installability_promoter.h
@@ -0,0 +1,46 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_WEBAPPS_BROWSER_INSTALLABLE_ML_INSTALLABILITY_PROMOTER_H_
+#define COMPONENTS_WEBAPPS_BROWSER_INSTALLABLE_ML_INSTALLABILITY_PROMOTER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace webapps {
+
+// This class is used to measure metrics after page load and trigger a ML model
+// to promote installability of a site.
+class MLInstallabilityPromoter
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<MLInstallabilityPromoter> {
+ public:
+  ~MLInstallabilityPromoter() override;
+
+  MLInstallabilityPromoter(const MLInstallabilityPromoter&) = delete;
+  MLInstallabilityPromoter& operator=(const MLInstallabilityPromoter&) = delete;
+
+  // This is technically where the UKMs will be measured.
+  void StartGatheringMetricsForFrameUrl(const GURL& url);
+
+ private:
+  explicit MLInstallabilityPromoter(content::WebContents* web_contents);
+  friend class content::WebContentsUserData<MLInstallabilityPromoter>;
+
+  GURL frame_url_;
+  bool is_model_running_ = false;
+  base::WeakPtrFactory<MLInstallabilityPromoter> weak_factory_{this};
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+}  // namespace webapps
+
+#endif  // COMPONENTS_WEBAPPS_BROWSER_INSTALLABLE_ML_INSTALLABILITY_PROMOTER_H_
diff --git a/components/webauthn/DEPS b/components/webauthn/DEPS
index cc682c9..8755d7be 100644
--- a/components/webauthn/DEPS
+++ b/components/webauthn/DEPS
@@ -1,9 +1,10 @@
 include_rules = [
   "+components/keyed_service",
+  "+components/payments/content/android/java/src/org/chromium/components/payments",
   "+components/sync",
   "+content/public/browser",
   "+device/fido",
-  "+services/device/public/java/src/org/chromium/device/DeviceFeatureList.java",
   "+mojo/public/cpp/bindings",
+  "+services/device/public/java/src/org/chromium/device/DeviceFeatureList.java",
   "+third_party/blink/public/mojom/webauthn",
 ]
diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/BUILD.gn
index 5f055bb..4d92a90 100644
--- a/components/webauthn/android/BUILD.gn
+++ b/components/webauthn/android/BUILD.gn
@@ -62,13 +62,23 @@
 android_library("test_support_java") {
   testonly = true
   sources = [
+    "java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java",
     "java/src/org/chromium/components/webauthn/MockFido2CredentialRequest.java",
   ]
   deps = [
     ":java",
+    "//base:base_java_test_support",
+    "//components/payments/content/android:feature_list_java",
+    "//components/payments/mojom:mojom_java",
     "//content/public/android:content_java",
+    "//mojo/public/mojom/base:base_java",
+    "//third_party/android_deps:guava_android_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/blink/public/mojom:android_mojo_bindings_java",
+    "//third_party/junit:junit",
     "//url:origin_java",
+    "//url/mojom:url_mojom_gurl_java",
+    "//url/mojom:url_mojom_origin_java",
   ]
 }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2ApiTestHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
similarity index 77%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2ApiTestHelper.java
rename to components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
index 3fc16857..c2448a65 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2ApiTestHelper.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
@@ -2,13 +2,14 @@
 // 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.webauth;
+package org.chromium.components.webauthn;
 
 import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.content.Intent;
+import android.os.ConditionVariable;
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.util.Base64;
@@ -39,8 +40,6 @@
 import org.chromium.blink.mojom.UvmEntry;
 import org.chromium.components.payments.PaymentFeatureList;
 import org.chromium.components.payments.PaymentFeatureListJni;
-import org.chromium.components.webauthn.Fido2Api;
-import org.chromium.components.webauthn.WebAuthnCredentialDetails;
 import org.chromium.content.browser.ClientDataJsonImpl;
 import org.chromium.content.browser.ClientDataJsonImplJni;
 import org.chromium.mojo_base.mojom.TimeDelta;
@@ -51,6 +50,7 @@
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /* NO_BUILDER:
@@ -288,6 +288,62 @@
             -13, -22, 61, 32, 79, 53, 106, 127, -67, 32, 4, 0, 0, 6, 0, -1, -1, 20, 0, 0, 0, 15, 0,
             0, 0, 98, 111, 98, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 0};
 
+    // Serialized registration response converted from JSON received from the Credential Manager
+    // API.
+    private static final byte[] TEST_SERIALIZED_CREDMAN_MAKE_CREDENTIAL_RESPONSE = new byte[] {64,
+            0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 104, 1, 0, 0, 0,
+            0, 0, 0, 48, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 0, 0, 0, 0, -7, -1, -1, -1, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0,
+            0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 43, 0, 0,
+            0, 78, 51, 103, 120, 53, 49, 101, 106, 103, 77, 79, 99, 107, 56, 54, 101, 49, 84, 76,
+            71, 118, 78, 67, 112, 51, 54, 90, 86, 49, 100, 101, 105, 102, 99, 117, 73, 95, 104, 87,
+            51, 101, 87, 107, 0, 0, 0, 0, 0, 40, 0, 0, 0, 32, 0, 0, 0, 55, 120, 49, -25, 87, -93,
+            -128, -61, -100, -109, -50, -98, -43, 50, -58, -68, -48, -87, -33, -90, 85, -43, -41,
+            -94, 125, -53, -120, -2, 21, -73, 121, 105, 8, 0, 0, 0, 0, 0, 0, 0, -84, 0, 0, 0, -92,
+            0, 0, 0, 17, -54, 103, 117, 94, -24, 15, -48, -108, 31, 8, 99, -50, -54, -38, 125, -48,
+            -25, 91, 114, 111, 41, 32, 108, -21, -25, 66, -10, -32, -107, -62, 102, 93, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 55, 120, 49, -25, 87, -93, -128,
+            -61, -100, -109, -50, -98, -43, 50, -58, -68, -48, -87, -33, -90, 85, -43, -41, -94,
+            125, -53, -120, -2, 21, -73, 121, 105, -91, 1, 2, 3, 38, 32, 1, 33, 88, 32, -44, 11,
+            -114, -24, 96, -51, 111, 103, 66, 47, -29, 52, 75, -3, -57, 84, -63, 121, -95, -102,
+            -54, -112, -12, 115, -82, -100, -11, 86, -118, -106, -3, 8, 34, 88, 32, -35, 48, -66,
+            126, 31, 89, 68, -128, 30, 121, 109, 105, 78, 6, 90, 36, -58, -12, -69, 67, -56, 102,
+            -27, -125, -20, 4, -49, 64, 8, -77, -22, -80, 0, 0, 0, 0, -54, 0, 0, 0, -62, 0, 0, 0,
+            -93, 99, 102, 109, 116, 100, 110, 111, 110, 101, 103, 97, 116, 116, 83, 116, 109, 116,
+            -96, 104, 97, 117, 116, 104, 68, 97, 116, 97, 88, -92, 17, -54, 103, 117, 94, -24, 15,
+            -48, -108, 31, 8, 99, -50, -54, -38, 125, -48, -25, 91, 114, 111, 41, 32, 108, -21, -25,
+            66, -10, -32, -107, -62, 102, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 32, 55, 120, 49, -25, 87, -93, -128, -61, -100, -109, -50, -98, -43, 50, -58,
+            -68, -48, -87, -33, -90, 85, -43, -41, -94, 125, -53, -120, -2, 21, -73, 121, 105, -91,
+            1, 2, 3, 38, 32, 1, 33, 88, 32, -44, 11, -114, -24, 96, -51, 111, 103, 66, 47, -29, 52,
+            75, -3, -57, 84, -63, 121, -95, -102, -54, -112, -12, 115, -82, -100, -11, 86, -118,
+            -106, -3, 8, 34, 88, 32, -35, 48, -66, 126, 31, 89, 68, -128, 30, 121, 109, 105, 78, 6,
+            90, 36, -58, -12, -69, 67, -56, 102, -27, -125, -20, 4, -49, 64, 8, -77, -22, -80, 0, 0,
+            0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 99, 0, 0, 0, 91, 0, 0, 0,
+            48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61, 3, 1, 7,
+            3, 66, 0, 4, -44, 11, -114, -24, 96, -51, 111, 103, 66, 47, -29, 52, 75, -3, -57, 84,
+            -63, 121, -95, -102, -54, -112, -12, 115, -82, -100, -11, 86, -118, -106, -3, 8, -35,
+            48, -66, 126, 31, 89, 68, -128, 30, 121, 109, 105, 78, 6, 90, 36, -58, -12, -69, 67,
+            -56, 102, -27, -125, -20, 4, -49, 64, 8, -77, -22, -80, 0, 0, 0, 0, 0};
+
+    // Serialized assertion response converted from JSON received from the Credential Manager API.
+    private static final byte[] TEST_SERIALIZED_CREDMAN_GET_CREDENTIAL_RESPONSE = new byte[] {80, 0,
+            0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -40, 0, 0, 0, 0, 0,
+            0, 0, 32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0,
+            32, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0,
+            0, 0, 0, 0, 23, 0, 0, 0, 15, 0, 0, 0, 98, 111, 98, 64, 101, 120, 97, 109, 112, 108, 101,
+            46, 99, 111, 109, 0, 40, 0, 0, 0, 32, 0, 0, 0, 106, -74, -93, 37, 53, -69, 59, -77, -36,
+            -5, -4, 68, 32, 13, 51, -17, -4, 125, -52, -62, 73, -54, -54, 28, 60, 55, 6, 39, -55,
+            -67, 5, 81, 8, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 37, 0, 0, 0, 116, -90, -22, -110, 19,
+            -55, -100, 47, 116, -78, 36, -110, -77, 32, -49, 64, 38, 42, -108, -63, -87, 80, -96,
+            57, 127, 41, 37, 11, 96, -124, 30, -16, 29, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 70, 0, 0,
+            0, 48, 68, 2, 32, 38, 99, -77, -120, -102, -49, -28, -77, -66, 30, -123, -27, -65, -113,
+            4, -92, -110, -110, 120, 75, -38, -69, -117, 69, -123, 64, 54, 60, 54, -16, -17, -17, 2,
+            32, 60, 114, -37, 38, -92, -4, -16, -2, 119, 95, 41, -16, 59, -72, 18, -105, -125, 119,
+            -88, -7, -2, -38, 100, -25, -4, -111, 11, -52, 1, -20, -121, 115, 0, 0, 19, 0, 0, 0, 11,
+            0, 0, 0, 85, 50, 104, 107, 97, 72, 78, 111, 99, 50, 103, 0, 0, 0, 0, 0};
+
     // TEST_USER_HANDLE is the user ID contained within `TEST_DISCOVERABLE_CREDENTIAL_ASSERTION`.
     public static final byte[] TEST_USER_HANDLE = "bob@example.com".getBytes(UTF_8);
 
@@ -299,6 +355,10 @@
     private static final int[] TEST_USER_VERIFICATION_METHOD = new int[] {0x00000002, 0x00000200};
     private static final short[] TEST_KEY_PROTECTION_TYPE = new short[] {0x0002, 0x0001};
     private static final short[] TEST_MATCHER_PROTECTION_TYPE = new short[] {0x0004, 0x0001};
+    private static final String TEST_SERIALIZED_MAKE_CREDENTIAL_REQUEST_JSON =
+            "{serialized_make_request}";
+    private static final String TEST_SERIALIZED_GET_ASSERTION_REQUEST_JSON =
+            "{serialized_get_request}";
 
     /**
      * Builds a test intent to be returned by a successful call to makeCredential.
@@ -608,4 +668,98 @@
         credential.mIsPayment = false;
         return credential;
     }
+
+    public static void mockFido2CredentialRequestJni(JniMocker mocker) {
+        Fido2CredentialRequest.Natives fido2CredentialRequestJni =
+                new Fido2CredentialRequest.Natives() {
+                    @Override
+                    public String createOptionsToJson(ByteBuffer serializedOptions) {
+                        return TEST_SERIALIZED_MAKE_CREDENTIAL_REQUEST_JSON;
+                    }
+
+                    @Override
+                    public byte[] makeCredentialResponseFromJson(String json) {
+                        return TEST_SERIALIZED_CREDMAN_MAKE_CREDENTIAL_RESPONSE;
+                    }
+
+                    @Override
+                    public String getOptionsToJson(ByteBuffer serializedOptions) {
+                        return TEST_SERIALIZED_GET_ASSERTION_REQUEST_JSON;
+                    }
+
+                    @Override
+                    public byte[] getCredentialResponseFromJson(String json) {
+                        return TEST_SERIALIZED_CREDMAN_GET_CREDENTIAL_RESPONSE;
+                    }
+                };
+        mocker.mock(Fido2CredentialRequestJni.TEST_HOOKS, fido2CredentialRequestJni);
+    }
+
+    public static AuthenticatorCallback getAuthenticatorCallback() {
+        return new AuthenticatorCallback();
+    }
+
+    /**
+     * Callback class to pass to Fido2CredentialRequest WebAuthn operations.
+     */
+    public static class AuthenticatorCallback {
+        private Integer mStatus;
+        private MakeCredentialAuthenticatorResponse mMakeCredentialResponse;
+        private GetAssertionAuthenticatorResponse mGetAssertionAuthenticatorResponse;
+        private List<byte[]> mGetMatchingCredentialIdsResponse;
+
+        // Signals when request is complete.
+        private final ConditionVariable mDone = new ConditionVariable();
+
+        AuthenticatorCallback() {}
+
+        public void onRegisterResponse(int status, MakeCredentialAuthenticatorResponse response) {
+            assert mStatus == null;
+            mStatus = status;
+            mMakeCredentialResponse = response;
+            unblock();
+        }
+
+        public void onSignResponse(int status, GetAssertionAuthenticatorResponse response) {
+            assert mStatus == null;
+            mStatus = status;
+            mGetAssertionAuthenticatorResponse = response;
+            unblock();
+        }
+
+        public void onGetMatchingCredentialIds(List<byte[]> matchingCredentialIds) {
+            mGetMatchingCredentialIdsResponse = matchingCredentialIds;
+            unblock();
+        }
+
+        public void onError(int status) {
+            assert mStatus == null;
+            mStatus = status;
+            unblock();
+        }
+
+        public Integer getStatus() {
+            return mStatus;
+        }
+
+        public MakeCredentialAuthenticatorResponse getMakeCredentialResponse() {
+            return mMakeCredentialResponse;
+        }
+
+        public GetAssertionAuthenticatorResponse getGetAssertionResponse() {
+            return mGetAssertionAuthenticatorResponse;
+        }
+
+        public List<byte[]> getGetMatchingCredentialIdsResponse() {
+            return mGetMatchingCredentialIdsResponse;
+        }
+
+        public void blockUntilCalled() {
+            mDone.block();
+        }
+
+        private void unblock() {
+            mDone.open();
+        }
+    }
 }
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
index b66b10ad..a56fe6c 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
@@ -76,6 +76,8 @@
     static final String CREDENTIAL_EXISTS_ERROR_MSG =
             "One of the excluded credentials exists on the local device";
     static final String LOW_LEVEL_ERROR_MSG = "Low level error 0x6a80";
+    static final String CRED_MAN_EXCEPTION_TYPE_USER_CANCEL =
+            "android.credentials.CreateCredentialException.TYPE_USER_CANCELED";
 
     private static Boolean sIsCredManEnabled;
 
@@ -89,8 +91,13 @@
     private boolean mAppIdExtensionUsed;
     private boolean mEchoCredProps;
     private WebAuthnBrowserBridge mBrowserBridge;
+    private Object mCredentialManagerServiceForTesting;
+    private Class mCredManCreateRequestBuilderClassForTesting;
+    private Class mCredManGetRequestBuilderClassForTesting;
+    private Class mCredManCredentialOptionBuilderClassForTesting;
     private boolean mAttestationAcceptable;
     private boolean mIsCrossOrigin;
+    private boolean mOverrideVersionCheckForTesting;
 
     private enum ConditionalUiState {
         NONE,
@@ -131,15 +138,17 @@
         mMakeCredentialCallback = null;
     }
 
+    @OptIn(markerClass = androidx.core.os.BuildCompat.PrereleaseSdkCheck.class)
     private boolean isCredManEnabled() {
         if (sIsCredManEnabled == null) {
             sIsCredManEnabled =
-                    DeviceFeatureList.isEnabled(DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN);
+                    DeviceFeatureList.isEnabled(DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN)
+                    && (BuildCompat.isAtLeastU() || mOverrideVersionCheckForTesting);
         }
         return sIsCredManEnabled;
     }
 
-    @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+    @SuppressWarnings("NewApi")
     public void handleMakeCredentialRequest(PublicKeyCredentialCreationOptions options,
             RenderFrameHost frameHost, Origin origin, MakeCredentialResponseCallback callback,
             FidoErrorResponseCallback errorCallback) {
@@ -167,7 +176,7 @@
                 || options.authenticatorSelection.residentKey == ResidentKeyRequirement.DISCOURAGED;
         mEchoCredProps = options.credProps;
 
-        if (isCredManEnabled() && BuildCompat.isAtLeastU()) {
+        if (isCredManEnabled()) {
             makeCredentialViaCredMan(options, origin);
             return;
         }
@@ -207,7 +216,7 @@
         returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
     }
 
-    @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+    @SuppressWarnings("NewApi")
     public void handleGetAssertionRequest(PublicKeyCredentialRequestOptions options,
             RenderFrameHost frameHost, Origin callerOrigin, PaymentOptions payment,
             GetAssertionResponseCallback callback, FidoErrorResponseCallback errorCallback) {
@@ -244,7 +253,7 @@
         byte[] clientDataHash = null;
 
         // Payments should still go through Google Play Services.
-        if (payment == null && isCredManEnabled() && BuildCompat.isAtLeastU()) {
+        if (payment == null && isCredManEnabled()) {
             if (options.isConditional) {
                 mConditionalUiState = ConditionalUiState.WAITING_FOR_CREDENTIAL_LIST;
                 prefetchCredentialsViaCredMan(
@@ -395,6 +404,20 @@
         mBrowserBridge = bridge;
     }
 
+    @VisibleForTesting
+    public void setOverrideVersionCheckForTesting(boolean override) {
+        mOverrideVersionCheckForTesting = override;
+    }
+
+    @VisibleForTesting
+    public void setCredManClassesForTesting(Object credentialManager, Class createRequestBuilder,
+            Class getRequestBuilder, Class credentialOptionBuilder) {
+        mCredentialManagerServiceForTesting = credentialManager;
+        mCredManCreateRequestBuilderClassForTesting = createRequestBuilder;
+        mCredManGetRequestBuilderClassForTesting = getRequestBuilder;
+        mCredManCredentialOptionBuilderClassForTesting = credentialOptionBuilder;
+    }
+
     private boolean apiAvailable() {
         return ExternalAuthUtils.getInstance().canUseGooglePlayServices(
                 new UserRecoverableErrorHandler.Silent());
@@ -686,7 +709,7 @@
     }
 
     @VisibleForTesting
-    public String convertOriginToString(Origin origin) {
+    public static String convertOriginToString(Origin origin) {
         // Wrapping with GURLUtils.getOrigin() in order to trim default ports.
         return GURLUtils.getOrigin(
                 origin.getScheme() + "://" + origin.getHost() + ":" + origin.getPort());
@@ -706,12 +729,42 @@
         }
     }
 
+    @SuppressWarnings("WrongConstant")
+    Object credentialManagerService(Context context) {
+        if (mCredentialManagerServiceForTesting != null) {
+            return mCredentialManagerServiceForTesting;
+        }
+        // TODO: switch "credential" to `Context.CREDENTIAL_SERVICE` and remove the
+        // `@SuppressWarnings` when the Android U SDK is available.
+        return context.getSystemService("credential");
+    }
+
+    Class credManCreateRequestBuilderClass() throws ClassNotFoundException {
+        if (mCredManCreateRequestBuilderClassForTesting != null) {
+            return mCredManCreateRequestBuilderClassForTesting;
+        }
+        return Class.forName("android.credentials.CreateCredentialRequest$Builder");
+    }
+
+    Class credManGetRequestBuilderClass() throws ClassNotFoundException {
+        if (mCredManGetRequestBuilderClassForTesting != null) {
+            return mCredManGetRequestBuilderClassForTesting;
+        }
+        return Class.forName("android.credentials.GetCredentialRequest$Builder");
+    }
+
+    Class credManCredentialOptionBuilderClass() throws ClassNotFoundException {
+        if (mCredManCredentialOptionBuilderClassForTesting != null) {
+            return mCredManCredentialOptionBuilderClassForTesting;
+        }
+        return Class.forName("android.credentials.CredentialOption$Builder");
+    }
+
     /**
      * Create a credential using the Android 14 CredMan API.
      * TODO: update the version code to U when Chromium builds with Android 14 SDK.
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    @SuppressWarnings("WrongConstant")
     private void makeCredentialViaCredMan(
             PublicKeyCredentialCreationOptions options, Origin origin) {
         final String requestAsJson =
@@ -744,8 +797,7 @@
                 String errorType = getCredManExceptionType(e);
                 Log.e(TAG, "CredMan CreateCredential call failed: %s",
                         errorType + " (" + e.getMessage() + ")");
-                if (errorType.equals(
-                            "android.credentials.CreateCredentialException.TYPE_USER_CANCELED")) {
+                if (errorType.equals(CRED_MAN_EXCEPTION_TYPE_USER_CANCEL)) {
                     returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
                 } else {
                     // Includes:
@@ -795,8 +847,7 @@
         };
 
         try {
-            final Class createCredentialRequestBuilder =
-                    Class.forName("android.credentials.CreateCredentialRequest$Builder");
+            final Class createCredentialRequestBuilder = credManCreateRequestBuilderClass();
             final Object builder =
                     createCredentialRequestBuilder
                             .getConstructor(String.class, Bundle.class, Bundle.class)
@@ -808,9 +859,7 @@
             builderClass.getMethod("setOrigin", String.class)
                     .invoke(builder, convertOriginToString(origin));
             final Object request = builderClass.getMethod("build").invoke(builder);
-            // TODO: switch "credential" to `Context.CREDENTIAL_SERVICE` and remove the
-            // `@SuppressWarnings` when the Android U SDK is available.
-            final Object manager = context.getSystemService("credential");
+            final Object manager = credentialManagerService(context);
             try {
                 manager.getClass()
                         .getMethod("createCredential", Context.class, request.getClass(),
@@ -848,7 +897,6 @@
      * TODO: update the version code to U when Chromium builds with Android 14 SDK.
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    @SuppressWarnings("WrongConstant")
     private void getCredentialViaCredMan(PublicKeyCredentialRequestOptions options, Origin origin) {
         final Context context = ContextUtils.getApplicationContext();
 
@@ -860,8 +908,7 @@
                 String errorType = getCredManExceptionType(getCredentialException);
                 Log.e(TAG, "CredMan getCredential call failed: %s",
                         errorType + " (" + getCredentialException.getMessage() + ")");
-                if (errorType.equals(
-                            "android.credentials.GetCredentialException.TYPE_USER_CANCELED")) {
+                if (errorType.equals(CRED_MAN_EXCEPTION_TYPE_USER_CANCEL)) {
                     returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
                 } else {
                     // Includes:
@@ -920,10 +967,7 @@
                 returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
                 return;
             }
-
-            // TODO: switch "credential" to `Context.CREDENTIAL_SERVICE` and remove the
-            // `@SuppressWarnings` when the Android U SDK is available.
-            final Object manager = context.getSystemService("credential");
+            final Object manager = credentialManagerService(context);
             try {
                 manager.getClass()
                         .getMethod("getCredential", Context.class, getCredentialRequest.getClass(),
@@ -961,7 +1005,6 @@
      * TODO: update the version code to U when Chromium builds with Android 14 SDK.
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    @SuppressWarnings("WrongConstant")
     private void prefetchCredentialsViaCredMan(PublicKeyCredentialRequestOptions options,
             Origin origin, String callerOriginString, byte[] clientDataHash) {
         final Context context = ContextUtils.getApplicationContext();
@@ -1038,9 +1081,7 @@
                 return;
             }
 
-            // TODO: switch "credential" to `Context.CREDENTIAL_SERVICE` and remove the
-            // `@SuppressWarnings` when the Android U SDK is available.
-            final Object manager = context.getSystemService("credential");
+            final Object manager = credentialManagerService(context);
             manager.getClass()
                     .getMethod("prepareGetCredential", getCredentialRequest.getClass(),
                             android.os.CancellationSignal.class,
@@ -1074,8 +1115,7 @@
         // Build the CredentialOption:
         Object credentialOption;
         try {
-            final Class<?> credentialOptionBuilderClass =
-                    Class.forName("android.credentials.CredentialOption$Builder");
+            final Class<?> credentialOptionBuilderClass = credManCredentialOptionBuilderClass();
             final Object credentialOptionBuilder =
                     credentialOptionBuilderClass
                             .getConstructor(String.class, Bundle.class, Bundle.class)
@@ -1096,8 +1136,7 @@
         }
 
         // Build the GetCredentialRequest:
-        final Class<?> getCredentialRequestBuilderClass =
-                Class.forName("android.credentials.GetCredentialRequest$Builder");
+        final Class<?> getCredentialRequestBuilderClass = credManGetRequestBuilderClass();
         final Object getCredentialRequestBuilderObject =
                 getCredentialRequestBuilderClass.getConstructor(Bundle.class)
                         .newInstance(new Bundle());
@@ -1143,8 +1182,9 @@
         return messageDigest.digest();
     }
 
+    @VisibleForTesting
     @NativeMethods
-    interface Natives {
+    public interface Natives {
         String createOptionsToJson(ByteBuffer serializedOptions);
         byte[] makeCredentialResponseFromJson(String json);
         String getOptionsToJson(ByteBuffer serializedOptions);
diff --git a/components/webdata/common/PRESUBMIT.py b/components/webdata/common/PRESUBMIT.py
new file mode 100644
index 0000000..bdf81c01
--- /dev/null
+++ b/components/webdata/common/PRESUBMIT.py
@@ -0,0 +1,79 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script for the WebDatabase.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools."""
+
+PRESUBMIT_VERSION = '2.0.0'
+
+def CheckCurrentDBVersionUpdatedCorrectly(input_api, output_api):
+  """Checks that whenever the WebDatabase::kCurrentVersionNumber is updated,
+     - WebDatabaseMigrationTest::kCurrentTestedVersionNumber is updated
+       accordingly.
+     - A version_x.sql file for the previous version is added."""
+
+  def FindAffectedFile(path):
+    return next(iter(input_api.change.AffectedTestableFiles(
+      file_filter = lambda f: f.LocalPath() == path)), None)
+
+  # Helper functions to extract integer constants from an affected file `f` via
+  # a regex `pattern`, those first capture group corresponds to the integer.
+  # `new_content` indicates whether the old/new content of `f` is searched.
+  def FindInt(f, pattern, new_content = True):
+    content = "".join(f.NewContents() if new_content else f.OldContents())
+    match = pattern.search(content)
+    return int(match.group(1)) if match else None
+  def FindCppInt(f, name, new_content = True):
+    return FindInt(f, input_api.re.compile("%s = ([0-9]+)" % name), new_content)
+
+  # Determine if the version changed.
+  webdb_file = FindAffectedFile("components/webdata/common/web_database.cc")
+  if not webdb_file:
+    return []
+  version_var_name = "WebDatabase::kCurrentVersionNumber"
+  version = FindCppInt(webdb_file, version_var_name, new_content=True)
+  if version == FindCppInt(webdb_file, version_var_name, new_content=False):
+    return []
+
+  # Find the current tested version and check that it matches `version`.
+  migration_test_path = \
+    "components/webdata/common/web_database_migration_unittest.cc"
+  migration_test_file = FindAffectedFile(migration_test_path)
+  if (not migration_test_file or version != FindCppInt(migration_test_file,
+      "WebDatabaseMigrationTest::kCurrentTestedVersionNumber")):
+    return [output_api.PresubmitError("""
+Whenever WebDatabase::kCurrentVersionNumber is updated,
+WebDatabaseMigrationTest::kCurrentTestedVersionNumber in %s
+needs to be updated.""" % migration_test_path)]
+
+  # Check that a golden file for the previous version was added, and that its
+  # version is set correctly.
+  golden_file_dir = "components/test/data/web_database/"
+  golden_file = FindAffectedFile(
+    "%s/version_%d.sql" % (golden_file_dir, version-1))
+  sql_version_pattern = input_api.re.compile(
+      "INSERT INTO meta VALUES\('version','([0-9]+')\)")
+  if not golden_file or version-1 != FindInt(golden_file, sql_version_pattern):
+    return [output_api.PresubmitError("""
+A golden file for version {0} needs to be added in {1}.
+There are generally two ways of doing so:
+- Copy version_{2}.sql. Update the version to {0} and make any changes that were
+  made in version {0} (new tables, columns, etc). You can find out what
+  changed by either looking at the WebDatabaseMigrationTest, or by finding
+  the relevant CL (blame on version_{2}.sql) and looking at the migration logic.
+- Generate the file from scratch:
+  - Launch Chrome with WebDatabase version {0}. That is, without any of the new
+    changes that version {3} introduces.
+    ./out/Default/chrome --user-data-dir=/tmp/sql
+    No need to complete the first run - closing Chrome immediately is fine.
+  - Run sqlite3 '/tmp/sql/Default/Web Data'
+        .output version_{0}.sql
+        .dump
+        .exit
+  - Remove any INSERT statements to tables other than "meta" from
+    version_{0}.sql.""".format(version-1, golden_file_dir, version-2, version))]
+
+  return []
diff --git a/content/browser/accessibility/ax_platform_node_textprovider_win_browsertest.cc b/content/browser/accessibility/ax_platform_node_textprovider_win_browsertest.cc
index 0da36ae..d4e1ced 100644
--- a/content/browser/accessibility/ax_platform_node_textprovider_win_browsertest.cc
+++ b/content/browser/accessibility/ax_platform_node_textprovider_win_browsertest.cc
@@ -167,7 +167,7 @@
       <!DOCTYPE html>
       <html>
         <body>
-          <div style='overflow: hidden visible; width: 10em; height: 2.4em;'>
+          <div style='overflow: hidden; width: 10em; height: 2.1em;'>
             <span style='white-space: pre-line;'>AAA BBB
               CCCCCC
               DDDDDD</span>
@@ -201,4 +201,84 @@
   text_provider_ranges.Reset();
 }
 
+IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextProviderWinBrowserTest,
+                       GetVisibleRangesInContentEditable) {
+  LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML(
+      <!DOCTYPE html>
+      <html>
+        <body>
+          <div contenteditable="true">
+            <p>hello</p>
+          </div>
+        </body>
+      </html>
+  )HTML"));
+
+  auto* gc_node = FindNode(ax::mojom::Role::kGenericContainer, "hello");
+
+  ASSERT_NE(nullptr, gc_node);
+  EXPECT_EQ(1u, gc_node->PlatformChildCount());
+
+  ComPtr<ITextProvider> text_provider;
+  GetTextProviderFromTextNode(text_provider, gc_node);
+
+  base::win::ScopedSafearray text_provider_ranges;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->GetVisibleRanges(text_provider_ranges.Receive()));
+  ASSERT_UIA_SAFEARRAY_OF_TEXTRANGEPROVIDER(text_provider_ranges.Get(), 1U);
+
+  ITextRangeProvider** array_data;
+  ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
+      text_provider_ranges.Get(), reinterpret_cast<void**>(&array_data)));
+
+  // If the `embedded_object_character` was being exposed, the search for this
+  // string would fail.
+  // We have to use `FindText` instead of the `EXPECT_UIA_TEXTRANGE_EQ` macro
+  // since that macro uses `GetText` API which hardcodes the
+  // `AXEmbeddedObjectCharacter` to be exposed, which then in this case would
+  // mess up the text range. Filing a bug for `GetText`. CRBug: 1445692
+  base::win::ScopedBstr find_string(L"hello");
+  Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider_found;
+  EXPECT_HRESULT_SUCCEEDED(array_data[0]->FindText(
+      find_string.Get(), false, false, &text_range_provider_found));
+  ASSERT_TRUE(text_range_provider_found.Get());
+  ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(text_provider_ranges.Get()));
+  text_provider_ranges.Reset();
+}
+
+IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextProviderWinBrowserTest,
+                       GetVisibleRangesForTextSlightlyOutsideContainer) {
+  LoadInitialAccessibilityTreeFromHtml(std::string(R"HTML(
+      <!DOCTYPE html>
+      <html>
+        <body>
+          <div role='textbox' contenteditable="true" style='height: 10px;'>
+            <span style='height:20px; display:inline-block'>hello</span>
+          </div>
+        </body>
+      </html>
+  )HTML"));
+
+  auto* gc_node = FindNode(ax::mojom::Role::kTextField, "hello");
+
+  ASSERT_NE(nullptr, gc_node);
+  EXPECT_EQ(1u, gc_node->PlatformChildCount());
+
+  ComPtr<ITextProvider> text_provider;
+  GetTextProviderFromTextNode(text_provider, gc_node);
+
+  base::win::ScopedSafearray text_provider_ranges;
+  EXPECT_HRESULT_SUCCEEDED(
+      text_provider->GetVisibleRanges(text_provider_ranges.Receive()));
+  ASSERT_UIA_SAFEARRAY_OF_TEXTRANGEPROVIDER(text_provider_ranges.Get(), 1U);
+
+  ITextRangeProvider** array_data;
+  ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
+      text_provider_ranges.Get(), reinterpret_cast<void**>(&array_data)));
+
+  EXPECT_UIA_TEXTRANGE_EQ(array_data[0], L"hello");
+  ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(text_provider_ranges.Get()));
+  text_provider_ranges.Reset();
+}
+
 }  // namespace content
diff --git a/content/browser/android/selection/selection_popup_controller.cc b/content/browser/android/selection/selection_popup_controller.cc
index 814a374..1dd0bc6e 100644
--- a/content/browser/android/selection/selection_popup_controller.cc
+++ b/content/browser/android/selection/selection_popup_controller.cc
@@ -7,6 +7,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "cc/slim/features.h"
 #include "content/browser/android/selection/composited_touch_handle_drawable.h"
 #include "content/browser/gpu/gpu_data_manager_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
@@ -15,9 +16,11 @@
 #include "content/common/features.h"
 #include "content/public/android/content_jni_headers/SelectionPopupControllerImpl_jni.h"
 #include "content/public/browser/context_menu_params.h"
+#include "content/public/common/content_features.h"
 #include "third_party/blink/public/common/context_menu_data/edit_flags.h"
 #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
+#include "ui/gfx/android/android_surface_control_compat.h"
 #include "ui/gfx/geometry/point_conversions.h"
 
 using base::android::AttachCurrentThread;
@@ -28,6 +31,18 @@
 
 namespace content {
 
+namespace {
+
+bool IsAndroidSurfaceControlMagnifierEnabled() {
+  static bool enabled =
+      gfx::SurfaceControl::SupportsSurfacelessControl() &&
+      features::IsSlimCompositorEnabled() &&
+      base::FeatureList::IsEnabled(features::kAndroidSurfaceControlMagnifier);
+  return enabled;
+}
+
+}  // namespace
+
 static jboolean
 JNI_SelectionPopupControllerImpl_IsMagnifierWithSurfaceControlSupported(
     JNIEnv* env) {
diff --git a/content/browser/attribution_reporting/attribution_config.h b/content/browser/attribution_reporting/attribution_config.h
index af63ac7..a49d936e 100644
--- a/content/browser/attribution_reporting/attribution_config.h
+++ b/content/browser/attribution_reporting/attribution_config.h
@@ -98,8 +98,8 @@
 
     // Default constants for the report delivery time to be used when declaring
     // field trial params.
-    static constexpr base::TimeDelta kDefaultMinDelay = base::Minutes(10);
-    static constexpr base::TimeDelta kDefaultDelaySpan = base::Minutes(50);
+    static constexpr base::TimeDelta kDefaultMinDelay = base::TimeDelta();
+    static constexpr base::TimeDelta kDefaultDelaySpan = base::Minutes(10);
 
     // Controls the report delivery time.
     base::TimeDelta min_delay = kDefaultMinDelay;
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 203ae5325..75266f2 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -710,7 +710,7 @@
           attribution_reporting::AggregatableValues(),
           ::aggregation_service::mojom::AggregationCoordinator::kDefault,
           attribution_reporting::mojom::SourceRegistrationTimeConfig::
-              kInclude))));
+              kExclude))));
 }
 
 IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc b/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc
index 39963d1b..3ed3c3c2 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_delegate_impl_unittest.cc
@@ -231,8 +231,7 @@
   base::Time trigger_time = base::Time::Now();
   EXPECT_THAT(
       AttributionStorageDelegateImpl().GetAggregatableReportTime(trigger_time),
-      AllOf(Ge(trigger_time + base::Minutes(10)),
-            Lt(trigger_time + base::Hours(1))));
+      AllOf(Ge(trigger_time), Lt(trigger_time + base::Minutes(10))));
 }
 
 TEST(AttributionStorageDelegateImplTest, NewReportID_IsValidGUID) {
@@ -657,8 +656,7 @@
   base::Time trigger_time = base::Time::Now();
   EXPECT_THAT(
       AttributionStorageDelegateImpl().GetAggregatableReportTime(trigger_time),
-      AllOf(Ge(trigger_time + base::Minutes(10)),
-            Lt(trigger_time + base::Hours(1))));
+      AllOf(Ge(trigger_time), Lt(trigger_time + base::Minutes(10))));
 }
 
 TEST(AttributionStorageDelegateImplTest,
diff --git a/content/browser/back_forward_cache_basics_browsertest.cc b/content/browser/back_forward_cache_basics_browsertest.cc
index 214519a..6e9f8ca 100644
--- a/content/browser/back_forward_cache_basics_browsertest.cc
+++ b/content/browser/back_forward_cache_basics_browsertest.cc
@@ -232,8 +232,8 @@
   EXPECT_THAT(
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
-          NotRestoredReasons(NotRestoredReason::kRelatedActiveContentsExist,
-                             NotRestoredReason::kBrowsingInstanceNotSwapped),
+          NotRestoredReasons({NotRestoredReason::kRelatedActiveContentsExist,
+                              NotRestoredReason::kBrowsingInstanceNotSwapped}),
           BlockListedFeatures()));
 
   // 4) Make the popup drop the window.opener connection. It happens when the
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 12f81c39..aebc59d 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -2430,9 +2430,9 @@
   // a
   EXPECT_THAT(can_store_result.tree_reasons->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures),
+                  NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
                   BlockListedFeatures(
-                      blink::scheduler::WebSchedulerTrackedFeature::kDummy)));
+                      {blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
   // a->a
   EXPECT_THAT(
       can_store_result.tree_reasons->GetChildren().at(0)->GetDocumentResult(),
@@ -2442,9 +2442,9 @@
   EXPECT_THAT(
       can_store_result.tree_reasons->GetChildren().at(1)->GetDocumentResult(),
       MatchesDocumentResult(
-          NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures),
+          NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
           BlockListedFeatures(
-              blink::scheduler::WebSchedulerTrackedFeature::kDummy)));
+              {blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
   // a->c
   EXPECT_THAT(
       can_store_result.tree_reasons->GetChildren().at(2)->GetDocumentResult(),
@@ -2476,7 +2476,7 @@
                     FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kJavaScriptExecution),
+                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
                   BlockListedFeatures()));
 }
 
@@ -2512,7 +2512,7 @@
   // Subframe result in the tree contains the reason.
   EXPECT_THAT(GetTreeResult()->GetChildren().at(0)->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kJavaScriptExecution),
+                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
                   BlockListedFeatures()));
 }
 
@@ -2560,7 +2560,7 @@
                   .at(0)
                   ->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kJavaScriptExecution),
+                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
                   BlockListedFeatures()));
 }
 
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc
index f6f147a0..4f3dccc 100644
--- a/content/browser/back_forward_cache_features_browsertest.cc
+++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -1929,12 +1929,12 @@
   EXPECT_THAT(
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
-          NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures,
-                             NotRestoredReason::kBrowsingInstanceNotSwapped),
+          NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures,
+                              NotRestoredReason::kBrowsingInstanceNotSwapped}),
           BlockListedFeatures(
-              blink::scheduler::WebSchedulerTrackedFeature::kDummy,
-              blink::scheduler::WebSchedulerTrackedFeature::
-                  kBroadcastChannel)));
+              {blink::scheduler::WebSchedulerTrackedFeature::kDummy,
+               blink::scheduler::WebSchedulerTrackedFeature::
+                   kBroadcastChannel})));
 }
 
 // Tests which blocklisted features are tracked in the metrics when we used a
diff --git a/content/browser/back_forward_cache_internal_browsertest.cc b/content/browser/back_forward_cache_internal_browsertest.cc
index 78496fc4..72ff1d5 100644
--- a/content/browser/back_forward_cache_internal_browsertest.cc
+++ b/content/browser/back_forward_cache_internal_browsertest.cc
@@ -873,10 +873,11 @@
                     FROM_HERE);
 
   // Make sure that the tree reasons match the flattened reasons.
-  EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
-              MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kTimeoutPuttingInCache),
-                  BlockListedFeatures()));
+  EXPECT_THAT(
+      GetTreeResult()->GetDocumentResult(),
+      MatchesDocumentResult(
+          NotRestoredReasons({NotRestoredReason::kTimeoutPuttingInCache}),
+          BlockListedFeatures()));
 }
 
 // Test the race condition where a document is evicted from the BackForwardCache
@@ -1148,7 +1149,7 @@
   // Make sure that the tree reasons match the flattened reasons.
   EXPECT_THAT(
       GetTreeResult()->GetDocumentResult(),
-      MatchesDocumentResult(NotRestoredReasons(NotRestoredReason::kTimeout),
+      MatchesDocumentResult(NotRestoredReasons({NotRestoredReason::kTimeout}),
                             BlockListedFeatures()));
 }
 
@@ -4195,9 +4196,9 @@
   // 6. Check the blocked reasons are set correctly on the fenced frame.
   EXPECT_THAT(child_c_results->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures),
+                  NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
                   BlockListedFeatures(
-                      blink::scheduler::WebSchedulerTrackedFeature::kDummy)));
+                      {blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
 
   // 7. Ensure that the web exposed reasons do not replicate any of
   // fenced frame results.
diff --git a/content/browser/back_forward_cache_no_store_browsertest.cc b/content/browser/back_forward_cache_no_store_browsertest.cc
index cd24a7c..4d9d46a 100644
--- a/content/browser/back_forward_cache_no_store_browsertest.cc
+++ b/content/browser/back_forward_cache_no_store_browsertest.cc
@@ -143,7 +143,7 @@
   // Make sure that the tree result also has the same reason.
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -199,7 +199,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -248,7 +248,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
   RenderFrameHostImplWrapper rfh_a_2(current_frame_host());
   rfh_a_2->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
@@ -264,7 +264,7 @@
                     FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -312,7 +312,7 @@
                     FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -347,8 +347,8 @@
                     {}, {}, {}, {}, FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kJavaScriptExecution,
-                                     NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution,
+                                      NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -386,10 +386,10 @@
   EXPECT_THAT(
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
-          NotRestoredReasons(NotRestoredReason::kBlocklistedFeatures,
-                             NotRestoredReason::kCacheControlNoStore),
-          BlockListedFeatures(blink::scheduler::WebSchedulerTrackedFeature::
-                                  kBroadcastChannel)));
+          NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures,
+                              NotRestoredReason::kCacheControlNoStore}),
+          BlockListedFeatures({blink::scheduler::WebSchedulerTrackedFeature::
+                                   kBroadcastChannel})));
 }
 
 // Test that a page with cache-control:no-store records eviction reasons along
@@ -423,8 +423,8 @@
                     {}, {}, {}, {}, FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kJavaScriptExecution,
-                                     NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kJavaScriptExecution,
+                                      NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -508,7 +508,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -567,7 +567,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
@@ -632,7 +632,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 
   RenderFrameHostImplWrapper rfh_a_2(current_frame_host());
@@ -649,7 +649,7 @@
                     FROM_HERE);
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
-                  NotRestoredReasons(NotRestoredReason::kCacheControlNoStore),
+                  NotRestoredReasons({NotRestoredReason::kCacheControlNoStore}),
                   BlockListedFeatures()));
 }
 
@@ -1050,7 +1050,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1108,7 +1108,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
@@ -1192,7 +1192,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1235,7 +1235,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1283,7 +1283,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1372,7 +1372,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1417,7 +1417,7 @@
   EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
               MatchesDocumentResult(
                   NotRestoredReasons(
-                      NotRestoredReason::kCacheControlNoStoreCookieModified),
+                      {NotRestoredReason::kCacheControlNoStoreCookieModified}),
                   BlockListedFeatures()));
 }
 
@@ -1553,7 +1553,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
@@ -1610,7 +1610,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
@@ -1699,7 +1699,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
@@ -1763,7 +1763,7 @@
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
           NotRestoredReasons(
-              NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified),
+              {NotRestoredReason::kCacheControlNoStoreHTTPOnlyCookieModified}),
           BlockListedFeatures()));
 }
 
diff --git a/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc b/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
index 3c9f2d6..5f7d6fde 100644
--- a/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
+++ b/content/browser/back_forward_cache_not_restored_reasons_browsertest.cc
@@ -310,8 +310,8 @@
   EXPECT_THAT(
       GetTreeResult()->GetDocumentResult(),
       MatchesDocumentResult(
-          NotRestoredReasons(NotRestoredReason::kRelatedActiveContentsExist,
-                             NotRestoredReason::kBrowsingInstanceNotSwapped),
+          NotRestoredReasons({NotRestoredReason::kRelatedActiveContentsExist,
+                              NotRestoredReason::kBrowsingInstanceNotSwapped}),
           BlockListedFeatures()));
 
   // Both reasons are recorded and sent to the renderer.
diff --git a/content/browser/devtools/devtools_trust_token_browsertest.cc b/content/browser/devtools/devtools_trust_token_browsertest.cc
index e892e15..cc474d92 100644
--- a/content/browser/devtools/devtools_trust_token_browsertest.cc
+++ b/content/browser/devtools/devtools_trust_token_browsertest.cc
@@ -46,9 +46,10 @@
     EXPECT_GT(tokens.size(), 0ul);
 
     for (const auto& token : tokens) {
-      const std::string* issuer = token.GetDict().FindString("issuerOrigin");
+      const auto& token_dict = token.GetDict();
+      const std::string* issuer = token_dict.FindString("issuerOrigin");
       if (*issuer == issuerOrigin) {
-        const absl::optional<int> actualCount = token.FindIntPath("count");
+        const absl::optional<int> actualCount = token_dict.FindInt("count");
         EXPECT_THAT(actualCount, ::testing::Optional(expectedCount));
         return;
       }
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 84825728..4f3aec06 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -1621,9 +1621,6 @@
               ContentFileChooser;
         case BackForwardCacheDisable::DisabledReasonId::kSerial:
           return Page::BackForwardCacheNotRestoredReasonEnum::ContentSerial;
-        case BackForwardCacheDisable::DisabledReasonId::kFileSystemAccess:
-          return Page::BackForwardCacheNotRestoredReasonEnum::
-              ContentFileSystemAccess;
         case BackForwardCacheDisable::DisabledReasonId::
             kMediaDevicesDispatcherHost:
           return Page::BackForwardCacheNotRestoredReasonEnum::
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index a84e713..0241b27 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -38,6 +38,7 @@
 #include "components/download/public/common/download_features.h"
 #include "components/download/public/common/download_file_factory.h"
 #include "components/download/public/common/download_file_impl.h"
+#include "components/download/public/common/download_item.h"
 #include "components/download/public/common/download_item_impl.h"
 #include "components/download/public/common/download_task_runner.h"
 #include "components/download/public/common/parallel_download_configs.h"
@@ -773,6 +774,13 @@
   base::HistogramTester histogram_tester_;
 };
 
+class ReceivedBytesCountingObserver : public DownloadCountingObserver {
+ private:
+  bool IsCountReached(download::DownloadItem* download, int count) override {
+    return download->GetReceivedBytes() == count;
+  }
+};
+
 // Class to wait for a WebContents to kick off a specified number of
 // navigations.
 class NavigationStartObserver : public WebContentsObserver {
@@ -4651,6 +4659,8 @@
 // Verify that if the second request fails after the beginning request takes
 // over and completes its slice, download should complete.
 IN_PROC_BROWSER_TEST_F(ParallelDownloadTest, MiddleSliceDelayedError) {
+  const int64_t kFileSize = 5097152;
+
   scoped_refptr<TestFileErrorInjector> injector(
       TestFileErrorInjector::Create(DownloadManagerForShell(shell())));
 
@@ -4661,7 +4671,7 @@
   injector->InjectError(err);
   TestDownloadHttpResponse::Parameters parameters;
   parameters.etag = "ABC";
-  parameters.size = 5097152;
+  parameters.size = kFileSize;
   parameters.connection_type = net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1;
   // The 2nd response will be dalyed.
   parameters.SetResponseForRangeRequest(1699000, 2000000, k404Response,
@@ -4682,15 +4692,23 @@
 
   // Wait for the 3rd request to complete first.
   test_response_handler()->WaitUntilCompletion(1);
-  ReceivedSlicesCountingObserver obs;
-  obs.WaitForFinished(download, 2);
+  ReceivedSlicesCountingObserver slices_counting_observer;
+  slices_counting_observer.WaitForFinished(download, 2);
   std::vector<download::DownloadItem::ReceivedSlice> received_slices =
       download->GetReceivedSlices();
   EXPECT_EQ(received_slices[1].offset + received_slices[1].received_bytes,
-            5097152);
-  // Now resume the first request and wait for it to complete.
+            kFileSize);
+
+  // Now resume the first request and wait for it to complete, including writing
+  // the whole file.
   request_pause_handler.Resume();
-  test_response_handler()->WaitUntilCompletion(2);
+  ReceivedBytesCountingObserver bytes_counting_observer;
+  bytes_counting_observer.WaitForFinished(download, kFileSize);
+  // Note that download is not yet completed even though the whole file is
+  // downloaded - second request is not yet processed.
+  EXPECT_EQ(download->GetState(),
+            download::DownloadItem::DownloadState::IN_PROGRESS);
+
   // Dispatch the delayed response, and wait for download to complete.
   test_response_handler()->DispatchDelayedResponses();
   WaitForCompletion(download);
diff --git a/content/browser/file_system_access/file_system_access_file_handle_impl.cc b/content/browser/file_system_access/file_system_access_file_handle_impl.cc
index 144f435..172521c6 100644
--- a/content/browser/file_system_access/file_system_access_file_handle_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_handle_impl.cc
@@ -758,8 +758,8 @@
                      std::move(callback)),
       url(), swap_url,
       storage::FileSystemOperation::CopyOrMoveOptionSet(
-          storage::FileSystemOperation::CopyOrMoveOption::
-              kPreserveLastModified),
+          {storage::FileSystemOperation::CopyOrMoveOption::
+               kPreserveLastModified}),
       storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
       std::make_unique<storage::CopyOrMoveHookDelegate>());
 }
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl.cc b/content/browser/file_system_access/file_system_access_file_writer_impl.cc
index 9b6d3e692..424e295 100644
--- a/content/browser/file_system_access/file_system_access_file_writer_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_writer_impl.cc
@@ -256,8 +256,8 @@
           /*source_url=*/swap_url(),
           /*dest_url=*/url(),
           FileSystemOperation::CopyOrMoveOptionSet(
-              FileSystemOperation::CopyOrMoveOption::
-                  kPreserveDestinationPermissions),
+              {FileSystemOperation::CopyOrMoveOption::
+                   kPreserveDestinationPermissions}),
           std::move(quarantine_connection_callback_),
           has_transient_user_activation_);
   // Allows the unique pointer to be bound to the callback so the helper stays
diff --git a/content/browser/file_system_access/file_system_access_handle_base.cc b/content/browser/file_system_access/file_system_access_handle_base.cc
index 8c36dd4..c215d32 100644
--- a/content/browser/file_system_access/file_system_access_handle_base.cc
+++ b/content/browser/file_system_access/file_system_access_handle_base.cc
@@ -19,9 +19,7 @@
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
 #include "content/browser/file_system_access/file_system_access_safe_move_helper.h"
 #include "content/browser/file_system_access/file_system_access_transfer_token_impl.h"
-#include "content/browser/renderer_host/back_forward_cache_disable.h"
 #include "content/browser/web_contents/web_contents_impl.h"
-#include "content/public/browser/back_forward_cache.h"
 #include "content/public/browser/file_system_access_permission_context.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
@@ -70,15 +68,6 @@
         WebContentsImpl::FromRenderFrameHostID(context_.frame_id);
     if (web_contents) {
       web_contents_ = web_contents->GetWeakPtr();
-    }
-
-    // Disable back-forward cache as File System Access's usage of
-    // RenderFrameHost::IsActive at the moment is not compatible with bfcache.
-    BackForwardCache::DisableForRenderFrameHost(
-        context_.frame_id,
-        BackForwardCacheDisable::DisabledReason(
-            BackForwardCacheDisable::DisabledReasonId::kFileSystemAccess));
-    if (web_contents_) {
       static_cast<WebContentsImpl*>(web_contents_.get())
           ->IncrementFileSystemAccessHandleCount();
     }
diff --git a/content/browser/file_system_access/file_system_access_safe_move_helper_unittest.cc b/content/browser/file_system_access/file_system_access_safe_move_helper_unittest.cc
index 5bcb9ac..e54042e 100644
--- a/content/browser/file_system_access/file_system_access_safe_move_helper_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_safe_move_helper_unittest.cc
@@ -180,8 +180,8 @@
                                                     kFrameId),
         source_url, dest_url,
         storage::FileSystemOperation::CopyOrMoveOptionSet(
-            storage::FileSystemOperation::CopyOrMoveOption::
-                kPreserveDestinationPermissions),
+            {storage::FileSystemOperation::CopyOrMoveOption::
+                 kPreserveDestinationPermissions}),
         quarantine_callback_,
         /*has_transient_user_activation=*/false);
   }
diff --git a/content/browser/file_system_access/file_system_chooser_browsertest.cc b/content/browser/file_system_access/file_system_chooser_browsertest.cc
index 32aa18e..4c33024 100644
--- a/content/browser/file_system_access/file_system_chooser_browsertest.cc
+++ b/content/browser/file_system_access/file_system_chooser_browsertest.cc
@@ -11,13 +11,14 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gmock_callback_support.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
 #include "content/browser/file_system_access/fixed_file_system_access_permission_grant.h"
 #include "content/browser/file_system_access/mock_file_system_access_permission_context.h"
 #include "content/browser/file_system_access/mock_file_system_access_permission_grant.h"
-#include "content/browser/renderer_host/back_forward_cache_disable.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_process_host.h"
@@ -30,6 +31,7 @@
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/public/test/fake_file_system_access_permission_context.h"
 #include "content/public/test/file_system_chooser_test_helpers.h"
+#include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "storage/browser/file_system/external_mount_points.h"
@@ -917,29 +919,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest,
-                       FileSystemAccessUsageDisablesBackForwardCache) {
-  BackForwardCacheDisabledTester tester;
-
-  const base::FilePath test_file = CreateTestFile("file contents");
-  SelectFileDialogParams dialog_params;
-  ui::SelectFileDialog::SetFactory(
-      new FakeSelectFileDialogFactory({test_file}, &dialog_params));
-  ASSERT_TRUE(
-      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
-  EXPECT_EQ(test_file.BaseName().AsUTF8Unsafe(),
-            EvalJs(shell(),
-                   "(async () => {"
-                   "  let [e] = await self.showOpenFilePicker();"
-                   "  self.selected_entry = e;"
-                   "  return e.name; })()"));
-  EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
-      shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(),
-      shell()->web_contents()->GetPrimaryMainFrame()->GetRoutingID(),
-      BackForwardCacheDisable::DisabledReason(
-          BackForwardCacheDisable::DisabledReasonId::kFileSystemAccess)));
-}
-
-IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest,
                        OpenDirectory_LastPickedDirExists) {
   base::FilePath test_dir = CreateTestDir();
 
@@ -1919,4 +1898,46 @@
             dialog_params.title);
 }
 
+class FileSystemChooserBackForwardCacheBrowserTest
+    : public FileSystemChooserBrowserTest {
+ public:
+  FileSystemChooserBackForwardCacheBrowserTest() {
+    InitBackForwardCacheFeature(&feature_list_for_back_forward_cache_,
+                                /*enable_back_forward_cache=*/true);
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
+};
+
+IN_PROC_BROWSER_TEST_F(FileSystemChooserBackForwardCacheBrowserTest,
+                       IsEligibleForBackForwardCache) {
+  const base::FilePath test_file = CreateTestFile("file contents");
+  SelectFileDialogParams dialog_params;
+  ui::SelectFileDialog::SetFactory(
+      new FakeSelectFileDialogFactory({test_file}, &dialog_params));
+  ASSERT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
+  EXPECT_EQ(test_file.BaseName().AsUTF8Unsafe(),
+            EvalJs(shell(),
+                   "(async () => {"
+                   "  let [e] = await self.showOpenFilePicker();"
+                   "  self.selected_entry = e;"
+                   "  return e.name; })()"));
+
+  RenderFrameHostWrapper initial_rfh(
+      shell()->web_contents()->GetPrimaryMainFrame());
+
+  // Navigate to another page and expect the previous RenderFrameHost to be
+  // in the BFCache.
+  ASSERT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
+  EXPECT_TRUE(static_cast<RenderFrameHostImpl*>(initial_rfh.get())
+                  ->IsInBackForwardCache());
+
+  // And then navigating back restores `initial_rfh` as the primary main frame.
+  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
+  EXPECT_EQ(initial_rfh.get(), shell()->web_contents()->GetPrimaryMainFrame());
+}
+
 }  // namespace content
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index 415a4f8..8a8b244f 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -1156,9 +1156,10 @@
   interest_group.priority_signals_overrides = {{{"old1", 1}, {"old2", 2}}};
   interest_group.seller_capabilities.emplace();
   interest_group.seller_capabilities->insert(std::make_pair(
-      kOriginA, blink::SellerCapabilities::kInterestGroupCounts));
-  interest_group.all_sellers_capabilities =
-      blink::SellerCapabilities::kLatencyStats;
+      kOriginA, blink::SellerCapabilitiesType(
+                    {blink::SellerCapabilities::kInterestGroupCounts})));
+  interest_group.all_sellers_capabilities = {
+      blink::SellerCapabilities::kLatencyStats};
   interest_group.update_url = kUpdateUrlA;
   interest_group.bidding_url = kBiddingLogicUrlA;
   interest_group.trusted_bidding_signals_url = kTrustedBiddingSignalsUrlA;
@@ -1216,12 +1217,13 @@
 
   EXPECT_EQ(group.all_sellers_capabilities,
             blink::SellerCapabilitiesType(
-                blink::SellerCapabilities::kInterestGroupCounts,
-                blink::SellerCapabilities::kLatencyStats));
+                {blink::SellerCapabilities::kInterestGroupCounts,
+                 blink::SellerCapabilities::kLatencyStats}));
   ASSERT_TRUE(group.seller_capabilities);
   ASSERT_EQ(group.seller_capabilities->size(), 1u);
   EXPECT_EQ(group.seller_capabilities->at(kOriginA),
-            blink::SellerCapabilities::kLatencyStats);
+            blink::SellerCapabilitiesType(
+                {blink::SellerCapabilities::kLatencyStats}));
   ASSERT_TRUE(group.bidding_url.has_value());
   EXPECT_EQ(group.bidding_url->spec(),
             base::StringPrintf("%s/interest_group/new_bidding_logic.js",
@@ -2086,11 +2088,13 @@
   ASSERT_EQ(groups.size(), 1u);
   const auto& group = groups[0].interest_group;
   EXPECT_EQ(group.all_sellers_capabilities,
-            blink::SellerCapabilities::kInterestGroupCounts);
+            blink::SellerCapabilitiesType(
+                {blink::SellerCapabilities::kInterestGroupCounts}));
   ASSERT_TRUE(group.seller_capabilities);
   ASSERT_EQ(group.seller_capabilities->size(), 1u);
   EXPECT_EQ(group.seller_capabilities->at(kOriginA),
-            blink::SellerCapabilities::kLatencyStats);
+            blink::SellerCapabilitiesType(
+                {blink::SellerCapabilities::kLatencyStats}));
 }
 
 // The server response can't be parsed as valid JSON. The update is cancelled.
@@ -4705,8 +4709,8 @@
             "https://example.com/new_render");
   EXPECT_EQ(group.all_sellers_capabilities,
             blink::SellerCapabilitiesType(
-                blink::SellerCapabilities::kInterestGroupCounts,
-                blink::SellerCapabilities::kLatencyStats));
+                {blink::SellerCapabilities::kInterestGroupCounts,
+                 blink::SellerCapabilities::kLatencyStats}));
   EXPECT_EQ(group.execution_mode,
             blink::InterestGroup::ExecutionMode::kGroupedByOriginMode);
 }
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index b34a74b..cd11bcd 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -12696,8 +12696,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12731,8 +12732,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders,
@@ -12768,7 +12770,7 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetAllSellerCapabilities(blink::SellerCapabilities::kLatencyStats)
+          .SetAllSellerCapabilities({blink::SellerCapabilities::kLatencyStats})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12805,8 +12807,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12842,8 +12845,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12876,8 +12880,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12910,8 +12915,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -12945,8 +12951,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -13002,8 +13009,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -13027,8 +13035,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -13058,8 +13067,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, {kTrustedSignalsFetchLatency});
@@ -13092,8 +13102,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder2, kBidder2Name)
@@ -13102,8 +13113,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13151,8 +13163,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder2, kBidder2Name)
@@ -13161,8 +13174,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13204,8 +13218,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder1, kBidder1NameAlt)
@@ -13214,8 +13229,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13261,8 +13277,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder1, kBidder1NameAlt)
@@ -13271,8 +13288,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13320,8 +13338,9 @@
           .SetTrustedBiddingSignalsKeys({{"k1", "k2"}})
           .SetAds({{blink::InterestGroup::Ad(GURL("https://ad1.com"),
                                              absl::nullopt)}})
-          .SetSellerCapabilities({{{url::Origin::Create(kSellerUrl),
-                                    blink::SellerCapabilities::kLatencyStats}}})
+          .SetSellerCapabilities(
+              {{{url::Origin::Create(kSellerUrl),
+                 {blink::SellerCapabilities::kLatencyStats}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(bidders, /*trusted_fetch_latency=*/
@@ -13365,7 +13384,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder2, kBidder2Name)
@@ -13376,7 +13395,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13418,7 +13437,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder2, kBidder2Name)
@@ -13429,7 +13448,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
 
   RunExtendedPABuyersAuction(
@@ -13474,7 +13493,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
   bidders.emplace_back(MakeInterestGroup(
       blink::TestInterestGroupBuilder(kBidder1, kBidder1NameAlt)
@@ -13485,7 +13504,7 @@
                                              absl::nullopt)}})
           .SetSellerCapabilities(
               {{{url::Origin::Create(kSellerUrl),
-                 blink::SellerCapabilities::kInterestGroupCounts}}})
+                 {blink::SellerCapabilities::kInterestGroupCounts}}}})
           .Build()));
 
   RunAuctionAndWait(kSellerUrl, std::move(bidders));
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index d342d09..555f8f1 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -2681,8 +2681,8 @@
         const auto& group = groups[0].interest_group;
         return group.all_sellers_capabilities ==
                    blink::SellerCapabilitiesType(
-                       blink::SellerCapabilities::kInterestGroupCounts,
-                       blink::SellerCapabilities::kLatencyStats) &&
+                       {blink::SellerCapabilities::kInterestGroupCounts,
+                        blink::SellerCapabilities::kLatencyStats}) &&
                group.execution_mode ==
                    blink::InterestGroup::ExecutionMode::kGroupedByOriginMode;
       }));
@@ -2727,21 +2727,23 @@
                     /*name=*/"cars")
                     .SetSellerCapabilities(
                         {{{url::Origin::Create(GURL("https://example.test")),
-                           blink::SellerCapabilities::kInterestGroupCounts}}})
+                           {blink::SellerCapabilities::kInterestGroupCounts}}}})
                     .SetAllSellerCapabilities(
-                        blink::SellerCapabilities::kLatencyStats)
+                        {blink::SellerCapabilities::kLatencyStats})
                     .Build()));
 
   std::vector<StorageInterestGroup> groups = GetInterestGroupsForOwner(origin);
   ASSERT_EQ(groups.size(), 1u);
   const blink::InterestGroup& group = groups[0].interest_group;
   EXPECT_EQ(group.all_sellers_capabilities,
-            blink::SellerCapabilities::kLatencyStats);
+            blink::SellerCapabilitiesType(
+                {blink::SellerCapabilities::kLatencyStats}));
   ASSERT_TRUE(group.seller_capabilities);
   ASSERT_EQ(group.seller_capabilities->size(), 1u);
   EXPECT_EQ(group.seller_capabilities->at(
                 url::Origin::Create(GURL("https://example.test"))),
-            blink::SellerCapabilities::kInterestGroupCounts);
+            blink::SellerCapabilitiesType(
+                {blink::SellerCapabilities::kInterestGroupCounts}));
 }
 
 IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
@@ -5272,7 +5274,7 @@
                     .SetAds({{{ad2_url, /*metadata=*/absl::nullopt}}})
                     .SetUpdateUrl(update_url)
                     .SetAllSellerCapabilities(
-                        blink::SellerCapabilities::kInterestGroupCounts)
+                        {blink::SellerCapabilities::kInterestGroupCounts})
                     .Build()));
 
   // `ad2_url` wins, because "cars" is removed for not satisfying
@@ -5300,8 +5302,8 @@
             for (const StorageInterestGroup& group : groups) {
               if (group.interest_group.all_sellers_capabilities !=
                   blink::SellerCapabilitiesType(
-                      blink::SellerCapabilities::kInterestGroupCounts,
-                      blink::SellerCapabilities::kLatencyStats)) {
+                      {blink::SellerCapabilities::kInterestGroupCounts,
+                       blink::SellerCapabilities::kLatencyStats})) {
                 return false;
               }
             }
@@ -5368,7 +5370,7 @@
                     .SetUpdateUrl(update_url)
                     .SetSellerCapabilities(
                         {{{test_origin,
-                           blink::SellerCapabilities::kInterestGroupCounts}}})
+                           {blink::SellerCapabilities::kInterestGroupCounts}}}})
                     .Build()));
 
   // `ad2_url` wins, because "cars" is removed for not satisfying
@@ -5405,8 +5407,8 @@
               }
               if (it->second !=
                   blink::SellerCapabilitiesType(
-                      blink::SellerCapabilities::kInterestGroupCounts,
-                      blink::SellerCapabilities::kLatencyStats)) {
+                      {blink::SellerCapabilities::kInterestGroupCounts,
+                       blink::SellerCapabilities::kLatencyStats})) {
                 return false;
               }
             }
@@ -5477,7 +5479,7 @@
                     .SetUpdateUrl(update_url)
                     .SetSellerCapabilities(
                         {{{other_origin,
-                           blink::SellerCapabilities::kInterestGroupCounts}}})
+                           {blink::SellerCapabilities::kInterestGroupCounts}}}})
                     .Build()));
 
   // There is no winner, because "cars" is removed for not satisfying
@@ -5514,8 +5516,8 @@
               }
               if (it->second !=
                   blink::SellerCapabilitiesType(
-                      blink::SellerCapabilities::kInterestGroupCounts,
-                      blink::SellerCapabilities::kLatencyStats)) {
+                      {blink::SellerCapabilities::kInterestGroupCounts,
+                       blink::SellerCapabilities::kLatencyStats})) {
                 return false;
               }
             }
@@ -5569,7 +5571,7 @@
               .SetAds({{{ad1_url, /*metadata=*/absl::nullopt}}})
               .SetUpdateUrl(update_url)
               .SetAllSellerCapabilities(
-                  blink::SellerCapabilities::kInterestGroupCounts)
+                  {blink::SellerCapabilities::kInterestGroupCounts})
               .Build()));
   EXPECT_EQ(kSuccess,
             JoinInterestGroupAndVerify(
@@ -5610,8 +5612,8 @@
             for (const StorageInterestGroup& group : groups) {
               if (group.interest_group.all_sellers_capabilities !=
                   blink::SellerCapabilitiesType(
-                      blink::SellerCapabilities::kInterestGroupCounts,
-                      blink::SellerCapabilities::kLatencyStats)) {
+                      {blink::SellerCapabilities::kInterestGroupCounts,
+                       blink::SellerCapabilities::kLatencyStats})) {
                 return false;
               }
             }
diff --git a/content/browser/interest_group/interest_group_storage_unittest.cc b/content/browser/interest_group/interest_group_storage_unittest.cc
index 35c7cc6..02f8770 100644
--- a/content/browser/interest_group/interest_group_storage_unittest.cc
+++ b/content/browser/interest_group/interest_group_storage_unittest.cc
@@ -99,8 +99,8 @@
         /*priority_vector=*/{{{"a", 2}, {"b", -2.2}}},
         /*priority_signals_overrides=*/{{{"a", -2}, {"c", 10}, {"d", 1.2}}},
         /*seller_capabilities=*/
-        {{{full_origin, SellerCapabilities::kInterestGroupCounts},
-          {partial_origin, SellerCapabilities::kLatencyStats}}},
+        {{{full_origin, {SellerCapabilities::kInterestGroupCounts}},
+          {partial_origin, {SellerCapabilities::kLatencyStats}}}},
         /*all_sellers_capabilities=*/
         {SellerCapabilities::kInterestGroupCounts,
          SellerCapabilities::kLatencyStats},
diff --git a/content/browser/media/capture/video_capture_device_proxy_lacros.cc b/content/browser/media/capture/video_capture_device_proxy_lacros.cc
index 1142554..61052d0 100644
--- a/content/browser/media/capture/video_capture_device_proxy_lacros.cc
+++ b/content/browser/media/capture/video_capture_device_proxy_lacros.cc
@@ -31,8 +31,6 @@
 
 namespace {
 
-const int kVideoCaptureMinVersion = crosapi::mojom::ScreenManager::
-    MethodMinVersions::kGetScreenVideoCapturerMinVersion;
 const int kRequestRefreshFrameMinVersion = crosapi::mojom::VideoCaptureDevice::
     MethodMinVersions::kRequestRefreshFrameMinVersion;
 
@@ -44,29 +42,17 @@
 
 }  // namespace
 
-// static
-bool VideoCaptureDeviceProxyLacros::IsAvailable() {
-  auto* service = chromeos::LacrosService::Get();
-
-  if (!service)
-    return false;
-
-  return service->GetInterfaceVersion<crosapi::mojom::ScreenManager>() >=
-         kVideoCaptureMinVersion;
-}
-
 VideoCaptureDeviceProxyLacros::VideoCaptureDeviceProxyLacros(
     const DesktopMediaID& device_id)
     : capture_id_(device_id) {
-  CHECK(IsAvailable());
   CHECK(capture_id_.type == DesktopMediaID::TYPE_SCREEN ||
         capture_id_.type == DesktopMediaID::TYPE_WINDOW);
 
   // The LacrosService exists at all times except during early start-up and
   // late shut-down. This class should never be used in those two times.
   auto* lacros_service = chromeos::LacrosService::Get();
-  DCHECK(lacros_service);
-  DCHECK(lacros_service->IsAvailable<crosapi::mojom::ScreenManager>());
+  CHECK(lacros_service);
+  CHECK(lacros_service->IsAvailable<crosapi::mojom::ScreenManager>());
   lacros_service->BindScreenManagerReceiver(
       screen_manager_.BindNewPipeAndPassReceiver());
 
diff --git a/content/browser/media/capture/video_capture_device_proxy_lacros.h b/content/browser/media/capture/video_capture_device_proxy_lacros.h
index cfff640..46e8de70 100644
--- a/content/browser/media/capture/video_capture_device_proxy_lacros.h
+++ b/content/browser/media/capture/video_capture_device_proxy_lacros.h
@@ -45,12 +45,6 @@
 class CONTENT_EXPORT VideoCaptureDeviceProxyLacros
     : public media::VideoCaptureDevice {
  public:
-  // Helper method to check if the mojom version(s) required to use this class
-  // are available.
-  // May be called from any thread; should be checked before creating (or
-  // posting a task to create) an instance of this class.
-  static bool IsAvailable();
-
   explicit VideoCaptureDeviceProxyLacros(const DesktopMediaID& device_id);
 
   VideoCaptureDeviceProxyLacros(const VideoCaptureDeviceProxyLacros&) = delete;
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 5e59ed7..0d1ea38 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -278,7 +278,8 @@
 
   // `PreloadingPrediction` is added in `PreloadingDecider`.
 
-  redirect_chain_.push_back(std::make_unique<SinglePrefetch>(prefetch_url_));
+  redirect_chain_.push_back(
+      std::make_unique<SinglePrefetch>(prefetch_url_, referring_site_));
 }
 
 PrefetchContainer::~PrefetchContainer() {
@@ -348,8 +349,8 @@
     PrefetchService* prefetch_service) {
   if (!network_context_) {
     network_context_ = std::make_unique<PrefetchNetworkContext>(
-        prefetch_service, prefetch_type_, referrer_,
-        referring_render_frame_host_id_);
+        prefetch_service, IsIsolatedNetworkContextRequiredForURL(GetURL()),
+        prefetch_type_, referrer_, referring_render_frame_host_id_);
   }
   return network_context_.get();
 }
@@ -406,7 +407,8 @@
 }
 
 void PrefetchContainer::AddRedirectHop(const GURL& url) {
-  redirect_chain_.push_back(std::make_unique<SinglePrefetch>(url));
+  redirect_chain_.push_back(
+      std::make_unique<SinglePrefetch>(url, referring_site_));
 }
 
 absl::optional<bool> PrefetchContainer::GetEligibilityResultForRedirect(
@@ -652,9 +654,8 @@
 bool PrefetchContainer::DoesCurrentURLToServeMatch(const GURL& url) const {
   DCHECK(index_redirect_chain_to_serve_ >= 1 &&
          index_redirect_chain_to_serve_ < redirect_chain_.size());
-  return redirect_chain_[index_redirect_chain_to_serve_]->url_ == url ||
-         IsMatchingNoVarySearchUrl(
-             redirect_chain_[index_redirect_chain_to_serve_]->url_, url);
+  return IsMatchingURL(redirect_chain_[index_redirect_chain_to_serve_]->url_,
+                       url);
 }
 
 const GURL& PrefetchContainer::GetCurrentURLToServe() const {
@@ -698,11 +699,12 @@
 
 PrefetchContainer::SinglePrefetch* PrefetchContainer::GetSinglePrefetch(
     const GURL& url) const {
+  // TODO(https://crbug.com/1444568): Handle the case where the given URL
+  // matches multiple entries in |redirect_chain_|.
   for (auto itr = redirect_chain_.rbegin(); itr != redirect_chain_.rend();
        itr++) {
     GURL single_prefetch_url = (*itr)->url_;
-    if (single_prefetch_url == url ||
-        IsMatchingNoVarySearchUrl(single_prefetch_url, url)) {
+    if (IsMatchingURL(single_prefetch_url, url)) {
       return itr->get();
     }
   }
@@ -710,9 +712,32 @@
   return nullptr;
 }
 
-bool PrefetchContainer::IsMatchingNoVarySearchUrl(
-    const GURL& internal_url,
-    const GURL& external_url) const {
+PrefetchContainer::SinglePrefetch* PrefetchContainer::GetPreviousSinglePrefetch(
+    const GURL& url) const {
+  // TODO(https://crbug.com/1444568): Handle the case where the given URL
+  // matches multiple entries in |redirect_chain_|.
+  for (auto itr = redirect_chain_.rbegin(); itr != redirect_chain_.rend();
+       itr++) {
+    GURL single_prefetch_url = (*itr)->url_;
+    if (IsMatchingURL(single_prefetch_url, url)) {
+      // Once the SinglePrefetch that matches the given URL is found, then
+      // increment the reverse iterator to get the previous one.
+      itr++;
+      return itr != redirect_chain_.rend() ? itr->get() : nullptr;
+    }
+  }
+  NOTREACHED();
+  return nullptr;
+}
+
+bool PrefetchContainer::IsMatchingURL(const GURL& internal_url,
+                                      const GURL& external_url) const {
+  // Check if the URLs match directly.
+  if (internal_url == external_url) {
+    return true;
+  }
+
+  // Otherwise, try to use no_vary_search_helper_.
   if (!no_vary_search_helper_) {
     return false;
   }
@@ -756,6 +781,20 @@
   }
 }
 
+bool PrefetchContainer::IsIsolatedNetworkContextRequiredForURL(
+    const GURL& url) const {
+  SinglePrefetch* this_prefetch = GetSinglePrefetch(url);
+  CHECK(this_prefetch);
+  return this_prefetch->is_isolated_network_context_required_;
+}
+
+bool PrefetchContainer::IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+    const GURL& url) const {
+  SinglePrefetch* previous_prefetch = GetPreviousSinglePrefetch(url);
+  CHECK(previous_prefetch);
+  return previous_prefetch->is_isolated_network_context_required_;
+}
+
 bool PrefetchContainer::IsProxyRequiredForURL(const GURL& url) const {
   return !referring_origin_.IsSameOriginWith(url) &&
          prefetch_type_.IsProxyRequiredWhenCrossOrigin();
@@ -767,8 +806,13 @@
                  << ", URL=" << prefetch_container.GetURL() << "]";
 }
 
-PrefetchContainer::SinglePrefetch::SinglePrefetch(const GURL& url)
-    : url_(url) {}
+PrefetchContainer::SinglePrefetch::SinglePrefetch(
+    const GURL& url,
+    const net::SchemefulSite& referring_site)
+    : url_(url) {
+  net::SchemefulSite this_site(url_);
+  is_isolated_network_context_required_ = referring_site != this_site;
+}
 
 PrefetchContainer::SinglePrefetch::~SinglePrefetch() = default;
 
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h
index 91a9788..b9841573 100644
--- a/content/browser/preloading/prefetch/prefetch_container.h
+++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -82,6 +82,15 @@
   // The type of this prefetch. Controls how the prefetch is handled.
   const PrefetchType& GetPrefetchType() const { return prefetch_type_; }
 
+  // Whether or not an isolated network context is required to fetch the given
+  // url.
+  bool IsIsolatedNetworkContextRequiredForURL(const GURL& url) const;
+
+  // Whether or not an isolated network context is required for the previous
+  // redirect hop of the given url.
+  bool IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+      const GURL& url) const;
+
   // Whether or not the prefetch proxy would be required to fetch the given url
   // based on |prefetch_type_|.
   bool IsProxyRequiredForURL(const GURL& url) const;
@@ -297,7 +306,8 @@
   // broader prefetch. A prefetch can request multiple URLs due to redirects.
   class SinglePrefetch {
    public:
-    explicit SinglePrefetch(const GURL& url);
+    explicit SinglePrefetch(const GURL& url,
+                            const net::SchemefulSite& referring_site);
     ~SinglePrefetch();
 
     SinglePrefetch(const SinglePrefetch&) = delete;
@@ -308,6 +318,8 @@
     // original prefetch URL.
     GURL url_;
 
+    bool is_isolated_network_context_required_;
+
     // Whether this |url_| is eligible to be prefetched
     absl::optional<bool> is_eligible_;
 
@@ -340,9 +352,13 @@
   // Helper function to get the |SinglePrefetch| for the given URL.
   SinglePrefetch* GetSinglePrefetch(const GURL& url) const;
 
-  // Helper function to match URLs using |no_vary_search_helper_|.
-  bool IsMatchingNoVarySearchUrl(const GURL& internal_url,
-                                 const GURL& external_url) const;
+  // Helper function go get the |SinglePrefetch| that preceded the given URL.
+  // If called on the original URL of the prefetch, then nullptr is returned.
+  SinglePrefetch* GetPreviousSinglePrefetch(const GURL& url) const;
+
+  // Helper function to match URLs either directly or using
+  // |no_vary_search_helper_|.
+  bool IsMatchingURL(const GURL& internal_url, const GURL& external_url) const;
 
   // The ID of the RenderFrameHost that triggered the prefetch.
   GlobalRenderFrameHostId referring_render_frame_host_id_;
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index 15321b9..4cb618e9 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -96,8 +96,7 @@
 TEST_F(PrefetchContainerTest, CreatePrefetchContainer) {
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -108,9 +107,10 @@
             GlobalRenderFrameHostId(1234, 5678));
   EXPECT_EQ(prefetch_container.GetURL(), GURL("https://test.com"));
   EXPECT_EQ(prefetch_container.GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/true,
-                         /*use_prefetch_proxy=*/true,
+            PrefetchType(/*use_prefetch_proxy=*/true,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_TRUE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://test.com")));
 
   EXPECT_EQ(prefetch_container.GetPrefetchContainerKey(),
             std::make_pair(GlobalRenderFrameHostId(1234, 5678),
@@ -121,8 +121,7 @@
 TEST_F(PrefetchContainerTest, PrefetchStatus) {
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -141,8 +140,7 @@
 TEST_F(PrefetchContainerTest, IsDecoy) {
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -158,8 +156,7 @@
 TEST_F(PrefetchContainerTest, Servable) {
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -184,8 +181,7 @@
 
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl1,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -233,8 +229,7 @@
   base::HistogramTester histogram_tester;
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -294,8 +289,7 @@
   base::HistogramTester histogram_tester;
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -416,8 +410,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -538,8 +531,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -612,8 +604,7 @@
 
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl1,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -661,8 +652,7 @@
 
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl1,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -706,8 +696,7 @@
 
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), kTestUrl,
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -769,8 +758,7 @@
   for (const auto& test_case : test_cases) {
     PrefetchContainer prefetch_container(
         GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-        PrefetchType(/*use_isolated_network_context=*/true,
-                     /*use_prefetch_proxy=*/true, test_case.eagerness),
+        PrefetchType(/*use_prefetch_proxy=*/true, test_case.eagerness),
         blink::mojom::Referrer(),
         /*no_vary_search_expected=*/absl::nullopt,
         blink::mojom::SpeculationInjectionWorld::kNone,
@@ -826,8 +814,7 @@
 
   PrefetchContainer prefetch_container(
       GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       blink::mojom::Referrer(),
       /*no_vary_search_expected=*/absl::nullopt,
@@ -842,4 +829,50 @@
       "PrefetchProxy.Prefetch.RedirectChainSize", 3, 1);
 }
 
+TEST_F(PrefetchContainerTest, IsIsolatedNetworkRequired) {
+  base::HistogramTester histogram_tester;
+
+  blink::mojom::Referrer referrer;
+  referrer.url = GURL("https://test.com/referrer");
+  PrefetchContainer prefetch_container(
+      GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com/prefetch"),
+      PrefetchType(/*use_prefetch_proxy=*/true,
+                   blink::mojom::SpeculationEagerness::kEager),
+      referrer, /*no_vary_search_expected=*/absl::nullopt,
+      blink::mojom::SpeculationInjectionWorld::kNone,
+      /*prefetch_document_manager=*/nullptr);
+
+  prefetch_container.AddRedirectHop(GURL("https://test.com/redirect"));
+  prefetch_container.AddRedirectHop(GURL("https://m.test.com/redirect"));
+  prefetch_container.AddRedirectHop(GURL("https://other.com/redirect1"));
+  prefetch_container.AddRedirectHop(GURL("https://other.com/redirect2"));
+
+  EXPECT_FALSE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://test.com/prefetch")));
+
+  EXPECT_FALSE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://test.com/redirect")));
+  EXPECT_FALSE(
+      prefetch_container.IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+          GURL("https://test.com/redirect")));
+
+  EXPECT_FALSE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://m.test.com/redirect")));
+  EXPECT_FALSE(
+      prefetch_container.IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+          GURL("https://m.test.com/redirect")));
+
+  EXPECT_TRUE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://other.com/redirect1")));
+  EXPECT_FALSE(
+      prefetch_container.IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+          GURL("https://other.com/redirect1")));
+
+  EXPECT_TRUE(prefetch_container.IsIsolatedNetworkContextRequiredForURL(
+      GURL("https://other.com/redirect2")));
+  EXPECT_TRUE(
+      prefetch_container.IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+          GURL("https://other.com/redirect2")));
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index 6b135ebe..27dc313 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -190,8 +190,6 @@
         prefetches.emplace_back(
             candidate->url,
             PrefetchType(
-                /*use_isolated_network_context=*/referring_site !=
-                    prefetch_site,
                 /*use_prefetch_proxy=*/
                 candidate->requires_anonymous_client_ip_when_cross_origin,
                 candidate->eagerness),
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 80581ea..15aefbd 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -433,13 +433,16 @@
   // Create list of SpeculationCandidatePtrs.
   std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
 
+  auto referrer = blink::mojom::Referrer::New();
+  referrer->url = GetSameOriginUrl("/referrer");
+
   // Create candidate for private cross-origin prefetch. This candidate should
   // be prefetched by |PrefetchDocumentManager|.
   auto candidate1 = blink::mojom::SpeculationCandidate::New();
   candidate1->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate1->requires_anonymous_client_ip_when_cross_origin = true;
   candidate1->url = GetCrossOriginUrl("/candidate1.html");
-  candidate1->referrer = blink::mojom::Referrer::New();
+  candidate1->referrer = referrer->Clone();
   candidate1->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate1));
 
@@ -449,7 +452,7 @@
   candidate2->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate2->requires_anonymous_client_ip_when_cross_origin = false;
   candidate2->url = GetCrossOriginUrl("/candidate2.html");
-  candidate2->referrer = blink::mojom::Referrer::New();
+  candidate2->referrer = referrer->Clone();
   candidate2->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate2));
 
@@ -459,7 +462,7 @@
   candidate3->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate3->requires_anonymous_client_ip_when_cross_origin = false;
   candidate3->url = GetSameOriginUrl("/candidate3.html");
-  candidate3->referrer = blink::mojom::Referrer::New();
+  candidate3->referrer = referrer->Clone();
   candidate3->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate3));
 
@@ -470,7 +473,7 @@
       blink::mojom::SpeculationAction::kPrefetchWithSubresources;
   candidate4->requires_anonymous_client_ip_when_cross_origin = true;
   candidate4->url = GetCrossOriginUrl("/candidate4.html");
-  candidate4->referrer = blink::mojom::Referrer::New();
+  candidate4->referrer = referrer->Clone();
   candidate4->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate4));
 
@@ -480,7 +483,7 @@
   candidate5->action = blink::mojom::SpeculationAction::kPrerender;
   candidate5->requires_anonymous_client_ip_when_cross_origin = false;
   candidate5->url = GetCrossOriginUrl("/candidate5.html");
-  candidate5->referrer = blink::mojom::Referrer::New();
+  candidate5->referrer = referrer->Clone();
   candidate5->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate5));
 
@@ -490,7 +493,7 @@
   candidate6->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate6->requires_anonymous_client_ip_when_cross_origin = true;
   candidate6->url = GetCrossOriginUrl("/candidate6.html");
-  candidate6->referrer = blink::mojom::Referrer::New();
+  candidate6->referrer = referrer->Clone();
   candidate6->eagerness = blink::mojom::SpeculationEagerness::kConservative;
   candidates.push_back(std::move(candidate6));
 
@@ -500,7 +503,7 @@
   candidate7->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate7->requires_anonymous_client_ip_when_cross_origin = false;
   candidate7->url = GetSameSiteCrossOriginUrl("/candidate7.html");
-  candidate7->referrer = blink::mojom::Referrer::New();
+  candidate7->referrer = referrer->Clone();
   candidate7->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate7));
 
@@ -511,7 +514,7 @@
   candidate8->action = blink::mojom::SpeculationAction::kPrefetch;
   candidate8->requires_anonymous_client_ip_when_cross_origin = true;
   candidate8->url = GetSameOriginUrl("/candidate8.html");
-  candidate8->referrer = blink::mojom::Referrer::New();
+  candidate8->referrer = referrer->Clone();
   candidate8->eagerness = blink::mojom::SpeculationEagerness::kEager;
   candidates.push_back(std::move(candidate8));
 
@@ -529,35 +532,41 @@
   ASSERT_EQ(prefetch_urls.size(), 6U);
   EXPECT_EQ(prefetch_urls[0]->GetURL(), GetCrossOriginUrl("/candidate1.html"));
   EXPECT_EQ(prefetch_urls[0]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/true,
-                         /*use_prefetch_proxy=*/true,
+            PrefetchType(/*use_prefetch_proxy=*/true,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_TRUE(prefetch_urls[0]->IsIsolatedNetworkContextRequiredForURL(
+      GetCrossOriginUrl("/candidate1.html")));
   EXPECT_EQ(prefetch_urls[1]->GetURL(), GetCrossOriginUrl("/candidate2.html"));
   EXPECT_EQ(prefetch_urls[1]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/true,
-                         /*use_prefetch_proxy=*/false,
+            PrefetchType(/*use_prefetch_proxy=*/false,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_TRUE(prefetch_urls[1]->IsIsolatedNetworkContextRequiredForURL(
+      GetCrossOriginUrl("/candidate2.html")));
   EXPECT_EQ(prefetch_urls[2]->GetURL(), GetSameOriginUrl("/candidate3.html"));
   EXPECT_EQ(prefetch_urls[2]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/false,
-                         /*use_prefetch_proxy=*/false,
+            PrefetchType(/*use_prefetch_proxy=*/false,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_FALSE(prefetch_urls[2]->IsIsolatedNetworkContextRequiredForURL(
+      GetSameOriginUrl("/candidate3.html")));
   EXPECT_EQ(prefetch_urls[3]->GetURL(), GetCrossOriginUrl("/candidate6.html"));
   EXPECT_EQ(prefetch_urls[3]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/true,
-                         /*use_prefetch_proxy=*/true,
+            PrefetchType(/*use_prefetch_proxy=*/true,
                          blink::mojom::SpeculationEagerness::kConservative));
+  EXPECT_TRUE(prefetch_urls[3]->IsIsolatedNetworkContextRequiredForURL(
+      GetCrossOriginUrl("/candidate6.html")));
   EXPECT_EQ(prefetch_urls[4]->GetURL(),
             GetSameSiteCrossOriginUrl("/candidate7.html"));
   EXPECT_EQ(prefetch_urls[4]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/false,
-                         /*use_prefetch_proxy=*/false,
+            PrefetchType(/*use_prefetch_proxy=*/false,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_FALSE(prefetch_urls[4]->IsIsolatedNetworkContextRequiredForURL(
+      GetSameSiteCrossOriginUrl("/candidate7.html")));
   EXPECT_EQ(prefetch_urls[5]->GetURL(), GetSameOriginUrl("/candidate8.html"));
   EXPECT_EQ(prefetch_urls[5]->GetPrefetchType(),
-            PrefetchType(/*use_isolated_network_context=*/false,
-                         /*use_prefetch_proxy=*/true,
+            PrefetchType(/*use_prefetch_proxy=*/true,
                          blink::mojom::SpeculationEagerness::kEager));
+  EXPECT_FALSE(prefetch_urls[5]->IsIsolatedNetworkContextRequiredForURL(
+      GetSameOriginUrl("/candidate8.html")));
 
   // Check that the only remaining entries in candidates are those that
   // shouldn't be prefetched by |PrefetchService|.
diff --git a/content/browser/preloading/prefetch/prefetch_network_context.cc b/content/browser/preloading/prefetch/prefetch_network_context.cc
index a60ea71..bc409a2d 100644
--- a/content/browser/preloading/prefetch/prefetch_network_context.cc
+++ b/content/browser/preloading/prefetch/prefetch_network_context.cc
@@ -39,10 +39,12 @@
 
 PrefetchNetworkContext::PrefetchNetworkContext(
     PrefetchService* prefetch_service,
+    bool use_isolated_network_context,
     const PrefetchType& prefetch_type,
     const blink::mojom::Referrer& referrer_,
     const GlobalRenderFrameHostId& referring_render_frame_host_id)
     : prefetch_service_(prefetch_service),
+      use_isolated_network_context_(use_isolated_network_context),
       prefetch_type_(prefetch_type),
       referrer_(referrer_),
       referring_render_frame_host_id_(referring_render_frame_host_id) {}
@@ -51,16 +53,16 @@
 
 network::mojom::NetworkContext* PrefetchNetworkContext::GetNetworkContext()
     const {
-  DCHECK(network_context_);
+  CHECK(network_context_);
   return network_context_.get();
 }
 
 network::mojom::URLLoaderFactory*
 PrefetchNetworkContext::GetURLLoaderFactory() {
   if (!url_loader_factory_) {
-    if (prefetch_type_.IsIsolatedNetworkContextRequired()) {
+    if (use_isolated_network_context_) {
       CreateIsolatedURLLoaderFactory();
-      DCHECK(network_context_);
+      CHECK(network_context_);
     } else {
       // Create new URL factory in the default network context.
       mojo::PendingRemote<network::mojom::URLLoaderFactory> url_factory_remote;
@@ -74,13 +76,13 @@
               std::move(url_factory_remote)));
     }
   }
-  DCHECK(url_loader_factory_);
+  CHECK(url_loader_factory_);
   return url_loader_factory_.get();
 }
 
 network::mojom::CookieManager* PrefetchNetworkContext::GetCookieManager() {
-  DCHECK(prefetch_type_.IsIsolatedNetworkContextRequired());
-  DCHECK(network_context_);
+  CHECK(use_isolated_network_context_);
+  CHECK(network_context_);
   if (!cookie_manager_)
     network_context_->GetCookieManager(
         cookie_manager_.BindNewPipeAndPassReceiver());
@@ -94,7 +96,7 @@
 }
 
 void PrefetchNetworkContext::CreateIsolatedURLLoaderFactory() {
-  DCHECK(prefetch_type_.IsIsolatedNetworkContextRequired());
+  CHECK(use_isolated_network_context_);
 
   network_context_.reset();
   url_loader_factory_.reset();
@@ -118,13 +120,13 @@
   }
 
   context_params->http_cache_enabled = true;
-  DCHECK(!context_params->http_cache_directory);
+  CHECK(!context_params->http_cache_directory);
 
   if (prefetch_type_.IsProxyRequiredWhenCrossOrigin() &&
       !prefetch_type_.IsProxyBypassedForTesting()) {
     PrefetchProxyConfigurator* prefetch_proxy_configurator =
         prefetch_service_->GetPrefetchProxyConfigurator();
-    DCHECK(prefetch_proxy_configurator);
+    CHECK(prefetch_proxy_configurator);
 
     context_params->initial_custom_proxy_config =
         prefetch_proxy_configurator->CreateCustomProxyConfig();
@@ -173,7 +175,7 @@
     network::mojom::NetworkContext* network_context,
     mojo::PendingReceiver<network::mojom::URLLoaderFactory> pending_receiver,
     absl::optional<net::IsolationInfo> isolation_info) {
-  DCHECK(network_context);
+  CHECK(network_context);
 
   auto factory_params = network::mojom::URLLoaderFactoryParams::New();
   factory_params->process_id = network::mojom::kBrowserProcessId;
diff --git a/content/browser/preloading/prefetch/prefetch_network_context.h b/content/browser/preloading/prefetch/prefetch_network_context.h
index d7800b2..77dd75a 100644
--- a/content/browser/preloading/prefetch/prefetch_network_context.h
+++ b/content/browser/preloading/prefetch/prefetch_network_context.h
@@ -30,6 +30,7 @@
  public:
   PrefetchNetworkContext(
       PrefetchService* prefetch_service,
+      bool use_isolated_network_context,
       const PrefetchType& prefetch_type,
       const blink::mojom::Referrer& referring_origin,
       const GlobalRenderFrameHostId& referring_render_frame_host_id);
@@ -69,10 +70,11 @@
 
   raw_ptr<PrefetchService> prefetch_service_;
 
-  // Determines whether or not an isolated network context or the default
-  // network context should be used. If an isolated network context is required,
-  // also determines if it should be configured to use the Prefetch Proxy or
-  // not.
+  // Whether an isolated network context or the default network context should
+  // be used.
+  const bool use_isolated_network_context_;
+
+  // Used to determine if the prefetch proxy should be used.
   const PrefetchType prefetch_type_;
 
   // These parameters are used when considering to proxy |url_loader_factory_|
diff --git a/content/browser/preloading/prefetch/prefetch_network_context_unittest.cc b/content/browser/preloading/prefetch/prefetch_network_context_unittest.cc
index 30f31c8..796040770 100644
--- a/content/browser/preloading/prefetch/prefetch_network_context_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_network_context_unittest.cc
@@ -105,8 +105,8 @@
   std::unique_ptr<PrefetchNetworkContext> prefetch_network_context =
       std::make_unique<PrefetchNetworkContext>(
           prefetch_service(),
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/false,
+          /*use_isolated_network_context=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/false,
                        blink::mojom::SpeculationEagerness::kEager),
           referring_origin, main_rfh()->GetGlobalId());
 
@@ -139,8 +139,8 @@
   std::unique_ptr<PrefetchNetworkContext> prefetch_network_context =
       std::make_unique<PrefetchNetworkContext>(
           prefetch_service(),
-          PrefetchType(/*use_isolated_network_context=*/false,
-                       /*use_prefetch_proxy=*/false,
+          /*use_isolated_network_context=*/false,
+          PrefetchType(/*use_prefetch_proxy=*/false,
                        blink::mojom::SpeculationEagerness::kEager),
           referring_origin, main_rfh()->GetGlobalId());
 
@@ -148,4 +148,4 @@
 }
 
 }  // namespace
-}  // namespace content
\ No newline at end of file
+}  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 0a82765..e73fe438 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -224,22 +224,22 @@
 }
 
 void RecordRedirectNetworkContextTransition(
-    bool prefetch_requires_isolated_network_context,
+    bool previous_requires_isolated_network_context,
     bool redirect_requires_isolated_network_context) {
   PrefetchRedirectNetworkContextTransition transition;
-  if (!prefetch_requires_isolated_network_context &&
+  if (!previous_requires_isolated_network_context &&
       !redirect_requires_isolated_network_context) {
     transition = PrefetchRedirectNetworkContextTransition::kDefaultToDefault;
   }
-  if (!prefetch_requires_isolated_network_context &&
+  if (!previous_requires_isolated_network_context &&
       redirect_requires_isolated_network_context) {
     transition = PrefetchRedirectNetworkContextTransition::kDefaultToIsolated;
   }
-  if (prefetch_requires_isolated_network_context &&
+  if (previous_requires_isolated_network_context &&
       !redirect_requires_isolated_network_context) {
     transition = PrefetchRedirectNetworkContextTransition::kIsolatedToDefault;
   }
-  if (prefetch_requires_isolated_network_context &&
+  if (previous_requires_isolated_network_context &&
       redirect_requires_isolated_network_context) {
     transition = PrefetchRedirectNetworkContextTransition::kIsolatedToIsolated;
   }
@@ -476,8 +476,7 @@
   // TODO(https://crbug.com/1439986): Allow same-site cross-origin prefetches
   // that require the prefetch proxy to be made.
   if (prefetch_container->IsProxyRequiredForURL(url) &&
-      !prefetch_container->GetPrefetchType()
-           .IsIsolatedNetworkContextRequired()) {
+      !prefetch_container->IsIsolatedNetworkContextRequiredForURL(url)) {
     std::move(result_callback)
         .Run(url, prefetch_container, false,
              PrefetchStatus::
@@ -487,8 +486,7 @@
 
   // We do not need to check the cookies of prefetches that do not need an
   // isolated network context.
-  if (!prefetch_container->GetPrefetchType()
-           .IsIsolatedNetworkContextRequired()) {
+  if (!prefetch_container->IsIsolatedNetworkContextRequiredForURL(url)) {
     std::move(result_callback)
         .Run(url, prefetch_container, true, absl::nullopt);
     return;
@@ -559,8 +557,7 @@
   // TODO(https://crbug.com/1343903): Copy proxy settings over to the isolated
   // network context for the prefetch in order to allow non-private cross origin
   // prefetches to be made using the existing proxy settings.
-  if (!prefetch_container->GetPrefetchType()
-           .IsIsolatedNetworkContextRequired()) {
+  if (!prefetch_container->IsIsolatedNetworkContextRequiredForURL(url)) {
     std::move(result_callback)
         .Run(url, prefetch_container, true, absl::nullopt);
     return;
@@ -646,8 +643,7 @@
     // network context. If the cookies in the default partition associated with
     // this URL change after this point, then the prefetched resources should
     // not be served.
-    if (prefetch_container->GetPrefetchType()
-            .IsIsolatedNetworkContextRequired()) {
+    if (prefetch_container->IsIsolatedNetworkContextRequiredForURL(url)) {
       prefetch_container->RegisterCookieListener(
           url, browser_context_->GetDefaultStoragePartition()
                    ->GetCookieManagerForBrowserProcess());
@@ -692,8 +688,7 @@
   }
 
   prefetch_container->OnEligibilityCheckComplete(url, eligible, status);
-  if (prefetch_container->GetPrefetchType()
-          .IsIsolatedNetworkContextRequired()) {
+  if (prefetch_container->IsIsolatedNetworkContextRequiredForURL(url)) {
     prefetch_container->RegisterCookieListener(
         url, browser_context_->GetDefaultStoragePartition()
                  ->GetCookieManagerForBrowserProcess());
@@ -1019,15 +1014,18 @@
 
   // Check if the redirect requires a different network context than the
   // original prefetch.
-  net::SchemefulSite redirect_site(redirect_info.new_url);
-  bool is_isolated_network_context_required =
-      prefetch_container->GetReferringSite() != redirect_site;
+  bool redirect_requires_isolated_network_context =
+      prefetch_container->IsIsolatedNetworkContextRequiredForURL(
+          redirect_info.new_url);
+  bool previous_requires_isolated_network_context =
+      prefetch_container
+          ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop(
+              redirect_info.new_url);
   RecordRedirectNetworkContextTransition(
-      prefetch_container->GetPrefetchType().IsIsolatedNetworkContextRequired(),
-      is_isolated_network_context_required);
-  if (is_isolated_network_context_required !=
-      prefetch_container->GetPrefetchType()
-          .IsIsolatedNetworkContextRequired()) {
+      previous_requires_isolated_network_context,
+      redirect_requires_isolated_network_context);
+  if (redirect_requires_isolated_network_context !=
+      previous_requires_isolated_network_context) {
     // TODO(https://crbug.com/1266876): Allow for redirects to switch network
     // contexts in a prefetch.
 
@@ -1283,8 +1281,8 @@
 
   // We only need to copy cookies if the prefetch used an isolated network
   // context.
-  if (!prefetch_container->GetPrefetchType()
-           .IsIsolatedNetworkContextRequired()) {
+  if (!prefetch_container->IsIsolatedNetworkContextRequiredForURL(
+          prefetch_container->GetCurrentURLToServe())) {
     return;
   }
 
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index 149070a..d108977f 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -686,8 +686,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -776,8 +775,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -838,8 +836,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -910,8 +907,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1000,8 +996,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1078,8 +1073,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1136,9 +1130,9 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/false,
-                   blink::mojom::SpeculationEagerness::kEager));
+      PrefetchType(/*use_prefetch_proxy=*/false,
+                   blink::mojom::SpeculationEagerness::kEager),
+      /*referrer_url=*/GURL("https://example.com/referrer"));
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com"),
@@ -1204,8 +1198,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1269,8 +1262,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1324,8 +1316,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("http://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1389,8 +1380,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1453,8 +1443,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/false,
+      PrefetchType(/*use_prefetch_proxy=*/false,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1525,8 +1514,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1580,8 +1568,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://localhost"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/false,
+      PrefetchType(/*use_prefetch_proxy=*/false,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1648,8 +1635,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1708,8 +1694,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1775,8 +1760,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1833,8 +1817,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -1900,9 +1883,9 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/false,
-                   blink::mojom::SpeculationEagerness::kEager));
+      PrefetchType(/*use_prefetch_proxy=*/false,
+                   blink::mojom::SpeculationEagerness::kEager),
+      /*referrer_url=*/GURL("https://example.com/referrer"));
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com"),
@@ -1975,8 +1958,7 @@
   // is only enforced for cross-origin requests.
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       /*referrer_url=*/GURL("https://example.com/referring_page"));
   base::RunLoop().RunUntilIdle();
@@ -2055,8 +2037,7 @@
   // of prefetches are blocked.
   MakePrefetchOnMainFrame(
       GURL("https://other.example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       /*referrer_url=*/GURL("https://example.com/referring_page"));
   base::RunLoop().RunUntilIdle();
@@ -2122,8 +2103,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2186,9 +2166,9 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/false,
-                   blink::mojom::SpeculationEagerness::kEager));
+      PrefetchType(/*use_prefetch_proxy=*/false,
+                   blink::mojom::SpeculationEagerness::kEager),
+      /*referrer_url=*/GURL("https://example.com/referring_page"));
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com"),
@@ -2255,8 +2235,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2315,8 +2294,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2381,8 +2359,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2445,8 +2422,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2512,8 +2488,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2587,8 +2562,7 @@
   // number of prefetches.
   MakePrefetchOnMainFrame(
       GURL("https://example1.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2600,8 +2574,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example2.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2613,8 +2586,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example3.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2763,8 +2735,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2841,8 +2812,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2910,8 +2880,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -2975,8 +2944,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3060,8 +3028,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3124,8 +3091,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3183,8 +3149,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3261,8 +3226,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3396,8 +3360,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com/?a=1"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       /*referrer_url=*/absl::nullopt,
       /*enable_no_vary_search_header=*/true);
@@ -3488,8 +3451,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3586,8 +3548,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3679,8 +3640,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3767,8 +3727,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -3852,8 +3811,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/false,
+      PrefetchType(/*use_prefetch_proxy=*/false,
                    blink::mojom::SpeculationEagerness::kEager),
       /*referrer_url=*/GURL("https://example.com/referrer"));
 
@@ -3955,8 +3913,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager),
       /*referrer_url=*/GURL("https://example.com/referrer"));
 
@@ -4049,9 +4006,9 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/false,
-                   /*use_prefetch_proxy=*/false,
-                   blink::mojom::SpeculationEagerness::kEager));
+      PrefetchType(/*use_prefetch_proxy=*/false,
+                   blink::mojom::SpeculationEagerness::kEager),
+      /*referrer_url=*/GURL("https://example.com/referrer"));
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com"),
@@ -4147,8 +4104,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true,
+      PrefetchType(/*use_prefetch_proxy=*/true,
                    blink::mojom::SpeculationEagerness::kEager));
   base::RunLoop().RunUntilIdle();
 
@@ -4228,8 +4184,7 @@
 
   MakePrefetchOnMainFrame(
       GURL("https://example.com"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true, GetParam()));
+      PrefetchType(/*use_prefetch_proxy=*/true, GetParam()));
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com"),
@@ -4338,8 +4293,7 @@
           std::vector<std::string>({"a"}));
   MakePrefetchOnMainFrame(
       GURL("https://example.com/index.html?a=5"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true, GetParam()),
+      PrefetchType(/*use_prefetch_proxy=*/true, GetParam()),
       /* referrer_url */ absl::nullopt,
       /* no_vary_search_support */ true,
       /* no_vary_search_hint */ std::move(no_vary_search_hint));
@@ -4455,8 +4409,7 @@
           std::vector<std::string>({"a"}));
   MakePrefetchOnMainFrame(
       GURL("https://example.com/index.html?a=5"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true, GetParam()),
+      PrefetchType(/*use_prefetch_proxy=*/true, GetParam()),
       /* referrer_url */ absl::nullopt,
       /* no_vary_search_support */ true,
       /* no_vary_search_hint */ std::move(no_vary_search_hint));
@@ -4564,8 +4517,7 @@
           std::vector<std::string>({"a"}));
   MakePrefetchOnMainFrame(
       GURL("https://example.com/index.html?a=5"),
-      PrefetchType(/*use_isolated_network_context=*/true,
-                   /*use_prefetch_proxy=*/true, GetParam()),
+      PrefetchType(/*use_prefetch_proxy=*/true, GetParam()),
       /* referrer_url */ absl::nullopt,
       /* no_vary_search_support */ true,
       /* no_vary_search_hint */ std::move(no_vary_search_hint));
diff --git a/content/browser/preloading/prefetch/prefetch_type.cc b/content/browser/preloading/prefetch/prefetch_type.cc
index dc38b97..f681ce97 100644
--- a/content/browser/preloading/prefetch/prefetch_type.cc
+++ b/content/browser/preloading/prefetch/prefetch_type.cc
@@ -11,12 +11,9 @@
 
 namespace content {
 
-PrefetchType::PrefetchType(bool use_isolated_network_context,
-                           bool use_prefetch_proxy,
+PrefetchType::PrefetchType(bool use_prefetch_proxy,
                            blink::mojom::SpeculationEagerness eagerness)
-    : use_isolated_network_context_(use_isolated_network_context),
-      use_prefetch_proxy_(use_prefetch_proxy),
-      eagerness_(eagerness) {}
+    : use_prefetch_proxy_(use_prefetch_proxy), eagerness_(eagerness) {}
 
 PrefetchType::~PrefetchType() = default;
 PrefetchType::PrefetchType(const PrefetchType& prefetch_type) = default;
@@ -30,11 +27,9 @@
 
 bool operator==(const PrefetchType& prefetch_type_1,
                 const PrefetchType& prefetch_type_2) {
-  return std::tie(prefetch_type_1.use_isolated_network_context_,
-                  prefetch_type_1.use_prefetch_proxy_,
+  return std::tie(prefetch_type_1.use_prefetch_proxy_,
                   prefetch_type_1.eagerness_) ==
-         std::tie(prefetch_type_2.use_isolated_network_context_,
-                  prefetch_type_2.use_prefetch_proxy_,
+         std::tie(prefetch_type_2.use_prefetch_proxy_,
                   prefetch_type_2.eagerness_);
 }
 
diff --git a/content/browser/preloading/prefetch/prefetch_type.h b/content/browser/preloading/prefetch/prefetch_type.h
index f434e901..7abf3b30 100644
--- a/content/browser/preloading/prefetch/prefetch_type.h
+++ b/content/browser/preloading/prefetch/prefetch_type.h
@@ -14,20 +14,13 @@
 // handled.
 class CONTENT_EXPORT PrefetchType {
  public:
-  PrefetchType(bool use_isolated_network_context,
-               bool use_prefetch_proxy,
+  PrefetchType(bool use_prefetch_proxy,
                blink::mojom::SpeculationEagerness eagerness);
   ~PrefetchType();
 
   PrefetchType(const PrefetchType& prefetch_type);
   PrefetchType& operator=(const PrefetchType& prefetch_type);
 
-  // Whether prefetches of this type need to use an isolated network context, or
-  // use the default network context.
-  bool IsIsolatedNetworkContextRequired() const {
-    return use_isolated_network_context_;
-  }
-
   // Whether this prefetch should bypass the proxy even though it would need to
   // be proxied for anonymity. For use in test automation only.
   bool IsProxyBypassedForTesting() const { return proxy_bypassed_for_testing_; }
@@ -45,7 +38,6 @@
   friend CONTENT_EXPORT bool operator==(const PrefetchType& prefetch_type_1,
                                         const PrefetchType& prefetch_type_2);
 
-  bool use_isolated_network_context_;
   bool use_prefetch_proxy_;
   bool proxy_bypassed_for_testing_ = false;
   blink::mojom::SpeculationEagerness eagerness_;
diff --git a/content/browser/preloading/prefetch/prefetch_type_unittest.cc b/content/browser/preloading/prefetch/prefetch_type_unittest.cc
index 898b4d60..cd2c4839 100644
--- a/content/browser/preloading/prefetch/prefetch_type_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_type_unittest.cc
@@ -12,48 +12,35 @@
 class PrefetchTypeTest : public ::testing::Test {};
 
 TEST_F(PrefetchTypeTest, GetPrefetchTypeParams) {
-  PrefetchType prefetch_type1(/*use_isolated_network_context=*/true,
-                              /*use_prefetch_proxy=*/true,
+  PrefetchType prefetch_type1(/*use_prefetch_proxy=*/true,
                               blink::mojom::SpeculationEagerness::kEager);
-  PrefetchType prefetch_type2(/*use_isolated_network_context=*/true,
-                              /*use_prefetch_proxy=*/false,
+  PrefetchType prefetch_type2(/*use_prefetch_proxy=*/false,
                               blink::mojom::SpeculationEagerness::kEager);
   PrefetchType prefetch_type3(
-      /*use_isolated_network_context=*/false,
       /*use_prefetch_proxy=*/false,
       blink::mojom::SpeculationEagerness::kConservative);
 
-  EXPECT_TRUE(prefetch_type1.IsIsolatedNetworkContextRequired());
   EXPECT_TRUE(prefetch_type1.IsProxyRequiredWhenCrossOrigin());
   EXPECT_EQ(prefetch_type1.GetEagerness(),
             blink::mojom::SpeculationEagerness::kEager);
 
-  EXPECT_TRUE(prefetch_type2.IsIsolatedNetworkContextRequired());
   EXPECT_FALSE(prefetch_type2.IsProxyRequiredWhenCrossOrigin());
   EXPECT_EQ(prefetch_type2.GetEagerness(),
             blink::mojom::SpeculationEagerness::kEager);
 
-  EXPECT_FALSE(prefetch_type3.IsIsolatedNetworkContextRequired());
   EXPECT_FALSE(prefetch_type3.IsProxyRequiredWhenCrossOrigin());
   EXPECT_EQ(prefetch_type3.GetEagerness(),
             blink::mojom::SpeculationEagerness::kConservative);
 }
 
 TEST_F(PrefetchTypeTest, ComparePrefetchTypes) {
-  PrefetchType prefetch_type1(/*use_isolated_network_context=*/true,
-                              /*use_prefetch_proxy=*/true,
+  PrefetchType prefetch_type1(/*use_prefetch_proxy=*/true,
                               blink::mojom::SpeculationEagerness::kEager);
-  PrefetchType prefetch_type2(/*use_isolated_network_context=*/true,
-                              /*use_prefetch_proxy=*/true,
+  PrefetchType prefetch_type2(/*use_prefetch_proxy=*/true,
                               blink::mojom::SpeculationEagerness::kEager);
-  PrefetchType prefetch_type3(/*use_isolated_network_context=*/true,
-                              /*use_prefetch_proxy=*/false,
+  PrefetchType prefetch_type3(/*use_prefetch_proxy=*/false,
                               blink::mojom::SpeculationEagerness::kEager);
-  PrefetchType prefetch_type4(/*use_isolated_network_context=*/false,
-                              /*use_prefetch_proxy=*/false,
-                              blink::mojom::SpeculationEagerness::kEager);
-  PrefetchType prefetch_type5(
-      /*use_isolated_network_context=*/true,
+  PrefetchType prefetch_type4(
       /*use_prefetch_proxy=*/true,
       blink::mojom::SpeculationEagerness::kConservative);
 
@@ -62,19 +49,14 @@
   EXPECT_TRUE(prefetch_type1 == prefetch_type2);
   EXPECT_TRUE(prefetch_type1 != prefetch_type3);
   EXPECT_TRUE(prefetch_type1 != prefetch_type4);
-  EXPECT_TRUE(prefetch_type1 != prefetch_type5);
 }
 
 TEST_F(PrefetchTypeTest, WptProxyTest) {
   PrefetchType prefetch_types[] = {
-      {/*isolated*/ true, /*use_proxy*/ true,
-       blink::mojom::SpeculationEagerness::kEager},
-      {/*isolated*/ true, /*use_proxy*/ true,
-       blink::mojom::SpeculationEagerness::kEager},
-      {/*isolated*/ true, /*use_proxy*/ false,
-       blink::mojom::SpeculationEagerness::kEager},
-      {/*isolated*/ false, /*use_proxy*/ false,
-       blink::mojom::SpeculationEagerness::kEager},
+      {/*use_proxy*/ true, blink::mojom::SpeculationEagerness::kEager},
+      {/*use_proxy*/ true, blink::mojom::SpeculationEagerness::kEager},
+      {/*use_proxy*/ false, blink::mojom::SpeculationEagerness::kEager},
+      {/*use_proxy*/ false, blink::mojom::SpeculationEagerness::kEager},
   };
   for (auto& prefetch_type : prefetch_types) {
     EXPECT_FALSE(prefetch_type.IsProxyBypassedForTesting());
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
index e09e6ed..efd9b70 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -168,8 +168,8 @@
 
   void CopyIsolatedCookies(
       base::WeakPtr<PrefetchContainer> prefetch_container) override {
-    if (!prefetch_container->GetPrefetchType()
-             .IsIsolatedNetworkContextRequired()) {
+    if (!prefetch_container->IsIsolatedNetworkContextRequiredForURL(
+            prefetch_container->GetCurrentURLToServe())) {
       return;
     }
 
@@ -445,8 +445,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -522,8 +521,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -603,13 +601,14 @@
 
   // No cookies are copied for prefetches where |use_isolated_network_context|
   // is false (i.e. same origin prefetches).
+  blink::mojom::Referrer referrer;
+  referrer.url = GURL("https://example.com/referrer");
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/false,
-                       /*use_prefetch_proxy=*/false,
+          PrefetchType(/*use_prefetch_proxy=*/false,
                        blink::mojom::SpeculationEagerness::kEager),
-          blink::mojom::Referrer(),
+          referrer,
           /*no_vary_search_expected=*/absl::nullopt,
           blink::mojom::SpeculationInjectionWorld::kNone,
           /*prefetch_document_manager=*/nullptr);
@@ -703,8 +702,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -756,8 +754,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -814,8 +811,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -887,8 +883,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -942,8 +937,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -1001,8 +995,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
@@ -1088,8 +1081,7 @@
   std::unique_ptr<PrefetchContainer> prefetch_container =
       std::make_unique<PrefetchContainer>(
           main_rfh()->GetGlobalId(), kTestUrl,
-          PrefetchType(/*use_isolated_network_context=*/true,
-                       /*use_prefetch_proxy=*/true,
+          PrefetchType(/*use_prefetch_proxy=*/true,
                        blink::mojom::SpeculationEagerness::kEager),
           blink::mojom::Referrer(),
           /*no_vary_search_expected=*/absl::nullopt,
diff --git a/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc b/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
index cd2e8d2..8874cd9b 100644
--- a/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
+++ b/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
@@ -255,10 +255,9 @@
     // If there are other reasons present outside of cache-control:no-store
     // related reasons, the page is not eligible for storing.
     return Difference(not_restored_reasons_,
-                      NotRestoredReasons(
-                          Reason::kCacheControlNoStore,
-                          Reason::kCacheControlNoStoreCookieModified,
-                          Reason::kCacheControlNoStoreHTTPOnlyCookieModified))
+                      {Reason::kCacheControlNoStore,
+                       Reason::kCacheControlNoStoreCookieModified,
+                       Reason::kCacheControlNoStoreHTTPOnlyCookieModified})
         .Empty();
   } else {
     return not_restored_reasons_.Empty();
diff --git a/content/browser/renderer_host/back_forward_cache_disable.cc b/content/browser/renderer_host/back_forward_cache_disable.cc
index 216d4601..5014dc19 100644
--- a/content/browser/renderer_host/back_forward_cache_disable.cc
+++ b/content/browser/renderer_host/back_forward_cache_disable.cc
@@ -20,8 +20,6 @@
       return "FileChooser";
     case BackForwardCacheDisable::DisabledReasonId::kSerial:
       return "Serial";
-    case BackForwardCacheDisable::DisabledReasonId::kFileSystemAccess:
-      return "FileSystemAccess";
     case BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost:
       return "MediaDevicesDispatcherHost";
     case BackForwardCacheDisable::DisabledReasonId::kWebBluetooth:
diff --git a/content/browser/renderer_host/back_forward_cache_disable.h b/content/browser/renderer_host/back_forward_cache_disable.h
index 53dc4b4..1ecac99 100644
--- a/content/browser/renderer_host/back_forward_cache_disable.h
+++ b/content/browser/renderer_host/back_forward_cache_disable.h
@@ -26,7 +26,7 @@
     kWebAuthenticationAPI = 3,
     kFileChooser = 4,
     kSerial = 5,
-    kFileSystemAccess = 6,
+    // kFileSystemAccess = 6, Removed. See https://crbug.com/1259861.
     kMediaDevicesDispatcherHost = 7,
     kWebBluetooth = 8,
     kWebUSB = 9,
diff --git a/content/browser/renderer_host/back_forward_cache_impl.cc b/content/browser/renderer_host/back_forward_cache_impl.cc
index 735b299..9615a72 100644
--- a/content/browser/renderer_host/back_forward_cache_impl.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl.cc
@@ -181,7 +181,7 @@
 // cache. Some of these features are listed as blocking back/forward cache
 // when actually the blocking is flag controlled and they are not registered
 // as being used if we don't want them to block.
-constexpr WebSchedulerTrackedFeatures kDisallowedFeatures(
+constexpr WebSchedulerTrackedFeatures kDisallowedFeatures = {
     WebSchedulerTrackedFeature::kBroadcastChannel,
     WebSchedulerTrackedFeature::kContainsPlugins,
     WebSchedulerTrackedFeature::kDedicatedWorkerOrWorklet,
@@ -212,17 +212,17 @@
     WebSchedulerTrackedFeature::kWebShare,
     WebSchedulerTrackedFeature::kWebSocket,
     WebSchedulerTrackedFeature::kWebTransport,
-    WebSchedulerTrackedFeature::kWebXR);
-constexpr WebSchedulerTrackedFeatures kInjectionFeatures(
+    WebSchedulerTrackedFeature::kWebXR};
+constexpr WebSchedulerTrackedFeatures kInjectionFeatures = {
     WebSchedulerTrackedFeature::kInjectedJavascript,
-    WebSchedulerTrackedFeature::kInjectedStyleSheet);
-constexpr WebSchedulerTrackedFeatures kNetworkFeatures(
+    WebSchedulerTrackedFeature::kInjectedStyleSheet};
+constexpr WebSchedulerTrackedFeatures kNetworkFeatures = {
     WebSchedulerTrackedFeature::kOutstandingNetworkRequestOthers,
     WebSchedulerTrackedFeature::kOutstandingNetworkRequestFetch,
-    WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR);
+    WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR};
 // A list of WebSchedulerTrackedFeatures that should never block back/forward
 // cache.
-constexpr WebSchedulerTrackedFeatures kAllowedFeatures(
+constexpr WebSchedulerTrackedFeatures kAllowedFeatures = {
     WebSchedulerTrackedFeature::kDocumentLoaded,
     WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoCache,
     // This is handled in |UpdateCanStoreToIncludeCacheControlNoStore()|, and no
@@ -238,7 +238,7 @@
     // main frame.
     WebSchedulerTrackedFeature::kAuthorizationHeader,
     // TODO(crbug.com/1357482): Figure out if this should be allowed.
-    WebSchedulerTrackedFeature::kWebNfc);
+    WebSchedulerTrackedFeature::kWebNfc};
 
 // The BackForwardCache feature is controlled via an experiment. This function
 // returns the allowed URL list where it is enabled.
@@ -829,7 +829,7 @@
       // |should_cache_control_no_store_enter| flag is false. If true, put the
       // page in and evict later.
       result.NoDueToFeatures(
-          WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoStore);
+          {WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoStore});
     }
   }
 
@@ -898,7 +898,7 @@
           root_rfh_->GetLastCommittedOrigin()) &&
       rfh->GetBackForwardCacheDisablingFeatures().Has(
           WebSchedulerTrackedFeature::kAuthorizationHeader)) {
-    result.NoDueToFeatures(WebSchedulerTrackedFeature::kAuthorizationHeader);
+    result.NoDueToFeatures({WebSchedulerTrackedFeature::kAuthorizationHeader});
   }
 }
 
diff --git a/content/browser/renderer_host/back_forward_cache_metrics_browsertest.cc b/content/browser/renderer_host/back_forward_cache_metrics_browsertest.cc
index 9f591a0..e42d7977b 100644
--- a/content/browser/renderer_host/back_forward_cache_metrics_browsertest.cc
+++ b/content/browser/renderer_host/back_forward_cache_metrics_browsertest.cc
@@ -52,15 +52,13 @@
 // which are related to the document finishing loading).
 // We ignore them to make tests easier to read and write.
 
-constexpr blink::scheduler::WebSchedulerTrackedFeatures kFeaturesToIgnore =
-    blink::scheduler::WebSchedulerTrackedFeatures(
-        blink::scheduler::WebSchedulerTrackedFeature::kDocumentLoaded,
-        blink::scheduler::WebSchedulerTrackedFeature::
-            kOutstandingNetworkRequestFetch,
-        blink::scheduler::WebSchedulerTrackedFeature::
-            kOutstandingNetworkRequestXHR,
-        blink::scheduler::WebSchedulerTrackedFeature::
-            kOutstandingNetworkRequestOthers);
+constexpr blink::scheduler::WebSchedulerTrackedFeatures kFeaturesToIgnore = {
+    blink::scheduler::WebSchedulerTrackedFeature::kDocumentLoaded,
+    blink::scheduler::WebSchedulerTrackedFeature::
+        kOutstandingNetworkRequestFetch,
+    blink::scheduler::WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR,
+    blink::scheduler::WebSchedulerTrackedFeature::
+        kOutstandingNetworkRequestOthers};
 
 using UkmMetrics = ukm::TestUkmRecorder::HumanReadableUkmMetrics;
 using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
@@ -121,7 +119,7 @@
     EXPECT_EQ(base::Difference(
                   current_frame_host()->GetBackForwardCacheDisablingFeatures(),
                   kFeaturesToIgnore),
-              blink::scheduler::WebSchedulerTrackedFeatures(feature));
+              blink::scheduler::WebSchedulerTrackedFeatures({feature}));
 
     // Close the web contents to ensure that no new notifications arrive to the
     // function local callback above after this function has returned.
diff --git a/content/browser/renderer_host/input/scroll_latency_browsertest.cc b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
index 79dd662..8530ec3f 100644
--- a/content/browser/renderer_host/input/scroll_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
@@ -372,7 +372,8 @@
 };
 
 // Crashes on Mac ASAN.  https://crbug.com/1188553
-#if BUILDFLAG(IS_MAC)
+// TODO(crbug/1188553): Flaky on Linux Wayland CI/CQ builders.
+#if BUILDFLAG(IS_MAC) || defined(OZONE_PLATFORM_WAYLAND)
 #define MAYBE_ScrollbarThumbDragLatency DISABLED_ScrollbarThumbDragLatency
 #else
 #define MAYBE_ScrollbarThumbDragLatency ScrollbarThumbDragLatency
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
index 3f86329..942122cf 100644
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
@@ -127,13 +127,6 @@
   }
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-bool ShouldUseDesktopCaptureLacrosV2() {
-  return base::FeatureList::IsEnabled(features::kDesktopCaptureLacrosV2) &&
-         VideoCaptureDeviceProxyLacros::IsAvailable();
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 enum DesktopCaptureImplementation {
@@ -352,19 +345,16 @@
 #endif  // defined(USE_AURA) || BUILDFLAG(IS_MAC)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-      if (ShouldUseDesktopCaptureLacrosV2()) {
-        TRACE_EVENT_INSTANT0(
-            TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
-            "UsingDesktopCaptureLacrosV2", TRACE_EVENT_SCOPE_THREAD);
-        start_capture_closure = base::BindOnce(
-            &InProcessVideoCaptureDeviceLauncher::
-                DoStartDesktopCaptureWithReceiverOnDeviceThread,
-            base::Unretained(this), desktop_id, params, std::move(receiver),
-            std::move(after_start_capture_callback));
-        break;
-      }
-#endif
-
+      TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
+                           "UsingDesktopCaptureLacrosV2",
+                           TRACE_EVENT_SCOPE_THREAD);
+      start_capture_closure = base::BindOnce(
+          &InProcessVideoCaptureDeviceLauncher::
+              DoStartDesktopCaptureWithReceiverOnDeviceThread,
+          base::Unretained(this), desktop_id, params, std::move(receiver),
+          std::move(after_start_capture_callback));
+      break;
+#else
       // All cases other than tab capture or Aura desktop/window capture.
       TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
                            "UsingDesktopCapturer", TRACE_EVENT_SCOPE_THREAD);
@@ -377,6 +367,7 @@
                              std::move(receiver_on_io_thread)),
           std::move(after_start_capture_callback));
       break;
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
     }
 #endif  // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
 
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 01aac20..1835a73 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -3702,16 +3702,16 @@
   RenderFrameHostImpl* main_frame = web_contents()->GetPrimaryMainFrame();
   // Simulate getting WebSocket in a feature vector from the renderer.
   main_frame->DidChangeBackForwardCacheDisablingFeatures(
-      CreateBlockingDetails(BlocklistedFeature::kWebSocket));
+      CreateBlockingDetails({BlocklistedFeature::kWebSocket}));
   ASSERT_EQ(main_frame->GetBackForwardCacheDisablingFeatures(),
-            BlocklistedFeatures(BlocklistedFeature::kWebSocket));
+            BlocklistedFeatures({BlocklistedFeature::kWebSocket}));
 
   // Simulate the browser side reporting WebRTC usage.
   main_frame->OnBackForwardCacheDisablingStickyFeatureUsed(
       static_cast<BlocklistedFeature>(BlocklistedFeature::kWebRTC));
   ASSERT_EQ(main_frame->GetBackForwardCacheDisablingFeatures(),
-            BlocklistedFeatures(BlocklistedFeature::kWebSocket,
-                                BlocklistedFeature::kWebRTC));
+            BlocklistedFeatures(
+                {BlocklistedFeature::kWebSocket, BlocklistedFeature::kWebRTC}));
 
   // Simulate a feature vector being updated from the renderer with some
   // features being activated and some being deactivated.
@@ -3722,8 +3722,8 @@
        BlocklistedFeature::kMainResourceHasCacheControlNoCache}));
   ASSERT_EQ(main_frame->GetBackForwardCacheDisablingFeatures(),
             BlocklistedFeatures(
-                BlocklistedFeature::kWebRTC,
-                BlocklistedFeature::kMainResourceHasCacheControlNoCache));
+                {BlocklistedFeature::kWebRTC,
+                 BlocklistedFeature::kMainResourceHasCacheControlNoCache}));
 
   // Navigate away and expect that no values persist the navigation.
   // Note that we are still simulating the renderer call, otherwise features
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 82af944..bcfe121 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -1574,7 +1574,7 @@
   }
 
   // TODO(crbug.com/1429083): record the appropriate metrics.
-  ShowModalDialog(idp->config_url);
+  ShowModalDialog(continue_on);
 }
 
 void FederatedAuthRequestImpl::OnTokenResponseReceived(
diff --git a/content/browser/webid/webid_browsertest.cc b/content/browser/webid/webid_browsertest.cc
index 5d3c203..c132926 100644
--- a/content/browser/webid/webid_browsertest.cc
+++ b/content/browser/webid/webid_browsertest.cc
@@ -756,10 +756,13 @@
   // Points the id assertion endpoint to a servlet.
   config_details.id_assertion_endpoint_url = "/authz/id_assertion_endpoint.php";
 
+  auto continue_on = GURL(BaseIdpUrl()).Resolve("/authz.html");
+
   // Add a servlet to serve a response for the id assertoin endpoint.
   config_details.servlets["/authz/id_assertion_endpoint.php"] =
       base::BindRepeating(
-          [](const HttpRequest& request) -> std::unique_ptr<HttpResponse> {
+          [](GURL url,
+             const HttpRequest& request) -> std::unique_ptr<HttpResponse> {
             std::string content;
             content += "client_id=client_id_1&";
             content += "nonce=12345&";
@@ -774,10 +777,11 @@
             response->set_content_type("text/json");
             // scope=calendar.readonly was requested, so need to
             // return a continuation url instead of a token.
-            response->set_content(
-                R"({"continue_on": "https://idp.example/continue.php"})");
+            auto body = R"({"continue_on": ")" + url.spec() + R"("})";
+            response->set_content(body);
             return response;
-          });
+          },
+          continue_on);
 
   idp_server()->SetConfigResponseDetails(config_details);
 
@@ -807,13 +811,15 @@
 
   base::RunLoop run_loop;
   EXPECT_CALL(*controller, ShowModalDialog(_, _))
-      .WillOnce(::testing::WithArg<0>([&modal, &run_loop](const GURL& url) {
-        // When the pop-up window is opened, resolve it immediately by
-        // returning a test web contents, which can then later be used
-        // to refer to the identity registry.
-        run_loop.Quit();
-        return modal->web_contents();
-      }));
+      .WillOnce(::testing::WithArg<0>(
+          [&continue_on, &modal, &run_loop](const GURL& url) {
+            EXPECT_EQ(url, continue_on);
+            // When the pop-up window is opened, resolve it immediately by
+            // returning a test web contents, which can then later be used
+            // to refer to the identity registry.
+            run_loop.Quit();
+            return modal->web_contents();
+          }));
 
   std::string script = R"(
           var result = navigator.credentials.get({
diff --git a/content/browser/webrtc/webrtc_internals_unittest.cc b/content/browser/webrtc/webrtc_internals_unittest.cc
index a661597..3db5313 100644
--- a/content/browser/webrtc/webrtc_internals_unittest.cc
+++ b/content/browser/webrtc/webrtc_internals_unittest.cc
@@ -252,9 +252,10 @@
 
   ASSERT_TRUE(observer.event_data()->is_list());
   EXPECT_EQ(1U, observer.event_data()->GetList().size());
-  base::Value& dict = observer.event_data()->GetList()[0];
-  ASSERT_TRUE(dict.is_dict());
-  ASSERT_FALSE(dict.FindPath("log"));
+  const base::Value::Dict* dict =
+      observer.event_data()->GetList()[0].GetIfDict();
+  ASSERT_TRUE(dict);
+  ASSERT_FALSE(dict->Find("log"));
 
   webrtc_internals.OnPeerConnectionRemoved(kFrameId, kLid);
 
@@ -279,9 +280,10 @@
 
   ASSERT_TRUE(observer.event_data()->is_list());
   EXPECT_EQ(1U, observer.event_data()->GetList().size());
-  base::Value& dict = observer.event_data()->GetList()[0];
-  ASSERT_TRUE(dict.is_dict());
-  ASSERT_TRUE(dict.FindPath("log")->is_list());
+  const base::Value::Dict* dict =
+      observer.event_data()->GetList()[0].GetIfDict();
+  ASSERT_TRUE(dict);
+  ASSERT_TRUE(dict->FindList("log"));
 
   // Make sure we the log entry was removed when the last observer was removed.
   webrtc_internals.RemoveObserver(&observer);
@@ -290,9 +292,9 @@
 
   ASSERT_TRUE(observer.event_data()->is_list());
   EXPECT_EQ(1U, observer.event_data()->GetList().size());
-  base::Value& updated_dict = observer.event_data()->GetList()[0];
-  ASSERT_TRUE(updated_dict.is_dict());
-  ASSERT_FALSE(updated_dict.FindPath("log"));
+  const base::Value::Dict* updated_dict =
+      observer.event_data()->GetList()[0].GetIfDict();
+  ASSERT_FALSE(updated_dict->Find("log"));
 
   webrtc_internals.OnPeerConnectionRemoved(kFrameId, kLid);
 
diff --git a/content/child/child_histogram_fetcher_impl.cc b/content/child/child_histogram_fetcher_impl.cc
index ff7820b..3293e96 100644
--- a/content/child/child_histogram_fetcher_impl.cc
+++ b/content/child/child_histogram_fetcher_impl.cc
@@ -12,6 +12,7 @@
 #include "base/location.h"
 #include "base/metrics/histogram_delta_serialization.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_macros_local.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "content/child/child_process.h"
 #include "ipc/ipc_sender.h"
@@ -41,6 +42,10 @@
     base::GlobalHistogramAllocator::CreateWithSharedMemoryRegion(shared_memory);
   }
 
+  // Emit a local histogram, which should not be reported to servers. This is
+  // monitored from the serverside.
+  LOCAL_HISTOGRAM_BOOLEAN("UMA.LocalHistogram", true);
+
   base::PersistentHistogramAllocator* global_allocator =
       base::GlobalHistogramAllocator::Get();
   if (global_allocator)
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 3221529..0b8f31f 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -298,8 +298,6 @@
       "font_list_android.cc",
     ]
 
-    deps += [ "//cc/slim" ]
-
     if (use_seccomp_bpf) {
       sources += [
         "//sandbox/policy/linux/bpf_base_policy_linux.cc",
diff --git a/content/common/features.cc b/content/common/features.cc
index d4c9c54..dbbb230 100644
--- a/content/common/features.cc
+++ b/content/common/features.cc
@@ -7,29 +7,10 @@
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 
-#if BUILDFLAG(IS_ANDROID)
-#include "cc/slim/features.h"  // nogncheck
-#include "ui/gfx/android/android_surface_control_compat.h"
-#endif
-
 namespace content {
 
 // Please keep features in alphabetical order.
 
-#if BUILDFLAG(IS_ANDROID)
-BASE_FEATURE(kAndroidSurfaceControlMagnifier,
-             "AndroidSurfaceControlMagnifier",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAndroidSurfaceControlMagnifierEnabled() {
-  static bool enabled =
-      gfx::SurfaceControl::SupportsSurfacelessControl() &&
-      features::IsSlimCompositorEnabled() &&
-      base::FeatureList::IsEnabled(kAndroidSurfaceControlMagnifier);
-  return enabled;
-}
-#endif  // BUILDFLAG(IS_ANDROID)
-
 BASE_FEATURE(kNavigationUpdatesChildViewsVisibility,
              "NavigationUpdatesChildViewsVisibility",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/content/common/features.h b/content/common/features.h
index f9a88590..7d1ce56 100644
--- a/content/common/features.h
+++ b/content/common/features.h
@@ -14,15 +14,6 @@
 
 // Please keep features in alphabetical order.
 
-#if BUILDFLAG(IS_ANDROID)
-// Use chromim's implementation of selection magnifier built using surface
-// control APIs, instead of using the system-provided magnifier.
-BASE_DECLARE_FEATURE(kAndroidSurfaceControlMagnifier);
-
-CONTENT_EXPORT bool IsAndroidSurfaceControlMagnifierEnabled();
-
-#endif  // BUILDFLAG(IS_ANDROID)
-
 // When enabled, RenderFrameHostManager::CommitPending will also update the
 // visibility of all child views, not just that of the main frame.
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kNavigationUpdatesChildViewsVisibility);
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 2f7f31e..6793c415 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -29,6 +29,14 @@
              "AndroidDownloadableFontsMatching",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_ANDROID)
+// Use chromim's implementation of selection magnifier built using surface
+// control APIs, instead of using the system-provided magnifier.
+BASE_FEATURE(kAndroidSurfaceControlMagnifier,
+             "AndroidSurfaceControlMagnifier",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // Enables FLEDGE and Attribution Reporting API integration.
 BASE_FEATURE(kAttributionFencedFrameReportingBeacon,
              "AttributionFencedFrameReportingBeacon",
@@ -279,13 +287,6 @@
              "DesktopCaptureChangeSource",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-// Enables the alternative, improved desktop/window capturer for LaCrOS
-BASE_FEATURE(kDesktopCaptureLacrosV2,
-             "DesktopCaptureLacrosV2",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-#endif
-
 // Adds a tab strip to PWA windows.
 // TODO(crbug.com/897314): Enable this feature.
 BASE_FEATURE(kDesktopPWAsTabStrip,
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 0cd21701..2ec4d733f 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -21,6 +21,9 @@
 // alongside the definition of their values in the .cc file.
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAllowContentInitiatedDataUrlNavigations);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAndroidDownloadableFontsMatching);
+#if BUILDFLAG(IS_ANDROID)
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kAndroidSurfaceControlMagnifier);
+#endif
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAttributionFencedFrameReportingBeacon);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAudioServiceLaunchOnStartup);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAudioServiceOutOfProcess);
@@ -60,9 +63,6 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kCriticalClientHint);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDebugHistoryInterventionNoUserActivation);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDesktopCaptureChangeSource);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-CONTENT_EXPORT BASE_DECLARE_FEATURE(kDesktopCaptureLacrosV2);
-#endif
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDesktopPWAsTabStrip);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDevicePosture);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDigitalGoodsApi);
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 d2f68c17..e8ed3a1 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
@@ -303,6 +303,14 @@
 media::GpuVideoAcceleratorFactories::OutputFormat
 GpuVideoAcceleratorFactoriesImpl::VideoFrameOutputFormat(
     media::VideoPixelFormat pixel_format) {
+  auto format = VideoFrameOutputFormatImpl(pixel_format);
+  UMA_HISTOGRAM_ENUMERATION("Media.GPU.OutputFormat", format);
+  return format;
+}
+
+media::GpuVideoAcceleratorFactories::OutputFormat
+GpuVideoAcceleratorFactoriesImpl::VideoFrameOutputFormatImpl(
+    media::VideoPixelFormat pixel_format) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   if (CheckContextLost())
     return media::GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED;
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 bf8aa6e..2999bf6 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
@@ -167,6 +167,9 @@
 
   void OnChannelTokenReady(const base::UnguessableToken& token);
 
+  // Implementation of VideoFrameOutputFormat method.
+  OutputFormat VideoFrameOutputFormatImpl(media::VideoPixelFormat pixel_format);
+
   const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   const scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 6c04ff6..3b8b76d 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -1101,6 +1101,7 @@
     case XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT:
     case XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO:
     case XR_REFERENCE_SPACE_TYPE_MAX_ENUM:
+    case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT:
       NOTREACHED();
   }
 
diff --git a/device/vr/openxr/openxr_platform_helper.cc b/device/vr/openxr/openxr_platform_helper.cc
index 4b15c8ec..d4698ce 100644
--- a/device/vr/openxr/openxr_platform_helper.cc
+++ b/device/vr/openxr/openxr_platform_helper.cc
@@ -5,6 +5,7 @@
 
 #include <memory>
 
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/version.h"
 #include "build/build_config.h"
@@ -56,8 +57,9 @@
       << "Each Process is only allowed one XrInstance at a time";
   XrInstanceCreateInfo instance_create_info = {XR_TYPE_INSTANCE_CREATE_INFO};
 
-  std::string application_name = version_info::GetProductName() + " " +
-                                 version_info::GetMajorVersionNumber();
+  std::string application_name =
+      base::StrCat({version_info::GetProductName(), " ",
+                    version_info::GetMajorVersionNumber()});
   size_t dest_size =
       std::size(instance_create_info.applicationInfo.applicationName);
   size_t src_size =
diff --git a/docs/security/faq.md b/docs/security/faq.md
index e96245a2..ce8f9c7 100644
--- a/docs/security/faq.md
+++ b/docs/security/faq.md
@@ -47,6 +47,35 @@
 there is no need to further assess the risk of Chromium vulnerabilities: we
 strive to fix vulnerabilities quickly and release often.
 
+<a name="TOC-How-can-I-know-which-fixes-to-include-in-my-downstream-project-">
+### How can I know which fixes to include in my downstream project?
+
+Chrome is built with mitigations and hardening which aim to prevent or reduce
+the impact of security issues. We classify bugs as security issues if they are
+known to affect a version and configuration of Chrome that we ship to the
+public. Some classes of bug might present as security issues if Chrome was
+compiled with different flags, or linked against a different C++ standard
+library, but do not with the toolchain and configuration that we use to build
+Chrome. We discuss some of these cases elsewhere in this FAQ.
+
+If we become aware of them, these issues may be triaged as `Type=Bug-Security,
+Security_Impact=None` or as `Type=Bug` because they do not affect the production
+version of Chrome. They may or may not be immediately visible to the public in
+the bug tracker, and may or may not be identified as security issues. If fixes
+are landed, they may or may not be merged from HEAD to a release branch. Chrome
+will only label, fix and merge security issues in Chrome, but attackers can
+still analyze public issues, or commits in the Chromium project to identify bugs
+that might be exploitable in other contexts.
+
+Chromium embedders and other downstream projects may build with different
+compilers, compile options, target operating systems, standard library, or
+additional software components. It is possible that some issues Chrome
+classifies as functional issues will manifest as security issues in a product
+embedding Chromium - it is the responsibility of any such project to understand
+what code they are shipping, and how it is compiled. We recommend using Chrome's
+[configuration](https://source.chromium.org/chromium/chromium/src/+/main:build/config/)
+whenever possible.
+
 <a name="TOC-Can-I-see-these-security-bugs-so-that-I-can-back-port-the-fixes-to-my-downstream-project-"></a>
 ### Can I see these security bugs so that I can back-port the fixes to my downstream project?
 
@@ -571,7 +600,7 @@
 
 To enable certificate chain validation, Chrome has access to two stores of trust
 anchors (i.e., certificates that are empowered as issuers). One trust anchor
-store is for authenticating public internet servers, and depending on the 
+store is for authenticating public internet servers, and depending on the
 version of Chrome being used and the platform it is running on, the
 [Chrome Root Store](https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/faq.md#what-is-the-chrome-root-store)
 might be in use. The private store contains certificates installed by the user
@@ -686,25 +715,25 @@
 ### What's the story with certificate revocation?
 
 Chrome's primary mechanism for checking certificate revocation status is
-[CRLsets](https://dev.chromium.org/Home/chromium-security/crlsets). 
+[CRLsets](https://dev.chromium.org/Home/chromium-security/crlsets).
 Additionally, by default, [stapled Online Certificate Status Protocol (OCSP)
 responses](https://en.wikipedia.org/wiki/OCSP_stapling) are honored.
 
 "Online" certificate revocation status checks using Certificate Revocation
 List (CRL) or OCSP URLs included in certificates are disabled by default. This
 is because unless a client, like Chrome, refuses to connect to a website if it
-cannot get a valid response, online checks offer limited security value. 
+cannot get a valid response, online checks offer limited security value.
 
 Unfortunately, there are many widely-prevalent causes for why a client
 might be unable to get a valid certificate revocation status response to
 include:
 * timeouts (e.g., an OCSP responder is online but does not respond within an
-  acceptable time limit), 
-* availability issues (e.g., the OCSP responder is offline), 
-* invalid responses (e.g., a "stale" or malformed status response), and 
-* local network attacks misrouting traffic or blocking responses. 
+  acceptable time limit),
+* availability issues (e.g., the OCSP responder is offline),
+* invalid responses (e.g., a "stale" or malformed status response), and
+* local network attacks misrouting traffic or blocking responses.
 
-Additional concern with OCSP checks are related to privacy. OCSP 
+Additional concern with OCSP checks are related to privacy. OCSP
 requests reveal details of individuals' browsing history to the operator of the
 OCSP responder (i.e., a third party). These details can be exposed accidentally
 (e.g., via data breach of logs) or intentionally (e.g., via subpoena). Chrome
diff --git a/docs/speed/binary_size/android_binary_size_trybot.md b/docs/speed/binary_size/android_binary_size_trybot.md
index 348c491..1fb69f96 100644
--- a/docs/speed/binary_size/android_binary_size_trybot.md
+++ b/docs/speed/binary_size/android_binary_size_trybot.md
@@ -22,7 +22,7 @@
 
 ## Checks:
 
-- All monitored differences will be displayed below your CL on gerrit's CL
+- All monitored differences will be displayed below your CL on Gerrit's CL
   review page (in the Binary Size section).
 - Non-bordered changes are small changes below the failure limit.
 - Red-bordered changes are above the limit and are failing the tryjob.
@@ -43,7 +43,7 @@
 - Look at the provided symbol diffs to understand where the size is coming from.
 - See if any of the generic [optimization advice] is applicable.
 - If you are writing a new feature or including a new library you might want to
-  think about skipping the android platform and to restrict this new
+  think about skipping the Android platform and to restrict this new
   feature/library to desktop platforms that might care less about binary size.
 - If reduction is not practical, add a rationale for the increase to the commit
   description. It should include:
@@ -51,12 +51,13 @@
     - If you think that there might not be a consensus that the code your adding
       is worth the added file size, then add why you think it is.
         - To get a feeling for how large existing features are, refer to
-          [go/chrome-supersize] (Googlers only).
+          [go/chrome-supersize](Googlers only).
 
 - Add a footer to the commit description along the lines of:
     - `Binary-Size: Size increase is unavoidable (see above).`
     - `Binary-Size: Increase is temporary.`
-    - `Binary-Size: See commit description.` <-- use this if longer than one line.
+    - `Binary-Size: See commit description.` <-- use this if longer than one
+      line.
 
 ***note
 **Note:** Make sure there are no blank lines between `Binary-Size:` and other
@@ -70,8 +71,8 @@
 
 ### Dex Method Count
 
-- **What:** Checks that the number of Java methods after optimization does not
-  increase by more than 50.
+- **What:** Checks that the number of Java / Kotlin methods after optimization
+  does not increase by more than 50.
 - **Why:** Ensures that large changes to this metric are scrutinized.
 
 #### What to do if the Check Fails?
@@ -131,9 +132,9 @@
 
 [LOGICALLY_CONST]: https://source.chromium.org/search?q=symbol:LOGICALLY_CONST
 
-### Added Symbols named “ForTest”
+### Added Symbols named "ForTest"
 
-- **What:** This checks that we don't have java symbols with “ForTest” in their
+- **What:** This checks that we don't have Java symbols with "ForTest" in their
   name in an optimized release APK.
 - **Why:** To prevent shipping unused test-only code to end-users.
 
@@ -169,7 +170,7 @@
 
 - **What & Why:** Learn about these expectation files [here][expectation files].
 
-[expectation files]: /chrome/android/java/README.md
+[expectation files]: /chrome/android/expectations/README.md
 
 #### What to do if the Check Fails?
 
@@ -183,12 +184,12 @@
 - Not all checks are perfect and sometimes you want to overrule the trybot (for
   example if you did your best and are unable to reduce binary size any
   further).
-- Adding a “Binary-Size: $ANY\_TEXT\_HERE” footer to your cl (next to “Bug:”)
+- Adding a "Binary-Size: $ANY\_TEXT\_HERE" footer to your CL (next to "Bug:")
   will bypass the bot assertions.
     - Most commits that trigger the warnings will also result in Telemetry
       alerts and be reviewed by a binary size sheriff. Failing to write an
       adequate justification may lead to the binary size sheriff filing a bug
-      against you to improve your cl.
+      against you to improve your CL.
 
 [binary-size@chromium.org]: https://groups.google.com/a/chromium.org/forum/#!forum/binary-size
 
@@ -204,10 +205,10 @@
 - This is the text diff produced by the supersize tool.
 - It lists all changed symbols and for each one, which section it lives in,
   which source file it came from as well as what is its size before, after and
-  the delta for your cl.
+  the delta for your CL.
 - It also contains a histogram of symbol size deltas.
 - You can use this to find which symbols grew and where the binary size impact
-  of your cl comes from.
+  of your CL comes from.
 
 ### Supersize html diff
 
diff --git a/fuchsia_web/common/init_logging.cc b/fuchsia_web/common/init_logging.cc
index fc0d98a0..b7d74ef 100644
--- a/fuchsia_web/common/init_logging.cc
+++ b/fuchsia_web/common/init_logging.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "components/version_info/version_info.h"
@@ -49,7 +50,8 @@
       "Starting %.*s %s", base::saturated_cast<int>(component_name.length()),
       component_name.data(), version_info::GetVersionNumber().c_str());
 #if !defined(OFFICIAL_BUILD)
-  version_string += " (built at " + version_info::GetLastChange() + ")";
+  version_string +=
+      base::StrCat({" (built at ", version_info::GetLastChange(), ")"});
 #endif  // !defined(OFFICIAL_BUILD)
 
   LOG(INFO) << version_string;
diff --git a/fuchsia_web/runners/cast/cast_runner_integration_test.cc b/fuchsia_web/runners/cast/cast_runner_integration_test.cc
index e7ec28c..cdf5b0fa 100644
--- a/fuchsia_web/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia_web/runners/cast/cast_runner_integration_test.cc
@@ -595,9 +595,9 @@
       GetDevToolsListFromPort(CastRunner::kRemoteDebuggingPort);
   EXPECT_EQ(devtools_list.size(), 1u);
 
-  base::Value* devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), app_url.spec());
+  const auto* devtools_url = devtools_list[0].GetDict().FindString("url");
+  ASSERT_TRUE(devtools_url);
+  EXPECT_EQ(*devtools_url, app_url.spec());
 }
 
 TEST_F(CastRunnerIntegrationTest, IsolatedContext) {
@@ -1044,9 +1044,11 @@
   base::Value::List devtools_list =
       GetDevToolsListFromPort(remote_debugging_port);
   EXPECT_EQ(devtools_list.size(), 1u);
-  base::Value* devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url);
+  {
+    const auto* devtools_url = devtools_list[0].GetDict().FindString("url");
+    ASSERT_TRUE(devtools_url);
+    EXPECT_EQ(*devtools_url, url);
+  }
 
   // Create a new `FrameHost` client, and immediately close it. The DevTools
   // port should remain open regardless.
@@ -1063,9 +1065,11 @@
 
   devtools_list = GetDevToolsListFromPort(remote_debugging_port);
   EXPECT_EQ(devtools_list.size(), 1u);
-  devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url2);
+  {
+    const auto* devtools_url = devtools_list[0].GetDict().FindString("url");
+    ASSERT_TRUE(devtools_url);
+    EXPECT_EQ(*devtools_url, url2);
+  }
 }
 
 #if defined(ARCH_CPU_ARM_FAMILY)
diff --git a/fuchsia_web/webengine/web_engine_debug_integration_test.cc b/fuchsia_web/webengine/web_engine_debug_integration_test.cc
index 7b35230..0297fd8 100644
--- a/fuchsia_web/webengine/web_engine_debug_integration_test.cc
+++ b/fuchsia_web/webengine/web_engine_debug_integration_test.cc
@@ -122,13 +122,14 @@
       GetDevToolsListFromPort(*dev_tools_listener_.debug_ports().begin());
   EXPECT_EQ(devtools_list.size(), 1u);
 
-  base::Value* devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url);
+  const auto& devtools_dict = devtools_list[0].GetDict();
+  const auto* devtools_url = devtools_dict.FindString("url");
+  ASSERT_TRUE(devtools_url);
+  EXPECT_EQ(*devtools_url, url);
 
-  base::Value* devtools_title = devtools_list[0].FindPath("title");
-  ASSERT_TRUE(devtools_title->is_string());
-  EXPECT_EQ(devtools_title->GetString(), "title 1");
+  const auto* devtools_title = devtools_dict.FindString("title");
+  ASSERT_TRUE(devtools_title);
+  EXPECT_EQ(*devtools_title, "title 1");
 
   // Unbind the context and wait for the listener to no longer have any active
   // DevTools port.
@@ -149,13 +150,14 @@
   base::Value::List devtools_list1 = GetDevToolsListFromPort(port1);
   EXPECT_EQ(devtools_list1.size(), 1u);
 
-  base::Value* devtools_url1 = devtools_list1[0].FindPath("url");
-  ASSERT_TRUE(devtools_url1->is_string());
-  EXPECT_EQ(devtools_url1->GetString(), url1);
+  const auto& devtools_dict1 = devtools_list1[0].GetDict();
+  const auto* devtools_url1 = devtools_dict1.FindString("url");
+  ASSERT_TRUE(devtools_url1);
+  EXPECT_EQ(*devtools_url1, url1);
 
-  base::Value* devtools_title1 = devtools_list1[0].FindPath("title");
-  ASSERT_TRUE(devtools_title1->is_string());
-  EXPECT_EQ(devtools_title1->GetString(), "title 1");
+  const auto* devtools_title1 = devtools_dict1.FindString("title");
+  ASSERT_TRUE(devtools_title1);
+  EXPECT_EQ(*devtools_title1, "title 1");
 
   // Connect a second Debug interface.
   fuchsia::web::DebugSyncPtr debug2;
@@ -184,13 +186,14 @@
   base::Value::List devtools_list2 = GetDevToolsListFromPort(port2);
   EXPECT_EQ(devtools_list2.size(), 1u);
 
-  base::Value* devtools_url2 = devtools_list2[0].FindPath("url");
-  ASSERT_TRUE(devtools_url2->is_string());
-  EXPECT_EQ(devtools_url2->GetString(), url2);
+  const auto& devtools_dict2 = devtools_list2[0].GetDict();
+  const auto* devtools_url2 = devtools_dict2.FindString("url");
+  ASSERT_TRUE(devtools_url2);
+  EXPECT_EQ(*devtools_url2, url2);
 
-  base::Value* devtools_title2 = devtools_list2[0].FindPath("title");
-  ASSERT_TRUE(devtools_title2->is_string());
-  EXPECT_EQ(devtools_title2->GetString(), "title 2");
+  const auto* devtools_title2 = devtools_dict2.FindString("title");
+  ASSERT_TRUE(devtools_title2);
+  EXPECT_EQ(*devtools_title2, "title 2");
 
   // Unbind the first Context, each listener should still have one open port.
   frame_data1.context.Unbind();
@@ -228,13 +231,14 @@
       GetDevToolsListFromPort(remote_debugging_port);
   EXPECT_EQ(devtools_list.size(), 1u);
 
-  base::Value* devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url);
+  const auto& devtools_dict = devtools_list[0].GetDict();
+  const auto* devtools_url = devtools_dict.FindString("url");
+  ASSERT_TRUE(devtools_url);
+  EXPECT_EQ(*devtools_url, url);
 
-  base::Value* devtools_title = devtools_list[0].FindPath("title");
-  ASSERT_TRUE(devtools_title->is_string());
-  EXPECT_EQ(devtools_title->GetString(), "title 1");
+  const auto* devtools_title = devtools_dict.FindString("title");
+  ASSERT_TRUE(devtools_title);
+  EXPECT_EQ(*devtools_title, "title 1");
 
   // Unbind the context and wait for the listener to no longer have any active
   // DevTools port.
diff --git a/fuchsia_web/webengine/web_engine_integration_test.cc b/fuchsia_web/webengine/web_engine_integration_test.cc
index 27e6201..1cc5da4 100644
--- a/fuchsia_web/webengine/web_engine_integration_test.cc
+++ b/fuchsia_web/webengine/web_engine_integration_test.cc
@@ -278,9 +278,11 @@
       GetDevToolsListFromPort(remote_debugging_port);
   EXPECT_EQ(devtools_list.size(), 1u);
 
-  base::Value* devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url);
+  {
+    const auto* devtools_url = devtools_list[0].GetDict().FindString("url");
+    ASSERT_TRUE(devtools_url);
+    EXPECT_EQ(*devtools_url, url);
+  }
 
   // Create a second frame, without remote debugging enabled. The remote
   // debugging service should still report a single Frame is present.
@@ -292,9 +294,11 @@
   devtools_list = GetDevToolsListFromPort(remote_debugging_port);
   EXPECT_EQ(devtools_list.size(), 1u);
 
-  devtools_url = devtools_list[0].FindPath("url");
-  ASSERT_TRUE(devtools_url->is_string());
-  EXPECT_EQ(devtools_url->GetString(), url);
+  {
+    const auto* devtools_url = devtools_list[0].GetDict().FindString("url");
+    ASSERT_TRUE(devtools_url);
+    EXPECT_EQ(*devtools_url, url);
+  }
 
   // Tear down the debuggable Frame. The remote debugging service should have
   // shut down.
diff --git a/gpu/command_buffer/service/service_utils.cc b/gpu/command_buffer/service/service_utils.cc
index 886d5734..ad01b39 100644
--- a/gpu/command_buffer/service/service_utils.cc
+++ b/gpu/command_buffer/service/service_utils.cc
@@ -267,18 +267,26 @@
     const base::CommandLine* command_line) {
   if (command_line->HasSwitch(switches::kUseWebGPUAdapter)) {
     auto value = command_line->GetSwitchValueASCII(switches::kUseWebGPUAdapter);
-    if (value.empty()) {
-      return WebGPUAdapterName::kDefault;
-    } else if (value == "opengles") {
-      return WebGPUAdapterName::kOpenGLES;
-    } else if (value == "swiftshader") {
-      return WebGPUAdapterName::kSwiftShader;
-    } else if (value == "default") {
-      return WebGPUAdapterName::kDefault;
-    } else {
-      DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUAdapter << "="
-                  << value << ".";
+
+    static const struct {
+      const char* name;
+      WebGPUAdapterName value;
+    } kAdapterNames[] = {
+        {"", WebGPUAdapterName::kDefault},
+        {"default", WebGPUAdapterName::kDefault},
+        {"d3d11", WebGPUAdapterName::kD3D11},
+        {"opengles", WebGPUAdapterName::kOpenGLES},
+        {"swiftshader", WebGPUAdapterName::kSwiftShader},
+    };
+
+    for (const auto& adapter_name : kAdapterNames) {
+      if (value == adapter_name.name) {
+        return adapter_name.value;
+      }
     }
+
+    DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUAdapter << "="
+                << value << ".";
   }
   return WebGPUAdapterName::kDefault;
 }
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
index 1134585d..001c50e9 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -7,6 +7,7 @@
 #include <inttypes.h>
 #include <memory>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "build/build_config.h"
 #include "build/chromecast_buildflags.h"
@@ -103,6 +104,31 @@
   NOTREACHED();
 }
 
+#if defined(USE_OZONE)
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum FormatPixmapSupport { kNone = 0, kNV12 = 1, kYV12 = 2, kMaxValue = kYV12 };
+
+// Return the supported format in order of fallback support.
+FormatPixmapSupport GetFormatPixmapSupport(
+    std::vector<gfx::BufferFormat> supported_formats) {
+  FormatPixmapSupport val = FormatPixmapSupport::kNone;
+  for (auto format : supported_formats) {
+    if (format == gfx::BufferFormat::YUV_420_BIPLANAR) {
+      val = FormatPixmapSupport::kNV12;
+      break;
+    } else if (format == gfx::BufferFormat::YVU_420) {
+      val = FormatPixmapSupport::kYV12;
+    }
+  }
+  return val;
+}
+
+// Set bool only once as formats supported on platform don't change on factory
+// creation.
+bool set_format_supported_metric = false;
+#endif
+
 }  // namespace
 
 // Overrides for flat_set lookups:
@@ -137,6 +163,28 @@
       is_for_display_compositor_(is_for_display_compositor),
       gr_context_type_(context_state ? context_state->gr_context_type()
                                      : GrContextType::kGL) {
+#if defined(USE_OZONE)
+  if (!set_format_supported_metric) {
+    bool is_pixmap_supported = ui::OzonePlatform::GetInstance()
+                                   ->GetPlatformRuntimeProperties()
+                                   .supports_native_pixmaps;
+    // Only log histogram for formats that are used with real GMBs containing
+    // native pixmap.
+    if (is_pixmap_supported) {
+      auto* factory =
+          ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone();
+      if (factory) {
+        // Get all formats that are supported by platform.
+        auto supported_formats = factory->GetSupportedFormatsForTexturing();
+        auto format = GetFormatPixmapSupport(supported_formats);
+        base::UmaHistogramEnumeration("GPU.SharedImage.FormatPixmapSupport",
+                                      format);
+      }
+    }
+    set_format_supported_metric = true;
+  }
+#endif
+
   auto shared_memory_backing_factory =
       std::make_unique<SharedMemoryImageBackingFactory>();
   factories_.push_back(std::move(shared_memory_backing_factory));
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 73d0cae..05a03c51 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -53,6 +53,7 @@
 #include "third_party/skia/include/gpu/GrBackendSemaphore.h"
 
 #if BUILDFLAG(IS_WIN)
+#include <dawn/native/D3D11Backend.h>
 #include <dawn/native/D3D12Backend.h>
 #include "ui/gl/gl_angle_util_win.h"
 #endif
@@ -1524,8 +1525,15 @@
   d3d11_device.As(&dxgi_device);
   Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
   dxgi_device->GetAdapter(&dxgi_adapter);
-  dawn::native::d3d12::AdapterDiscoveryOptions options(std::move(dxgi_adapter));
-  dawn_instance_->DiscoverAdapters(&options);
+
+  dawn::native::d3d12::AdapterDiscoveryOptions d3d12Options(dxgi_adapter);
+  dawn_instance_->DiscoverAdapters(&d3d12Options);
+
+  if (use_webgpu_adapter_ == WebGPUAdapterName::kD3D11) {
+    dawn::native::d3d11::AdapterDiscoveryOptions d3d11Options(
+        std::move(dxgi_adapter));
+    dawn_instance_->DiscoverAdapters(&d3d11Options);
+  }
 
 #if BUILDFLAG(ENABLE_VULKAN)
   // Also discover the SwiftShader adapter. It will be discovered by default
@@ -1574,7 +1582,11 @@
       continue;
     }
 
-    if (use_webgpu_adapter_ == WebGPUAdapterName::kOpenGLES) {
+    if (use_webgpu_adapter_ == WebGPUAdapterName::kD3D11) {
+      if (adapterProperties.backendType == WGPUBackendType_D3D11) {
+        adapters.push_back(adapter);
+      }
+    } else if (use_webgpu_adapter_ == WebGPUAdapterName::kOpenGLES) {
       if (adapterProperties.backendType == WGPUBackendType_OpenGLES) {
         adapters.push_back(adapter);
       }
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index dfbf1e9db..b45dad35 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -41,8 +41,9 @@
 
 enum class WebGPUAdapterName : uint32_t {
   kDefault = 0,
-  kOpenGLES = 1,
-  kSwiftShader = 2,
+  kD3D11 = 1,
+  kOpenGLES = 2,
+  kSwiftShader = 3,
 };
 
 // Affecting how chromium handles GPUPowerPreference in
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index 97f485e..8de668e0 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -22,8 +22,9 @@
 // Corresponds to gpu::WebGPUAdapterName.
 enum WebGPUAdapterName {
   kDefault = 0,
-  kOpenGLES = 1,
-  kSwiftShader = 2,
+  kD3D11 = 1,
+  kOpenGLES = 2,
+  kSwiftShader = 3,
 };
 
 // Corresponds to gpu::WebGPUPowerPreference.
diff --git a/gpu/ipc/common/gpu_preferences_mojom_traits.h b/gpu/ipc/common/gpu_preferences_mojom_traits.h
index 1fca060..673b6fc 100644
--- a/gpu/ipc/common/gpu_preferences_mojom_traits.h
+++ b/gpu/ipc/common/gpu_preferences_mojom_traits.h
@@ -103,6 +103,8 @@
     switch (input) {
       case gpu::WebGPUAdapterName::kDefault:
         return gpu::mojom::WebGPUAdapterName::kDefault;
+      case gpu::WebGPUAdapterName::kD3D11:
+        return gpu::mojom::WebGPUAdapterName::kD3D11;
       case gpu::WebGPUAdapterName::kOpenGLES:
         return gpu::mojom::WebGPUAdapterName::kOpenGLES;
       case gpu::WebGPUAdapterName::kSwiftShader:
@@ -117,6 +119,9 @@
       case gpu::mojom::WebGPUAdapterName::kDefault:
         *out = gpu::WebGPUAdapterName::kDefault;
         return true;
+      case gpu::mojom::WebGPUAdapterName::kD3D11:
+        *out = gpu::WebGPUAdapterName::kD3D11;
+        return true;
       case gpu::mojom::WebGPUAdapterName::kOpenGLES:
         *out = gpu::WebGPUAdapterName::kOpenGLES;
         return true;
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index e04da0960..8f42dad9 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -389,8 +389,12 @@
 source_set("app_internal") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "application_storage_metrics.h",
+    "application_storage_metrics.mm",
     "chrome_overlay_window.h",
     "chrome_overlay_window.mm",
+    "features.h",
+    "features.mm",
     "main_application_delegate.h",
     "main_application_delegate.mm",
     "main_application_delegate_testing.h",
@@ -443,6 +447,7 @@
     "//components/keyed_service/core",
     "//components/keyed_service/ios",
     "//components/metrics",
+    "//components/optimization_guide/core:features",
     "//components/password_manager/core/common",
     "//components/prefs",
     "//components/prefs/ios",
diff --git a/ios/chrome/app/DEPS b/ios/chrome/app/DEPS
index e864524d..c445306e 100644
--- a/ios/chrome/app/DEPS
+++ b/ios/chrome/app/DEPS
@@ -16,6 +16,7 @@
   "+components/history/core/browser",
   "+components/keyed_service/core",
   "+components/metrics",
+  "+components/optimization_guide/core",
   "+components/password_manager/core/browser",
   "+components/password_manager/core/common",
   "+components/payments/core",
diff --git a/ios/chrome/app/app_metrics_app_state_agent_unittest.mm b/ios/chrome/app/app_metrics_app_state_agent_unittest.mm
index 5f900ba..9c89fd1 100644
--- a/ios/chrome/app/app_metrics_app_state_agent_unittest.mm
+++ b/ios/chrome/app/app_metrics_app_state_agent_unittest.mm
@@ -93,9 +93,7 @@
         base::BindRepeating(&FakeProfileSessionDurationsService::Create));
     browser_state_ = test_cbs_builder.Build();
 
-    app_state_ = [[FakeAppState alloc] initWithBrowserLauncher:nil
-                                            startupInformation:nil
-                                           applicationDelegate:nil];
+    app_state_ = [[FakeAppState alloc] initWithStartupInformation:nil];
   }
 
   void SetUp() override {
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index b922d0b2..d6d291d7 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -178,7 +178,6 @@
   public_deps = [ ":app_state_header" ]
   sources = [
     "app_state.mm",
-    "browser_launcher.h",
     "metrics_mediator.h",
     "metrics_mediator.mm",
     "url_opener.h",
diff --git a/ios/chrome/app/application_delegate/app_state.h b/ios/chrome/app/application_delegate/app_state.h
index eb89412..4ed6fb9 100644
--- a/ios/chrome/app/application_delegate/app_state.h
+++ b/ios/chrome/app/application_delegate/app_state.h
@@ -13,13 +13,11 @@
 #import "ios/chrome/browser/ui/scoped_ui_blocker/ui_blocker_manager.h"
 
 @class AppState;
-@protocol BrowserLauncher;
 class ChromeBrowserState;
 @class CommandDispatcher;
 @protocol ConnectionInformation;
 typedef NS_ENUM(NSUInteger, DefaultPromoType);
 @class SceneState;
-@class MainApplicationDelegate;
 @class MemoryWarningHelper;
 @class MetricsMediator;
 @protocol StartupInformation;
@@ -50,11 +48,8 @@
 
 - (instancetype)init NS_UNAVAILABLE;
 
-- (instancetype)
-initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
-     startupInformation:(id<StartupInformation>)startupInformation
-    applicationDelegate:(MainApplicationDelegate*)applicationDelegate
-    NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithStartupInformation:
+    (id<StartupInformation>)startupInformation NS_DESIGNATED_INITIALIZER;
 
 // Dispatcher for app-level commands for multiwindow use cases.
 // Most features should use the browser-level dispatcher instead.
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index 1f90f13..612d272 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -21,13 +21,11 @@
 #import "components/metrics/metrics_service.h"
 #import "components/previous_session_info/previous_session_info.h"
 #import "ios/chrome/app/application_delegate/app_state+private.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/memory_warning_helper.h"
 #import "ios/chrome/app/application_delegate/metrics_mediator.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
 #import "ios/chrome/app/application_delegate/user_activity_handler.h"
 #import "ios/chrome/app/deferred_initialization_runner.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/browsing_data/sessions_storage_util.h"
 #import "ios/chrome/browser/crash_report/crash_helper.h"
 #import "ios/chrome/browser/crash_report/crash_keys_helper.h"
@@ -128,12 +126,6 @@
 @end
 
 @implementation AppState {
-  // Browser launcher to launch browser in different states.
-  __weak id<BrowserLauncher> _browserLauncher;
-
-  // UIApplicationDelegate for the application.
-  __weak MainApplicationDelegate* _mainApplicationDelegate;
-
   // Whether the application is currently in the background.
   // This is a workaround for rdar://22392526 where
   // -applicationDidEnterBackground: can be called twice.
@@ -146,18 +138,14 @@
 
 @synthesize userInteracted = _userInteracted;
 
-- (instancetype)
-initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
-     startupInformation:(id<StartupInformation>)startupInformation
-    applicationDelegate:(MainApplicationDelegate*)applicationDelegate {
+- (instancetype)initWithStartupInformation:
+    (id<StartupInformation>)startupInformation {
   self = [super init];
   if (self) {
     _observers = [AppStateObserverList
         observersWithProtocol:@protocol(AppStateObserver)];
     _agents = [[NSMutableArray alloc] init];
     _startupInformation = startupInformation;
-    _browserLauncher = browserLauncher;
-    _mainApplicationDelegate = applicationDelegate;
     _appCommandDispatcher = [[CommandDispatcher alloc] init];
 
     // Subscribe to scene connection notifications.
@@ -255,14 +243,11 @@
 
   [self.startupInformation expireFirstUserActionRecorder];
 
-  // Do not save cookies if it is already in progress.
-  id<BrowserProvider> currentBrowserProvider =
-      _browserLauncher.browserProviderInterface.currentBrowserProvider;
-  if (currentBrowserProvider.browser && !_savingCookies) {
+  if (self.mainBrowserState && !_savingCookies) {
     // Save cookies to disk. The empty critical closure guarantees that the task
     // will be run before backgrounding.
     scoped_refptr<net::URLRequestContextGetter> getter =
-        currentBrowserProvider.browser->GetBrowserState()->GetRequestContext();
+        self.mainBrowserState->GetRequestContext();
     _savingCookies = YES;
     __weak AppState* weakSelf = self;
 
diff --git a/ios/chrome/app/application_delegate/app_state_unittest.mm b/ios/chrome/app/application_delegate/app_state_unittest.mm
index a18eadd..e4eb664 100644
--- a/ios/chrome/app/application_delegate/app_state_unittest.mm
+++ b/ios/chrome/app/application_delegate/app_state_unittest.mm
@@ -14,7 +14,6 @@
 #import "ios/chrome/app/app_startup_parameters.h"
 #import "ios/chrome/app/application_delegate/app_state+private.h"
 #import "ios/chrome/app/application_delegate/app_state_observer.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/fake_startup_information.h"
 #import "ios/chrome/app/application_delegate/memory_warning_helper.h"
 #import "ios/chrome/app/application_delegate/metrics_mediator.h"
@@ -22,7 +21,6 @@
 #import "ios/chrome/app/application_delegate/startup_information.h"
 #import "ios/chrome/app/application_delegate/user_activity_handler.h"
 #import "ios/chrome/app/enterprise_app_agent.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/app/safe_mode_app_state_agent+private.h"
 #import "ios/chrome/app/safe_mode_app_state_agent.h"
 #import "ios/chrome/browser/crash_report/crash_helper.h"
@@ -62,17 +60,12 @@
 @interface TestAppState : AppState
 
 - (instancetype)
-    initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
-         startupInformation:(id<StartupInformation>)startupInformation
-        applicationDelegate:(MainApplicationDelegate*)applicationDelegate
-            connectedScenes:(NSArray<SceneState*>*)connectedScenes
+    initWithStartupInformation:(id<StartupInformation>)startupInformation
+               connectedScenes:(NSArray<SceneState*>*)connectedScenes
     NS_DESIGNATED_INITIALIZER;
 
-- (instancetype)
-    initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
-         startupInformation:(id<StartupInformation>)startupInformation
-        applicationDelegate:(MainApplicationDelegate*)applicationDelegate
-    NS_UNAVAILABLE;
+- (instancetype)initWithStartupInformation:
+    (id<StartupInformation>)startupInformation NS_UNAVAILABLE;
 
 @end
 
@@ -81,13 +74,9 @@
 }
 
 - (instancetype)
-    initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
-         startupInformation:(id<StartupInformation>)startupInformation
-        applicationDelegate:(MainApplicationDelegate*)applicationDelegate
-            connectedScenes:(NSArray<SceneState*>*)connectedScenes {
-  if ((self = [super initWithBrowserLauncher:browserLauncher
-                          startupInformation:startupInformation
-                         applicationDelegate:applicationDelegate])) {
+    initWithStartupInformation:(id<StartupInformation>)startupInformation
+               connectedScenes:(NSArray<SceneState*>*)connectedScenes {
+  if ((self = [super initWithStartupInformation:startupInformation])) {
     _connectedScenes = connectedScenes ? [connectedScenes copy] : @[];
   }
   return self;
@@ -201,19 +190,15 @@
  protected:
   AppStateTest() {
     // Init mocks.
-    browser_launcher_mock_ =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
     startup_information_mock_ =
         [OCMockObject mockForProtocol:@protocol(StartupInformation)];
     connection_information_mock_ =
         [OCMockObject mockForProtocol:@protocol(ConnectionInformation)];
-    main_application_delegate_ =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
     window_ = [OCMockObject mockForClass:[UIWindow class]];
     app_state_observer_mock_ =
         [OCMockObject mockForProtocol:@protocol(AppStateObserver)];
 
-    interface_provider_ = [[StubBrowserProviderInterface alloc] init];
+    provider_interface_ = [[StubBrowserProviderInterface alloc] init];
 
     app_state_observer_to_mock_main_controller_ =
         [AppStateObserverToMockMainController alloc];
@@ -230,7 +215,7 @@
     BlockCleanupTest::TearDown();
   }
 
-  void swizzleConnectedScenes(NSArray<SceneState*>* connectedScenes) {
+  void SwizzleConnectedScenes(NSArray<SceneState*>* connectedScenes) {
     connected_scenes_swizzle_block_ = ^NSArray<SceneState*>*(id self) {
       return connectedScenes;
     };
@@ -239,7 +224,7 @@
                                 connected_scenes_swizzle_block_));
   }
 
-  void swizzleSafeModeShouldStart(BOOL shouldStart) {
+  void SwizzleSafeModeShouldStart(BOOL shouldStart) {
     safe_mode_swizzle_block_ = ^BOOL(id self) {
       return shouldStart;
     };
@@ -248,7 +233,7 @@
         safe_mode_swizzle_block_));
   }
 
-  void swizzleHandleStartupParameters(
+  void SwizzleHandleStartupParameters(
       id<TabOpening> expectedTabOpener,
       ChromeBrowserState* expectedBrowserState) {
     handle_startup_swizzle_block_ =
@@ -270,14 +255,14 @@
         handle_startup_swizzle_block_));
   }
 
-  AppState* getAppStateWithOpenNTP(BOOL shouldOpenNTP, UIWindow* window) {
-    AppState* appState = getAppStateWithRealWindow(window);
+  AppState* GetAppStateWithOpenNTP(BOOL shouldOpenNTP, UIWindow* window) {
+    AppState* appState = GetAppStateWithRealWindow(window);
 
     id application = [OCMockObject mockForClass:[UIApplication class]];
     id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
     id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
     id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
-    Browser* browser = interface_provider_.currentBrowserProvider.browser;
+    Browser* browser = provider_interface_.currentBrowserProvider.browser;
 
     [[metricsMediator stub] updateMetricsStateBasedOnPrefsUserTriggered:NO];
     [[memoryHelper stub] resetForegroundMemoryWarningCount];
@@ -300,75 +285,71 @@
     return appState;
   }
 
-  SafeModeAppAgent* getSafeModeAppAgent() {
+  SafeModeAppAgent* GetSafeModeAppAgent() {
     if (!safe_mode_app_agent_) {
       safe_mode_app_agent_ = [[SafeModeAppAgent alloc] init];
     }
     return safe_mode_app_agent_;
   }
 
-  EnterpriseAppAgent* getEnterpriseAppAgent() {
+  EnterpriseAppAgent* GetEnterpriseAppAgent() {
     if (!enterprise_app_agent_) {
       enterprise_app_agent_ = [[EnterpriseAppAgent alloc] init];
     }
     return enterprise_app_agent_;
   }
 
-  AppState* getAppStateWithMock(bool with_safe_mode_agent) {
+  AppState* GetAppStateWithMock(bool with_safe_mode_agent) {
     if (!app_state_) {
       // The swizzle block needs the scene state before app_state is create, but
       // the scene state needs the app state. So this alloc before swizzling
       // and initiate after app state is created.
       main_scene_state_ = [FakeSceneState alloc];
-      swizzleConnectedScenes(@[ main_scene_state_ ]);
+      SwizzleConnectedScenes(@[ main_scene_state_ ]);
 
       app_state_ = [[TestAppState alloc]
-          initWithBrowserLauncher:browser_launcher_mock_
-               startupInformation:startup_information_mock_
-              applicationDelegate:main_application_delegate_
-                  connectedScenes:@[ main_scene_state_ ]];
+          initWithStartupInformation:startup_information_mock_
+                     connectedScenes:@[ main_scene_state_ ]];
 
       main_scene_state_ =
           [main_scene_state_ initWithAppState:app_state_
                                  browserState:browser_state_.get()];
-      main_scene_state_.window = getWindowMock();
+      main_scene_state_.window = GetWindowMock();
 
       if (with_safe_mode_agent) {
-        [app_state_ addAgent:getSafeModeAppAgent()];
+        [app_state_ addAgent:GetSafeModeAppAgent()];
         // Retrigger a sceneConnected event for the safe mode agent. This is
         // needed because the sceneConnected event triggered by the app state is
         // done before resetting the scene state with initWithAppState which
         // clears the observers and agents.
-        [getSafeModeAppAgent() appState:app_state_
+        [GetSafeModeAppAgent() appState:app_state_
                          sceneConnected:main_scene_state_];
       }
 
       // Add the enterprise agent for the app to boot past the enterprise init
       // stage.
-      [app_state_ addAgent:getEnterpriseAppAgent()];
+      [app_state_ addAgent:GetEnterpriseAppAgent()];
 
       [app_state_ addObserver:app_state_observer_to_mock_main_controller_];
     }
     return app_state_;
   }
 
-  AppState* getAppStateWithMock() {
-    return getAppStateWithMock(/*with_safe_mode_agent=*/true);
+  AppState* GetAppStateWithMock() {
+    return GetAppStateWithMock(/*with_safe_mode_agent=*/true);
   }
 
-  AppState* getAppStateWithRealWindow(UIWindow* window) {
+  AppState* GetAppStateWithRealWindow(UIWindow* window) {
     if (!app_state_) {
       // The swizzle block needs the scene state before app_state is create, but
       // the scene state needs the app state. So this alloc before swizzling
       // and initiate after app state is created.
       main_scene_state_ = [FakeSceneState alloc];
-      swizzleConnectedScenes(@[ main_scene_state_ ]);
+      SwizzleConnectedScenes(@[ main_scene_state_ ]);
 
       app_state_ = [[TestAppState alloc]
-          initWithBrowserLauncher:browser_launcher_mock_
-               startupInformation:startup_information_mock_
-              applicationDelegate:main_application_delegate_
-                  connectedScenes:@[ main_scene_state_ ]];
+          initWithStartupInformation:startup_information_mock_
+                     connectedScenes:@[ main_scene_state_ ]];
 
       main_scene_state_ =
           [main_scene_state_ initWithAppState:app_state_
@@ -376,11 +357,11 @@
       main_scene_state_.window = window;
       [window makeKeyAndVisible];
 
-      [app_state_ addAgent:getSafeModeAppAgent()];
+      [app_state_ addAgent:GetSafeModeAppAgent()];
 
       // Add the enterprise agent for the app to boot past the enterprise init
       // stage.
-      [app_state_ addAgent:getEnterpriseAppAgent()];
+      [app_state_ addAgent:GetEnterpriseAppAgent()];
 
       [app_state_ addObserver:app_state_observer_to_mock_main_controller_];
 
@@ -388,22 +369,17 @@
       // scene state. This is needed because the sceneConnected event triggered
       // by the app state is done before resetting the scene state with
       // initWithAppState which clears the observers and agents.
-      [getSafeModeAppAgent() appState:app_state_
+      [GetSafeModeAppAgent() appState:app_state_
                        sceneConnected:main_scene_state_];
     }
     return app_state_;
   }
 
-  id getBrowserLauncherMock() { return browser_launcher_mock_; }
-  id getStartupInformationMock() { return startup_information_mock_; }
-  id getConnectionInformationMock() { return connection_information_mock_; }
-  id getApplicationDelegateMock() { return main_application_delegate_; }
-  id getWindowMock() { return window_; }
-  id getAppStateObserverMock() { return app_state_observer_mock_; }
-  StubBrowserProviderInterface* getInterfaceProvider() {
-    return interface_provider_;
-  }
-  ChromeBrowserState* getBrowserState() { return browser_state_.get(); }
+  id GetStartupInformationMock() { return startup_information_mock_; }
+  id GetConnectionInformationMock() { return connection_information_mock_; }
+  id GetWindowMock() { return window_; }
+  id GetAppStateObserverMock() { return app_state_observer_mock_; }
+  ChromeBrowserState* GetBrowserState() { return browser_state_.get(); }
 
  private:
   web::WebTaskEnvironment task_environment_;
@@ -413,13 +389,11 @@
   EnterpriseAppAgent* enterprise_app_agent_;
   AppStateObserverToMockMainController*
       app_state_observer_to_mock_main_controller_;
-  id browser_launcher_mock_;
   id connection_information_mock_;
   id startup_information_mock_;
-  id main_application_delegate_;
   id window_;
   id app_state_observer_mock_;
-  StubBrowserProviderInterface* interface_provider_;
+  StubBrowserProviderInterface* provider_interface_;
   ScenesBlock connected_scenes_swizzle_block_;
   DecisionBlock safe_mode_swizzle_block_;
   HandleStartupParam handle_startup_swizzle_block_;
@@ -447,31 +421,12 @@
 TEST_F(AppStateNoFixtureTest, willResignActive) {
   // Setup.
   base::test::TaskEnvironment task_environment;
-  std::unique_ptr<TestChromeBrowserState> browser_state =
-      TestChromeBrowserState::Builder().Build();
-  std::unique_ptr<Browser> browser =
-      std::make_unique<TestBrowser>(browser_state.get());
-
-  StubBrowserProviderInterface* browserProviderInterface =
-      [[StubBrowserProviderInterface alloc] init];
-  browserProviderInterface.mainBrowserProvider.browser = browser.get();
-
-  id browserLauncher =
-      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
-  [[[browserLauncher stub] andReturn:browserProviderInterface]
-      browserProviderInterface];
-
-  id applicationDelegate =
-      [OCMockObject mockForClass:[MainApplicationDelegate class]];
-
   FakeStartupInformation* startupInformation =
       [[FakeStartupInformation alloc] init];
   [startupInformation setIsColdStart:YES];
 
   AppState* appState =
-      [[AppState alloc] initWithBrowserLauncher:browserLauncher
-                             startupInformation:startupInformation
-                            applicationDelegate:applicationDelegate];
+      [[AppState alloc] initWithStartupInformation:startupInformation];
 
   [appState addAgent:[[SafeModeAppAgent alloc] init]];
   AppStateObserverToMockMainController* observer =
@@ -498,19 +453,12 @@
   ios::provider::test::ResetAppDistributionNotificationsState();
   ASSERT_FALSE(ios::provider::test::AreAppDistributionNotificationsCanceled());
 
-  id browserLauncher =
-      [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
-  id applicationDelegate =
-      [OCMockObject mockForClass:[MainApplicationDelegate class]];
-
   id startupInformation =
       [OCMockObject mockForProtocol:@protocol(StartupInformation)];
   [[startupInformation expect] stopChromeMain];
 
   AppState* appState =
-      [[AppState alloc] initWithBrowserLauncher:browserLauncher
-                             startupInformation:startupInformation
-                            applicationDelegate:applicationDelegate];
+      [[AppState alloc] initWithStartupInformation:startupInformation];
 
   id appStateMock = OCMPartialMock(appState);
   [[appStateMock expect] completeUIInitialization];
@@ -541,23 +489,17 @@
 
 // Tests that -applicationWillEnterForeground resets components as needed.
 TEST_F(AppStateTest, applicationWillEnterForeground) {
-  swizzleSafeModeShouldStart(NO);
-  [[getStartupInformationMock() stub] setIsFirstRun:YES];
-  [[[getStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
+  SwizzleSafeModeShouldStart(NO);
+  [[GetStartupInformationMock() stub] setIsFirstRun:YES];
+  [[[GetStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
 
   // Setup.
   id application = [OCMockObject mockForClass:[UIApplication class]];
   id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
   id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
-  StubBrowserProviderInterface* browserProviderInterface =
-      getInterfaceProvider();
   id tabOpener = [OCMockObject mockForProtocol:@protocol(TabOpening)];
   std::unique_ptr<Browser> browser =
-      std::make_unique<TestBrowser>(getBrowserState());
-
-  [[[getBrowserLauncherMock() stub] andReturn:browserProviderInterface]
-      browserProviderInterface];
-  browserProviderInterface.mainBrowserProvider.browser = browser.get();
+      std::make_unique<TestBrowser>(GetBrowserState());
 
   [[metricsMediator expect] updateMetricsStateBasedOnPrefsUserTriggered:NO];
   [[memoryHelper expect] resetForegroundMemoryWarningCount];
@@ -565,16 +507,16 @@
   [[[tabOpener stub] andReturnValue:@YES]
       shouldOpenNTPTabOnActivationOfBrowser:browser.get()];
 
-  id appStateMock = OCMPartialMock(getAppStateWithMock());
+  id appStateMock = OCMPartialMock(GetAppStateWithMock());
   [[appStateMock expect] completeUIInitialization];
 
   // Simulate finishing the initialization before going to background.
-  [getAppStateWithMock() startInitialization];
-  [getAppStateWithMock() queueTransitionToNextInitStage];
+  [GetAppStateWithMock() startInitialization];
+  [GetAppStateWithMock() queueTransitionToNextInitStage];
 
   // Simulate background before going to foreground.
-  [[getStartupInformationMock() expect] expireFirstUserActionRecorder];
-  [getAppStateWithMock() applicationDidEnterBackground:application
+  [[GetStartupInformationMock() expect] expireFirstUserActionRecorder];
+  [GetAppStateWithMock() applicationDidEnterBackground:application
                                           memoryHelper:memoryHelper];
 
   void (^swizzleBlock)() = ^{
@@ -586,14 +528,14 @@
       swizzleBlock);
 
   // Actions.
-  [getAppStateWithMock() applicationWillEnterForeground:application
+  [GetAppStateWithMock() applicationWillEnterForeground:application
                                         metricsMediator:metricsMediator
                                            memoryHelper:memoryHelper];
 
   // Tests.
   EXPECT_OCMOCK_VERIFY(metricsMediator);
   EXPECT_OCMOCK_VERIFY(memoryHelper);
-  EXPECT_OCMOCK_VERIFY(getStartupInformationMock());
+  EXPECT_OCMOCK_VERIFY(GetStartupInformationMock());
 }
 
 // Tests that -applicationWillEnterForeground starts the browser if the
@@ -604,45 +546,39 @@
   id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
   id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
 
-  [[[getWindowMock() stub] andReturn:nil] rootViewController];
-  swizzleSafeModeShouldStart(NO);
+  [[[GetWindowMock() stub] andReturn:nil] rootViewController];
+  SwizzleSafeModeShouldStart(NO);
 
-  [[[getStartupInformationMock() stub] andReturnValue:@YES] isColdStart];
-  [[getStartupInformationMock() stub] setIsFirstRun:YES];
-  [[[getStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
+  [[[GetStartupInformationMock() stub] andReturnValue:@YES] isColdStart];
+  [[GetStartupInformationMock() stub] setIsFirstRun:YES];
+  [[[GetStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
 
   // Simulate finishing the initialization before going to background.
-  [getAppStateWithMock() startInitialization];
-  [getAppStateWithMock() queueTransitionToNextInitStage];
+  [GetAppStateWithMock() startInitialization];
+  [GetAppStateWithMock() queueTransitionToNextInitStage];
 
   // Actions.
-  [getAppStateWithMock() applicationWillEnterForeground:application
+  [GetAppStateWithMock() applicationWillEnterForeground:application
                                         metricsMediator:metricsMediator
                                            memoryHelper:memoryHelper];
-
-  // Tests.
-  EXPECT_OCMOCK_VERIFY(getBrowserLauncherMock());
 }
 
 // Tests that -applicationDidEnterBackground do nothing if the application has
 // never been in a Foreground stage.
 TEST_F(AppStateTest, applicationDidEnterBackgroundStageBackground) {
-  swizzleSafeModeShouldStart(NO);
-  [[getStartupInformationMock() stub] setIsFirstRun:YES];
-  [[[getStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
+  SwizzleSafeModeShouldStart(NO);
+  [[GetStartupInformationMock() stub] setIsFirstRun:YES];
+  [[[GetStartupInformationMock() stub] andReturnValue:@YES] isFirstRun];
 
   // Setup.
   ScopedKeyWindow scopedKeyWindow;
   id application = [OCMockObject mockForClass:[UIApplication class]];
   id memoryHelper = [OCMockObject mockForClass:[MemoryWarningHelper class]];
-  id browserLauncher = getBrowserLauncherMock();
-
-  [[[browserLauncher stub] andReturn:nil] browserProviderInterface];
 
   ASSERT_EQ(NSUInteger(0), [scopedKeyWindow.Get() subviews].count);
 
   // Action.
-  [getAppStateWithRealWindow(scopedKeyWindow.Get())
+  [GetAppStateWithRealWindow(scopedKeyWindow.Get())
       applicationDidEnterBackground:application
                        memoryHelper:memoryHelper];
 
@@ -652,7 +588,7 @@
 
 // Tests that -queueTransitionToNextInitStage transitions to the next stage.
 TEST_F(AppStateTest, queueTransitionToNextInitStage) {
-  AppState* appState = getAppStateWithMock();
+  AppState* appState = GetAppStateWithMock();
   ASSERT_EQ(appState.initStage, InitStageStart);
   [appState queueTransitionToNextInitStage];
   ASSERT_EQ(appState.initStage, static_cast<InitStage>(InitStageStart + 1));
@@ -661,7 +597,7 @@
 // Tests that -queueTransitionToNextInitStage notifies observers.
 TEST_F(AppStateTest, queueTransitionToNextInitStageNotifiesObservers) {
   // Setup.
-  AppState* appState = getAppStateWithMock();
+  AppState* appState = GetAppStateWithMock();
   id observer = [OCMockObject mockForProtocol:@protocol(AppStateObserver)];
   InitStage secondStage = static_cast<InitStage>(InitStageStart + 1);
   [appState addObserver:observer];
@@ -690,7 +626,7 @@
 TEST_F(AppStateTest,
        queueTransitionToNextInitStageReentrantFromWillTransitionToInitStage) {
   // Setup.
-  AppState* appState = getAppStateWithMock(/*with_safe_mode_agent=*/false);
+  AppState* appState = GetAppStateWithMock(/*with_safe_mode_agent=*/false);
   id observer1 = [OCMockObject mockForProtocol:@protocol(AppStateObserver)];
   AppStateTransitioningObserver* transitioningObserver =
       [[AppStateTransitioningObserver alloc] init];
@@ -731,7 +667,7 @@
 TEST_F(AppStateTest,
        queueTransitionToNextInitStageReentrantFromdidTransitionFromInitStage) {
   // Setup.
-  AppState* appState = getAppStateWithMock(/*with_safe_mode_agent=*/false);
+  AppState* appState = GetAppStateWithMock(/*with_safe_mode_agent=*/false);
   id observer1 = [OCMockObject mockForProtocol:@protocol(AppStateObserver)];
   AppStateTransitioningObserver* transitioningObserver =
       [[AppStateTransitioningObserver alloc] init];
diff --git a/ios/chrome/app/application_delegate/browser_launcher.h b/ios/chrome/app/application_delegate/browser_launcher.h
deleted file mode 100644
index 81eaf53..0000000
--- a/ios/chrome/app/application_delegate/browser_launcher.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
-#define IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
-
-#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
-
-// This protocol defines the startup method for the application.
-@protocol BrowserLauncher<NSObject>
-
-// Browser view information created during startup.
-@property(nonatomic, readonly) id<BrowserProviderInterface>
-    browserProviderInterface;
-
-@end
-
-#endif  // IOS_CHROME_APP_APPLICATION_DELEGATE_BROWSER_LAUNCHER_H_
diff --git a/ios/chrome/app/application_storage_metrics.h b/ios/chrome/app/application_storage_metrics.h
new file mode 100644
index 0000000..1266dba4
--- /dev/null
+++ b/ios/chrome/app/application_storage_metrics.h
@@ -0,0 +1,21 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_APP_APPLICATION_STORAGE_METRICS_H_
+#define IOS_CHROME_APP_APPLICATION_STORAGE_METRICS_H_
+
+#import <Foundation/Foundation.h>
+
+#import "base/files/file_path.h"
+
+// A key to NSUserDefaults storing the last time these metrics were logged.
+extern NSString* const kLastApplicationStorageMetricsLogTime;
+
+// Logs metrics about the storage used by the application and then updates the
+// `kLastApplicationStorageMetricsLogTime` user default value. `profile_path`
+// must point to the main user profile directory in order to log metrics about
+// Optimization Guide storage usage.
+void LogApplicationStorageMetrics(base::FilePath profile_path);
+
+#endif  // IOS_CHROME_APP_APPLICATION_STORAGE_METRICS_H_
diff --git a/ios/chrome/app/application_storage_metrics.mm b/ios/chrome/app/application_storage_metrics.mm
new file mode 100644
index 0000000..e0f9d77e
--- /dev/null
+++ b/ios/chrome/app/application_storage_metrics.mm
@@ -0,0 +1,120 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/app/application_storage_metrics.h"
+
+#import <Foundation/Foundation.h>
+
+#import "base/files/file_enumerator.h"
+#import "base/files/file_path.h"
+#import "base/files/file_util.h"
+#import "base/mac/foundation_util.h"
+#import "base/metrics/histogram_macros.h"
+#import "base/task/sequenced_task_runner.h"
+#import "base/task/thread_pool.h"
+#import "components/optimization_guide/core/optimization_guide_constants.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Key for last stored time that size metrics of the documents directory were
+// logged.
+NSString* const kLastApplicationStorageMetricsLogTime =
+    @"LastApplicationStorageMetricsLogTime";
+
+// Calculates and returns the total size used by `root`.
+int64_t CalculateTotalSize(base::FilePath root) {
+  base::File file(root, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  base::File::Info info;
+  if (!file.IsValid() || !file.GetInfo(&info)) {
+    return 0;
+  }
+
+  if (!info.is_directory) {
+    return info.size;
+  }
+
+  int64_t total_directory_size = 0;
+
+  base::FileEnumerator enumerator(
+      root, /*recursive=*/false,
+      base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
+  for (base::FilePath path = enumerator.Next(); !path.empty();
+       path = enumerator.Next()) {
+    int64_t dir_item_size = CalculateTotalSize(root.Append(path.BaseName()));
+    total_directory_size += dir_item_size;
+  }
+  return total_directory_size;
+}
+
+// Logs the "Documents" directory size. Accepts a task runner as a parameter in
+// order to keep it in scope throughout the execution.
+void LogDocumentsDirectorySize(scoped_refptr<base::SequencedTaskRunner>) {
+  base::FilePath documents_path = base::mac::GetUserDocumentPath();
+  int total_size_bytes = CalculateTotalSize(documents_path);
+  UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.DocumentsSize",
+                                 total_size_bytes / 1024 / 1024);
+}
+
+// Logs the "Library" directory size. Accepts a task runner as a parameter in
+// order to keep it in scope throughout the execution.
+void LogLibraryDirectorySize(scoped_refptr<base::SequencedTaskRunner>) {
+  base::FilePath library_path = base::mac::GetUserLibraryPath();
+  int total_size_bytes = CalculateTotalSize(library_path);
+  UMA_HISTOGRAM_MEMORY_MEDIUM_MB("IOS.SandboxMetrics.DocumentsSize",
+                                 total_size_bytes / 1024 / 1024);
+}
+
+// Logs the optimization guide model downloads directory size. Accepts a task
+// runner as a parameter in order to keep it in scope throughout the execution.
+void LogOptimizationGuideModelDownloadsMetrics(
+    base::FilePath profile_path,
+    scoped_refptr<base::SequencedTaskRunner>) {
+  int items = 0;
+
+  base::FilePath models_dir = profile_path.Append(
+      optimization_guide::kOptimizationGuidePredictionModelDownloads);
+  if (base::PathExists(models_dir)) {
+    int total_size_bytes = CalculateTotalSize(models_dir);
+    UMA_HISTOGRAM_MEMORY_MEDIUM_MB(
+        "IOS.SandboxMetrics.OptimizationGuideModelDownloadsSize",
+        total_size_bytes / 1024 / 1024);
+
+    base::FileEnumerator enumerator(models_dir, /*recursive=*/false,
+                                    base::FileEnumerator::DIRECTORIES);
+    for (base::FilePath path = enumerator.Next(); !path.empty();
+         path = enumerator.Next()) {
+      items++;
+    }
+  }
+
+  UMA_HISTOGRAM_COUNTS_1000(
+      "IOS.SandboxMetrics.OptimizationGuideModelDownloadedItems", items);
+}
+
+// Updates the last metric logged time. Accepts a task runner as a parameter in
+// order to keep it in scope throughout the execution.
+void UpdateLastLoggedTime(scoped_refptr<base::SequencedTaskRunner>) {
+  [[NSUserDefaults standardUserDefaults]
+      setObject:[NSDate date]
+         forKey:kLastApplicationStorageMetricsLogTime];
+}
+
+void LogApplicationStorageMetrics(base::FilePath profile_path) {
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+
+  task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&LogDocumentsDirectorySize, task_runner));
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(&LogLibraryDirectorySize, task_runner));
+  task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&LogOptimizationGuideModelDownloadsMetrics,
+                                profile_path, task_runner));
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(&UpdateLastLoggedTime, task_runner));
+}
diff --git a/ios/chrome/app/features.h b/ios/chrome/app/features.h
new file mode 100644
index 0000000..bd9323d2
--- /dev/null
+++ b/ios/chrome/app/features.h
@@ -0,0 +1,12 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_APP_FEATURES_H_
+#define IOS_CHROME_APP_FEATURES_H_
+
+#import "base/feature_list.h"
+
+BASE_DECLARE_FEATURE(kLogApplicationStorageSizeMetrics);
+
+#endif  // IOS_CHROME_APP_FEATURES_H_
diff --git a/ios/chrome/app/features.mm b/ios/chrome/app/features.mm
new file mode 100644
index 0000000..193bddc3
--- /dev/null
+++ b/ios/chrome/app/features.mm
@@ -0,0 +1,13 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/app/features.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+BASE_FEATURE(kLogApplicationStorageSizeMetrics,
+             "LogApplicationStorageSizeMetrics",
+             base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/app/main_application_delegate.mm b/ios/chrome/app/main_application_delegate.mm
index 817dd50..d46fc89 100644
--- a/ios/chrome/app/main_application_delegate.mm
+++ b/ios/chrome/app/main_application_delegate.mm
@@ -15,7 +15,6 @@
 #import "components/feature_engagement/public/event_constants.h"
 #import "components/feature_engagement/public/tracker.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/memory_warning_helper.h"
 #import "ios/chrome/app/application_delegate/metrics_mediator.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
@@ -36,6 +35,7 @@
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/ui/keyboard/menu_builder.h"
 #import "ios/web/common/uikit_ui_util.h"
@@ -57,8 +57,6 @@
   // Metrics mediator used to check and update the metrics accordingly to the
   // user preferences.
   MetricsMediator* _metricsMediator;
-  // Browser launcher to have a global launcher.
-  id<BrowserLauncher> _browserLauncher;
   // Container for startup information.
   id<StartupInformation> _startupInformation;
   // The set of "scene sessions" that needs to be discarded. See
@@ -86,11 +84,9 @@
     _mainController = [[MainController alloc] init];
     _metricsMediator = [[MetricsMediator alloc] init];
     [_mainController setMetricsMediator:_metricsMediator];
-    _browserLauncher = _mainController;
     _startupInformation = _mainController;
-    _appState = [[AppState alloc] initWithBrowserLauncher:_browserLauncher
-                                       startupInformation:_startupInformation
-                                      applicationDelegate:self];
+    _appState =
+        [[AppState alloc] initWithStartupInformation:_startupInformation];
     _pushNotificationDelegate = [[PushNotificationDelegate alloc] init];
     [_mainController setAppState:_appState];
   }
@@ -262,6 +258,8 @@
     completionHandler();
     return;
   }
+  // TODO(crbug.com/1442203) Remove this Browser dependency, ideally by
+  // refactoring into a dedicated agent.
   Browser* browser =
       _mainController.browserProviderInterface.mainBrowserProvider.browser;
   if (!browser) {
@@ -430,9 +428,11 @@
   }
 }
 
-// Notifies the FET that the app has launched from external intent (i.e. through
-// the share sheet), which is an eligibility criterion for the default browser
-// blue dot promo.
+// Notifies the Feature Engagement Tracker (FET) that the app has launched from
+// an external intent (i.e. through the share sheet), which is an eligibility
+// criterion for the default browser blue dot promo.
+// TODO(crbug.com/1442203) Remove this Browser dependency, ideally by
+// refactoring into a dedicated agent.
 - (void)notifyFETAppStartupFromExternalIntent {
   Browser* browser =
       _mainController.browserProviderInterface.mainBrowserProvider.browser;
diff --git a/ios/chrome/app/main_controller.h b/ios/chrome/app/main_controller.h
index b7c8ea67..4dc7018c 100644
--- a/ios/chrome/app/main_controller.h
+++ b/ios/chrome/app/main_controller.h
@@ -8,13 +8,13 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
 #import "ios/chrome/browser/shared/public/commands/browsing_data_commands.h"
 
 @class AppState;
-@class MetricsMediator;
 @protocol BrowsingDataCommands;
+@protocol BrowserProviderInterface;
+@class MetricsMediator;
 
 // The main controller of the application, owned by the MainWindow nib. Also
 // serves as the delegate for the app. Owns all the various top-level
@@ -22,10 +22,8 @@
 //
 // By design, it has no public API of its own. Anything interacting with
 // MainController should be doing so through a specific protocol.
-@interface MainController : NSObject <BrowserLauncher,
-                                      StartupInformation,
-                                      BrowsingDataCommands,
-                                      AppStateObserver>
+@interface MainController
+    : NSObject <StartupInformation, BrowsingDataCommands, AppStateObserver>
 
 // Contains information about the application state, for example whether the
 // safe mode is activated.
@@ -35,6 +33,13 @@
 // to the user preferences.
 @property(nonatomic, weak) MetricsMediator* metricsMediator;
 
+// The BrowserProviderInterface for the foreground scene, or for any background
+// connected scene if there's no foreground scene. If there are none of these,
+// this is `nil`.
+// TODO(crbug.com/1442203) Remove this property.
+@property(nonatomic, readonly) id<BrowserProviderInterface>
+    browserProviderInterface;
+
 @end
 
 #endif  // IOS_CHROME_APP_MAIN_CONTROLLER_H_
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index d553416..13771a5c 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -36,10 +36,12 @@
 #import "components/web_resource/web_resource_pref_names.h"
 #import "ios/chrome/app/app_metrics_app_state_agent.h"
 #import "ios/chrome/app/application_delegate/metrics_mediator.h"
+#import "ios/chrome/app/application_storage_metrics.h"
 #import "ios/chrome/app/blocking_scene_commands.h"
 #import "ios/chrome/app/deferred_initialization_runner.h"
 #import "ios/chrome/app/enterprise_app_agent.h"
 #import "ios/chrome/app/fast_app_terminate_buildflags.h"
+#import "ios/chrome/app/features.h"
 #import "ios/chrome/app/feed_app_agent.h"
 #import "ios/chrome/app/first_run_app_state_agent.h"
 #import "ios/chrome/app/memory_monitor.h"
@@ -210,6 +212,11 @@
 // Constants for deferred favicons clean up.
 NSString* const kFaviconsCleanup = @"FaviconsCleanup";
 
+// The minimum amount of time (2 weeks in seconds) between calculating and
+// logging metrics about the amount of device storage space used by Chrome.
+const NSTimeInterval kMinimumTimeBetweenDocumentsSizeLogging =
+    60.0 * 60.0 * 24.0 * 14.0;
+
 // Adapted from chrome/browser/ui/browser_init.cc.
 void RegisterComponentsForUpdate() {
   component_updater::ComponentUpdateService* cus =
@@ -1143,6 +1150,7 @@
   [self scheduleSaveFieldTrialValuesForExternals];
   [self scheduleEnterpriseManagedDeviceCheck];
   [self scheduleFaviconsCleanup];
+  [self scheduleLogDocumentsSize];
 #if BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
   [self scheduleDumpDocumentsStatistics];
 #endif  // BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
@@ -1218,6 +1226,23 @@
 }
 #endif  // BUILDFLAG(IOS_ENABLE_SANDBOX_DUMP)
 
+- (void)scheduleLogDocumentsSize {
+  if (!base::FeatureList::IsEnabled(kLogApplicationStorageSizeMetrics)) {
+    return;
+  }
+
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  NSDate* lastLogged = base::mac::ObjCCast<NSDate>(
+      [defaults objectForKey:kLastApplicationStorageMetricsLogTime]);
+  if (lastLogged && [[NSDate date] timeIntervalSinceDate:lastLogged] <
+                        kMinimumTimeBetweenDocumentsSizeLogging) {
+    return;
+  }
+
+  base::FilePath profilePath = self.appState.mainBrowserState->GetStatePath();
+  LogApplicationStorageMetrics(profilePath);
+}
+
 - (void)expireFirstUserActionRecorder {
   // Clear out any scheduled calls to this method. For example, the app may have
   // been backgrounded before the `kFirstUserActionTimeout` expired.
diff --git a/ios/chrome/app/safe_mode_app_state_agent_unittest.mm b/ios/chrome/app/safe_mode_app_state_agent_unittest.mm
index 5d127140..1818896d 100644
--- a/ios/chrome/app/safe_mode_app_state_agent_unittest.mm
+++ b/ios/chrome/app/safe_mode_app_state_agent_unittest.mm
@@ -7,9 +7,7 @@
 #import "base/ios/block_types.h"
 #import "base/test/ios/wait_util.h"
 #import "ios/chrome/app/application_delegate/app_state+private.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/app/safe_mode_app_state_agent+private.h"
 #import "ios/chrome/browser/shared/coordinator/scene/connection_information.h"
 #import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
@@ -58,14 +56,10 @@
   SafeModeAppStateAgentTest() {
     browser_state_ = TestChromeBrowserState::Builder().Build();
     window_ = [OCMockObject mockForClass:[UIWindow class]];
-    browser_launcher_mock_ =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
     startup_information_mock_ =
         [OCMockObject mockForProtocol:@protocol(StartupInformation)];
     connection_information_mock_ =
         [OCMockObject mockForProtocol:@protocol(ConnectionInformation)];
-    main_application_delegate_ =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
   }
 
   void swizzleSafeModeShouldStart(BOOL shouldStart) {
@@ -84,22 +78,18 @@
       // and initiate after app state is created.
       main_scene_state_ = [FakeSceneState alloc];
 
-      app_state_ =
-          [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
-                                 startupInformation:startup_information_mock_
-                                applicationDelegate:main_application_delegate_];
+      app_state_ = [[AppState alloc]
+          initWithStartupInformation:startup_information_mock_];
 
       main_scene_state_ =
           [main_scene_state_ initWithAppState:app_state_
                                  browserState:browser_state_.get()];
-      main_scene_state_.window = getWindowMock();
+      main_scene_state_.window = GetWindowMock();
     }
     return app_state_;
   }
 
-  id getWindowMock() { return window_; }
-
-  id getBrowserLauncherMock() { return browser_launcher_mock_; }
+  id GetWindowMock() { return window_; }
 
   FakeSceneState* GetSceneState() { return main_scene_state_; }
 
@@ -112,15 +102,13 @@
   std::unique_ptr<ScopedBlockSwizzler> safe_mode_swizzler_;
   DecisionBlock safe_mode_swizzle_block_;
 
-  id browser_launcher_mock_;
   id startup_information_mock_;
   id connection_information_mock_;
-  id main_application_delegate_;
   id window_;
 };
 
 TEST_F(SafeModeAppStateAgentTest, startSafeMode) {
-  id windowMock = getWindowMock();
+  id windowMock = GetWindowMock();
   [[windowMock expect] makeKeyAndVisible];
   [[[windowMock stub] andReturn:nil] rootViewController];
   [[windowMock stub] setRootViewController:[OCMArg any]];
diff --git a/ios/chrome/browser/commerce/push_notification/commerce_push_notification_client_unittest.mm b/ios/chrome/browser/commerce/push_notification/commerce_push_notification_client_unittest.mm
index d7d8e1bf..8de8cd3 100644
--- a/ios/chrome/browser/commerce/push_notification/commerce_push_notification_client_unittest.mm
+++ b/ios/chrome/browser/commerce/push_notification/commerce_push_notification_client_unittest.mm
@@ -173,9 +173,8 @@
     shopping_service_ = static_cast<commerce::MockShoppingService*>(
         commerce::ShoppingServiceFactory::GetForBrowserState(
             chrome_browser_state_.get()));
-    app_state_ = [[AppState alloc] initWithBrowserLauncher:nil
-                                        startupInformation:nil
-                                       applicationDelegate:nil];
+    app_state_ = [[AppState alloc] initWithStartupInformation:nil];
+
     scene_state_foreground_ =
         [[FakeSceneState alloc] initWithAppState:app_state_
                                     browserState:chrome_browser_state_.get()];
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index a22e07b..db2d594 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -873,6 +873,10 @@
     {"new-overflow-menu", flag_descriptions::kNewOverflowMenuName,
      flag_descriptions::kNewOverflowMenuDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kNewOverflowMenu)},
+    {"overflow-menu-customization",
+     flag_descriptions::kOverflowMenuCustomizationName,
+     flag_descriptions::kOverflowMenuCustomizationDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kOverflowMenuCustomization)},
     {"enable-lens-in-home-screen-widget",
      flag_descriptions::kEnableLensInHomeScreenWidgetName,
      flag_descriptions::kEnableLensInHomeScreenWidgetDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 00ef689..261bcc3 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -641,6 +641,10 @@
 const char kNewOverflowMenuName[] = "New Overflow Menu";
 const char kNewOverflowMenuDescription[] = "Enables the new overflow menu";
 
+const char kOverflowMenuCustomizationName[] = "Overflow Menu Customization";
+const char kOverflowMenuCustomizationDescription[] =
+    "Allow users to customize the order of the overflow menu";
+
 const char kNTPViewHierarchyRepairName[] = "NTP View Hierarchy Repair";
 const char kNTPViewHierarchyRepairDescription[] =
     "Checks if NTP view hierarchy is broken and fixes it if necessary.";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 1306de0..b7ca7c9 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -568,6 +568,10 @@
 extern const char kNewOverflowMenuName[];
 extern const char kNewOverflowMenuDescription[];
 
+// Title and description for the flag to enable overflow menu customization
+extern const char kOverflowMenuCustomizationName[];
+extern const char kOverflowMenuCustomizationDescription[];
+
 // Title and description for temporary bug fix to broken NTP view hierarhy.
 // TODO(crbug.com/1262536): Remove this when fixed.
 extern const char kNTPViewHierarchyRepairName[];
diff --git a/ios/chrome/browser/policy/device_management_service_configuration_ios.mm b/ios/chrome/browser/policy/device_management_service_configuration_ios.mm
index f8106b07..a566b1c 100644
--- a/ios/chrome/browser/policy/device_management_service_configuration_ios.mm
+++ b/ios/chrome/browser/policy/device_management_service_configuration_ios.mm
@@ -7,7 +7,8 @@
 #import <stdint.h>
 
 #import "base/logging.h"
-#import "base/strings/stringprintf.h"
+#import "base/strings/strcat.h"
+#import "base/strings/string_number_conversions.h"
 #import "base/system/sys_info.h"
 #import "build/build_config.h"
 #import "components/policy/core/browser/browser_policy_connector.h"
@@ -36,9 +37,9 @@
 }
 
 std::string DeviceManagementServiceConfigurationIOS::GetAgentParameter() const {
-  return base::StringPrintf("%s %s(%s)", version_info::GetProductName().c_str(),
-                            version_info::GetVersionNumber().c_str(),
-                            version_info::GetLastChange().c_str());
+  return base::StrCat({version_info::GetProductName(), " ",
+                       version_info::GetVersionNumber(), "(",
+                       version_info::GetLastChange(), ")"});
 }
 
 std::string DeviceManagementServiceConfigurationIOS::GetPlatformParameter()
@@ -53,11 +54,11 @@
   int32_t os_bugfix_version = 0;
   base::SysInfo::OperatingSystemVersionNumbers(
       &os_major_version, &os_minor_version, &os_bugfix_version);
-  os_version = base::StringPrintf("%d.%d.%d", os_major_version,
-                                  os_minor_version, os_bugfix_version);
+  os_version = base::StrCat({base::NumberToString(os_major_version), ".",
+                             base::NumberToString(os_minor_version), ".",
+                             base::NumberToString(os_bugfix_version)});
 
-  return base::StringPrintf("%s|%s|%s", os_name.c_str(), os_hardware.c_str(),
-                            os_version.c_str());
+  return base::StrCat({os_name, "|", os_hardware, "|", os_version});
 }
 
 std::string
diff --git a/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm b/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
index 691567d..39577c0 100644
--- a/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
+++ b/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
@@ -73,9 +73,7 @@
     agent_ = PolicyWatcherBrowserAgent::FromBrowser(browser_.get());
 
     // SceneState Browser Agent.
-    app_state_ = [[AppState alloc] initWithBrowserLauncher:nil
-                                        startupInformation:nil
-                                       applicationDelegate:nil];
+    app_state_ = [[AppState alloc] initWithStartupInformation:nil];
     scene_state_ =
         [[FakeSceneState alloc] initWithAppState:app_state_
                                     browserState:chrome_browser_state_.get()];
@@ -279,12 +277,8 @@
   PolicyWatcherBrowserAgent* agent =
       PolicyWatcherBrowserAgent::FromBrowser(browser.get());
 
-  // SceneState Browser Agent.
-  AppState* app_state = [[AppState alloc] initWithBrowserLauncher:nil
-                                               startupInformation:nil
-                                              applicationDelegate:nil];
   FakeSceneState* scene_state =
-      [[FakeSceneState alloc] initWithAppState:app_state
+      [[FakeSceneState alloc] initWithAppState:app_state_
                                   browserState:chrome_browser_state_.get()];
   scene_state.activationLevel = SceneActivationLevelForegroundActive;
   SceneStateBrowserAgent::CreateForBrowser(browser.get(), scene_state);
@@ -392,13 +386,9 @@
   PolicyWatcherBrowserAgent* agent =
       PolicyWatcherBrowserAgent::FromBrowser(browser.get());
 
-  // SceneState Browser Agent.
-  AppState* app_state = [[AppState alloc] initWithBrowserLauncher:nil
-                                               startupInformation:nil
-                                              applicationDelegate:nil];
   @autoreleasepool {
     FakeSceneState* scene_state =
-        [[FakeSceneState alloc] initWithAppState:app_state
+        [[FakeSceneState alloc] initWithAppState:app_state_
                                     browserState:chrome_browser_state_.get()];
     scene_state.activationLevel = SceneActivationLevelForegroundActive;
     SceneStateBrowserAgent::CreateForBrowser(browser.get(), scene_state);
@@ -443,13 +433,9 @@
   PolicyWatcherBrowserAgent* agent =
       PolicyWatcherBrowserAgent::FromBrowser(browser.get());
 
-  // SceneState Browser Agent.
-  AppState* app_state = [[AppState alloc] initWithBrowserLauncher:nil
-                                               startupInformation:nil
-                                              applicationDelegate:nil];
   @autoreleasepool {
     FakeSceneState* scene_state =
-        [[FakeSceneState alloc] initWithAppState:app_state
+        [[FakeSceneState alloc] initWithAppState:app_state_
                                     browserState:chrome_browser_state_.get()];
     scene_state.activationLevel = SceneActivationLevelForegroundActive;
     SceneStateBrowserAgent::CreateForBrowser(browser.get(), scene_state);
diff --git a/ios/chrome/browser/search_engines/ui_thread_search_terms_data.mm b/ios/chrome/browser/search_engines/ui_thread_search_terms_data.mm
index 46d424e6..2cedb1ca 100644
--- a/ios/chrome/browser/search_engines/ui_thread_search_terms_data.mm
+++ b/ios/chrome/browser/search_engines/ui_thread_search_terms_data.mm
@@ -8,6 +8,7 @@
 
 #import "base/check.h"
 #import "base/strings/escape.h"
+#import "base/strings/strcat.h"
 #import "components/google/core/common/google_util.h"
 #import "components/omnibox/browser/omnibox_field_trial.h"
 #import "components/version_info/version_info.h"
@@ -104,15 +105,12 @@
 
 std::string UIThreadSearchTermsData::GoogleImageSearchSource() const {
   DCHECK(thread_checker_.CalledOnValidThread());
-  std::string version(version_info::GetProductName() + " " +
-                      version_info::GetVersionNumber());
-  if (version_info::IsOfficialBuild())
-    version += " (Official)";
-  version += " " + version_info::GetOSType();
-  std::string modifier(GetChannelString());
-  if (!modifier.empty())
-    version += " " + modifier;
-  return version;
+  const std::string channel_name = GetChannelString();
+  return base::StrCat({version_info::GetProductName(), " ",
+                       version_info::GetVersionNumber(),
+                       version_info::IsOfficialBuild() ? " (Official) " : " ",
+                       version_info::GetOSType(),
+                       channel_name.empty() ? "" : " ", channel_name});
 }
 
 }  // namespace ios
diff --git a/ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent_unittest.mm b/ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent_unittest.mm
index 30aad2c..873f8c56 100644
--- a/ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent_unittest.mm
+++ b/ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent_unittest.mm
@@ -10,9 +10,7 @@
 #import "base/test/task_environment.h"
 #import "base/time/time.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/fake_startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/default_browser/utils.h"
 #import "ios/chrome/browser/default_browser/utils_test_support.h"
 #import "ios/chrome/browser/infobars/infobar_ios.h"
@@ -26,6 +24,7 @@
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
@@ -57,16 +56,10 @@
     std::unique_ptr<TestChromeBrowserState> chrome_browser_state =
         test_cbs_builder.Build();
 
-    id browser_launcher_mock =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
     FakeStartupInformation* startup_information =
         [[FakeStartupInformation alloc] init];
-    id main_application_delegate =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
     app_state_ =
-        [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock
-                               startupInformation:startup_information
-                              applicationDelegate:main_application_delegate];
+        [[AppState alloc] initWithStartupInformation:startup_information];
     app_state_.mainBrowserState = chrome_browser_state.get();
     scene_state_ =
         [[FakeSceneState alloc] initWithAppState:app_state_
diff --git a/ios/chrome/browser/ui/infobars/infobar_manager_app_interface.mm b/ios/chrome/browser/ui/infobars/infobar_manager_app_interface.mm
index f7358cf..8f6245e 100644
--- a/ios/chrome/browser/ui/infobars/infobar_manager_app_interface.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_manager_app_interface.mm
@@ -9,6 +9,7 @@
 #import "ios/chrome/browser/infobars/infobar_manager_impl.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/ui/infobars/test_infobar_delegate.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
diff --git a/ios/chrome/browser/ui/main/default_browser_promo_scene_agent_unittest.mm b/ios/chrome/browser/ui/main/default_browser_promo_scene_agent_unittest.mm
index 3908094..b4425c4 100644
--- a/ios/chrome/browser/ui/main/default_browser_promo_scene_agent_unittest.mm
+++ b/ios/chrome/browser/ui/main/default_browser_promo_scene_agent_unittest.mm
@@ -10,9 +10,7 @@
 #import "components/sync_preferences/pref_service_syncable.h"
 #import "components/sync_preferences/testing_pref_service_syncable.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/fake_startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/default_browser/utils.h"
 #import "ios/chrome/browser/default_browser/utils_test_support.h"
 #import "ios/chrome/browser/prefs/browser_prefs.h"
@@ -61,16 +59,10 @@
         std::make_unique<FakeAuthenticationServiceDelegate>());
     std::unique_ptr<Browser> browser_ =
         std::make_unique<TestBrowser>(browser_state_.get());
-    id browser_launcher_mock =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
     FakeStartupInformation* startup_information =
         [[FakeStartupInformation alloc] init];
-    id main_application_delegate =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
     app_state_ =
-        [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock
-                               startupInformation:startup_information
-                              applicationDelegate:main_application_delegate];
+        [[AppState alloc] initWithStartupInformation:startup_information];
     app_state_.mainBrowserState = browser_state_.get();
     promos_manager_ = std::make_unique<MockPromosManager>();
     scene_state_ =
diff --git a/ios/chrome/browser/ui/permissions/permissions_app_interface.mm b/ios/chrome/browser/ui/permissions/permissions_app_interface.mm
index 036570bf..ef6f0f3c 100644
--- a/ios/chrome/browser/ui/permissions/permissions_app_interface.mm
+++ b/ios/chrome/browser/ui/permissions/permissions_app_interface.mm
@@ -7,6 +7,7 @@
 #import "ios/chrome/app/main_controller.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/web/public/web_state.h"
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h b/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h
index 3c064de..cd25c05 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h
@@ -17,6 +17,9 @@
 // Feature to add a "Share Chrome App" action to the overflow menu
 BASE_DECLARE_FEATURE(kNewOverflowMenuShareChromeAction);
 
+// Feature to enable overflow menu customization.
+BASE_DECLARE_FEATURE(kOverflowMenuCustomization);
+
 // Whether the NewOverflowMenu feature is enabled.
 bool IsNewOverflowMenuEnabled();
 
@@ -30,4 +33,7 @@
 // Whether or not the NewOverflowMenuShareChromeAction is enabled.
 bool IsNewOverflowMenuShareChromeActionEnabled();
 
+// Whether or not overflow menu customization is enabled.
+bool IsOverflowMenuCustomizationEnabled();
+
 #endif  // IOS_CHROME_BROWSER_UI_POPUP_MENU_OVERFLOW_MENU_FEATURE_FLAGS_H_
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.mm b/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.mm
index ad2a3ed..e13dfa9 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.mm
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.mm
@@ -22,6 +22,10 @@
              "kNewOverflowMenuShareChromeAction",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kOverflowMenuCustomization,
+             "OverflowMenuCustomization",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool IsNewOverflowMenuEnabled() {
   if (@available(iOS 15, *)) {
     return base::FeatureList::IsEnabled(kNewOverflowMenu);
@@ -39,3 +43,8 @@
   return IsNewOverflowMenuEnabled() &&
          base::FeatureList::IsEnabled(kNewOverflowMenuShareChromeAction);
 }
+
+bool IsOverflowMenuCustomizationEnabled() {
+  return IsNewOverflowMenuEnabled() &&
+         base::FeatureList::IsEnabled(kOverflowMenuCustomization);
+}
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm
index 8ffb70f..45d20b6 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm
+++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm
@@ -13,6 +13,7 @@
 #import "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/ui/symbols/chrome_icon.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_coordinator.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_url_loader.h"
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 655ec33..6530e9b 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
@@ -96,9 +96,7 @@
       chrome_browser_state_.get(),
       std::make_unique<FakeAuthenticationServiceDelegate>());
   browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
-  app_state_ = [[AppState alloc] initWithBrowserLauncher:nil
-                                      startupInformation:nil
-                                     applicationDelegate:nil];
+  app_state_ = [[AppState alloc] initWithStartupInformation:nil];
   scene_state_ = [[SceneState alloc] initWithAppState:app_state_];
   SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
 
diff --git a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
index eedd11e..d956642b 100644
--- a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
@@ -143,11 +143,6 @@
   [self stopPasswordIssuesCoordinator];
 }
 
-- (void)setShouldDismissOnAllIssuesGone {
-  // No-op: This method is only used in the context of a
-  // PasswordIssuesCoordinator.
-}
-
 #pragma mark - Private
 
 - (void)stopPasswordIssuesCoordinator {
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
index 9d999087..9d73a58 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
@@ -283,13 +283,10 @@
                                      title:title
                                    message:message
                              barButtonItem:self.viewController.deleteButton];
-
-  __weak __typeof(self.delegate) weakDelegate = self.delegate;
   __weak __typeof(self.mediator) weakMediator = self.mediator;
   [self.actionSheetCoordinator
       addItemWithTitle:buttonText
                 action:^{
-                  [weakDelegate passwordDetailsWillDeletePassword];
                   [weakMediator removeCredential:password];
                 }
                  style:UIAlertActionStyleDestructive];
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
index 6173810..1b0aacde 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
@@ -14,10 +14,6 @@
 - (void)passwordDetailsCoordinatorDidRemove:
     (PasswordDetailsCoordinator*)coordinator;
 
-// Called when a passwword is currently being deleted from the
-// details page.
-- (void)passwordDetailsWillDeletePassword;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_COORDINATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.h b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.h
index 9734d16..6fa0341 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.h
+++ b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.h
@@ -23,17 +23,6 @@
 - (void)passwordIssuesCoordinatorDidRemove:
     (PasswordIssuesCoordinator*)coordinator;
 
-// Called by a PasswordIssuesCoordinator child to tell its
-// PasswordIssuesCoordinator parent to dismiss its own
-// PasswordIssuesTableViewController when all password issues are gone. This
-// happens when the PasswordIssuesCoordinator child gets notified by its
-// PasswordDetailsCoordinator that a password will be deleted from the password
-// details page. A PasswordIssuesCoordinator should dismiss its
-// PasswordIssuesTableViewController immediately after being notified that all
-// issues are gone only when its last issue was resolved from a password
-// deletion from the details page.
-- (void)setShouldDismissOnAllIssuesGone;
-
 @end
 
 // This coordinator presents a list of compromised credentials for the user.
diff --git a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.mm
index 322947ad..5ed2276 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_coordinator.mm
@@ -66,18 +66,6 @@
   // Coordinator for password issues displaying dismissed compromised
   // credentials.
   PasswordIssuesCoordinator* _dismissedPasswordIssuesCoordinator;
-
-  // Flag indicating if the coordinator should dismiss its view controller
-  // because the last password issue was resolved by a password deletion.
-  BOOL _shouldDismissOnAllIssuesGone;
-
-  // Flag indicating if the coordinator should dismiss its view controller after
-  // the view controller of a child coordinator is removed from the stack. When
-  // the issues and dismissed warnings are removed by the user, the coordinator
-  // should dismiss its view controller and go back to the previous screen. If
-  // there are child coordinators, this flag is used to dismiss the view
-  // controller after the children are dismissed.
-  BOOL _shouldDismissAfterChildCoordinatorRemoved;
 }
 
 // Main view controller for this coordinator.
@@ -195,72 +183,6 @@
 }
 
 - (void)dismissAfterAllIssuesGone {
-  // Early return if the last issue was not resolved by a password deletion, but
-  // by a password change. When the last issue is resolved by a password change,
-  // the details page has to be dismissed manually by the user.
-  if (_shouldDismissOnAllIssuesGone) {
-    [self navigateToPreviousViewController];
-  } else {
-    _shouldDismissAfterChildCoordinatorRemoved = YES;
-  }
-}
-
-#pragma mark - PasswordDetailsCoordinatorDelegate
-
-- (void)passwordDetailsCoordinatorDidRemove:
-    (PasswordDetailsCoordinator*)coordinator {
-  DCHECK_EQ(self.passwordDetails, coordinator);
-  [self.passwordDetails stop];
-  self.passwordDetails.delegate = nil;
-  self.passwordDetails = nil;
-
-  [self onChildCoordinatorDidRemove];
-}
-
-- (void)passwordDetailsWillDeletePassword {
-  _shouldDismissOnAllIssuesGone = self.mediator.hasOneIssueLeft;
-  [self.delegate setShouldDismissOnAllIssuesGone];
-}
-
-#pragma mark - PasswordIssuesCoordinatorDelegate
-
-- (void)passwordIssuesCoordinatorDidRemove:
-    (PasswordIssuesCoordinator*)coordinator {
-  CHECK_EQ(_dismissedPasswordIssuesCoordinator, coordinator);
-  [self stopDismissedPasswordIssuesCoordinator];
-
-  [self onChildCoordinatorDidRemove];
-}
-
-- (void)setShouldDismissOnAllIssuesGone {
-  _shouldDismissOnAllIssuesGone = self.mediator.hasOneIssueLeft;
-}
-
-#pragma mark - Private
-
-- (void)stopDismissedPasswordIssuesCoordinator {
-  [_dismissedPasswordIssuesCoordinator stop];
-  _dismissedPasswordIssuesCoordinator.reauthModule = nil;
-  _dismissedPasswordIssuesCoordinator.delegate = nil;
-  _dismissedPasswordIssuesCoordinator = nil;
-}
-
-// Called after the view controller of a child coordinator of `self` was removed
-// from the navigation stack.
-- (void)onChildCoordinatorDidRemove {
-  // If the content of the view controller was gone while a child coordinator
-  // was presenting content, dismiss the view controller now that the child
-  // coordinator's vc was removed.
-  if (_shouldDismissAfterChildCoordinatorRemoved) {
-    CHECK_EQ(self.baseNavigationController.topViewController,
-             self.viewController);
-    _shouldDismissAfterChildCoordinatorRemoved = NO;
-    [self.baseNavigationController popViewControllerAnimated:NO];
-  }
-}
-
-// Navigates to the previous view controller in the navigation stack.
-- (void)navigateToPreviousViewController {
   UINavigationController* baseNavigationController =
       self.baseNavigationController;
   NSInteger indexInNavigationController =
@@ -282,4 +204,31 @@
                  animated:YES];
 }
 
+#pragma mark - PasswordDetailsCoordinatorDelegate
+
+- (void)passwordDetailsCoordinatorDidRemove:
+    (PasswordDetailsCoordinator*)coordinator {
+  DCHECK_EQ(self.passwordDetails, coordinator);
+  [self.passwordDetails stop];
+  self.passwordDetails.delegate = nil;
+  self.passwordDetails = nil;
+}
+
+#pragma mark - PasswordIssuesCoordinatorDelegate
+
+- (void)passwordIssuesCoordinatorDidRemove:
+    (PasswordIssuesCoordinator*)coordinator {
+  CHECK_EQ(_dismissedPasswordIssuesCoordinator, coordinator);
+  [self stopDismissedPasswordIssuesCoordinator];
+}
+
+#pragma mark - Private
+
+- (void)stopDismissedPasswordIssuesCoordinator {
+  [_dismissedPasswordIssuesCoordinator stop];
+  _dismissedPasswordIssuesCoordinator.reauthModule = nil;
+  _dismissedPasswordIssuesCoordinator.delegate = nil;
+  _dismissedPasswordIssuesCoordinator = nil;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_mediator.mm b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_mediator.mm
index 01d866e7..5542dbb24 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues/password_issues_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues/password_issues_mediator.mm
@@ -246,10 +246,6 @@
 }
 
 - (BOOL)hasOneIssueLeft {
-  if (_warningType == WarningType::kReusedPasswordsWarning) {
-    return _insecureCredentials.has_value() &&
-           _insecureCredentials->size() == 2;
-  }
   return _insecureCredentials.has_value() &&
          _insecureCredentials->size() == 1 && _dismissedWarningsCount == 0;
 }
diff --git a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
index 967a145..fdc72cf 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
@@ -351,11 +351,6 @@
   self.passwordIssuesCoordinator = nil;
 }
 
-- (void)setShouldDismissOnAllIssuesGone {
-  // No-op: This method is only used in the context of a
-  // PasswordIssuesCoordinator.
-}
-
 #pragma mark - PasswordCheckupCoordinatorDelegate
 
 - (void)passwordCheckupCoordinatorDidRemove:
@@ -376,11 +371,6 @@
   self.passwordDetailsCoordinator = nil;
 }
 
-- (void)passwordDetailsWillDeletePassword {
-  // No-op: This method is only used when the Password Details page is presented
-  // from a PasswordIssuesCoordinator.
-}
-
 #pragma mark AddPasswordDetailsCoordinatorDelegate
 
 - (void)passwordDetailsTableViewControllerDidFinish:
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
index 0c9af5c..496d9e1 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
@@ -288,11 +288,6 @@
   self.passwordIssuesCoordinator = nil;
 }
 
-- (void)setShouldDismissOnAllIssuesGone {
-  // No-op: This method is only used in the context of a
-  // PasswordIssuesCoordinator.
-}
-
 #pragma mark - PrivacySafeBrowsingCoordinatorDelegate
 
 - (void)privacySafeBrowsingCoordinatorDidRemove:
diff --git a/ios/chrome/browser/ui/settings/settings_app_interface.mm b/ios/chrome/browser/ui/settings/settings_app_interface.mm
index 82b5cfe..b35043f 100644
--- a/ios/chrome/browser/ui/settings/settings_app_interface.mm
+++ b/ios/chrome/browser/ui/settings/settings_app_interface.mm
@@ -16,6 +16,7 @@
 #import "ios/chrome/browser/prefs/pref_names.h"
 #import "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/app/tab_test_util.h"
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
index 4805124..33ef0db0 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -769,11 +769,6 @@
   self.passwordDetailsCoordinator = nil;
 }
 
-- (void)passwordDetailsWillDeletePassword {
-  // No-op: This method is only used when the Password Details page is presented
-  // from a PasswordIssuesCoordinator.
-}
-
 #pragma mark - ClearBrowsingDataCoordinatorDelegate
 
 - (void)clearBrowsingDataCoordinatorViewControllerWasRemoved:
diff --git a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
index ad22d4e6..d10e4c3 100644
--- a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
+++ b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
@@ -9,9 +9,7 @@
 #import "base/test/task_environment.h"
 #import "components/favicon/ios/web_favicon_driver.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/fake_startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
@@ -58,15 +56,9 @@
  public:
   StartSurfaceSceneAgentTest()
       : browser_state_(TestChromeBrowserState::Builder().Build()),
-        browser_launcher_mock_(
-            [OCMockObject mockForProtocol:@protocol(BrowserLauncher)]),
         startup_information_([[FakeStartupInformation alloc] init]),
-        main_application_delegate_(
-            [OCMockObject mockForClass:[MainApplicationDelegate class]]),
         app_state_([[FakeAppStateInitStage alloc]
-            initWithBrowserLauncher:browser_launcher_mock_
-                 startupInformation:startup_information_
-                applicationDelegate:main_application_delegate_]),
+            initWithStartupInformation:startup_information_]),
         scene_state_([[FakeSceneState alloc]
             initWithAppState:app_state_
                 browserState:browser_state_.get()]),
@@ -89,9 +81,7 @@
  protected:
   base::test::TaskEnvironment task_environment_;
   std::unique_ptr<TestChromeBrowserState> browser_state_;
-  id browser_launcher_mock_;
   FakeStartupInformation* startup_information_;
-  id main_application_delegate_;
   FakeAppStateInitStage* app_state_;
   // The scene state that the agent works with.
   FakeSceneState* scene_state_;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_mediator.mm
index 35ff415b..23caa8e 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_mediator.mm
@@ -561,10 +561,11 @@
   loadParams.transition_type = ui::PAGE_TRANSITION_TYPED;
   webState->GetNavigationManager()->LoadURLWithParams(loadParams);
 
-  // Insert a new webState using the `INSERT_PINNED` flag.
+  // Insert a new webState using the `INSERT_PINNED` flag and activate it.
   self.webStateList->InsertWebState(
       base::checked_cast<int>(index), std::move(webState),
-      (WebStateList::INSERT_PINNED), WebStateOpener());
+      (WebStateList::INSERT_PINNED | WebStateList::INSERT_ACTIVATE),
+      WebStateOpener());
 }
 
 // Converts the collection view's item index to WebStateList index.
diff --git a/ios/chrome/browser/ui/whats_new/promo/whats_new_scene_agent_unittest.mm b/ios/chrome/browser/ui/whats_new/promo/whats_new_scene_agent_unittest.mm
index ef5ffbae..297614e 100644
--- a/ios/chrome/browser/ui/whats_new/promo/whats_new_scene_agent_unittest.mm
+++ b/ios/chrome/browser/ui/whats_new/promo/whats_new_scene_agent_unittest.mm
@@ -7,9 +7,7 @@
 #import "base/test/scoped_feature_list.h"
 #import "base/test/task_environment.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/fake_startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/promos_manager/constants.h"
 #import "ios/chrome/browser/promos_manager/features.h"
 #import "ios/chrome/browser/promos_manager/mock_promos_manager.h"
@@ -61,21 +59,15 @@
         TestChromeBrowserState::Builder().Build();
     std::unique_ptr<Browser> browser_ =
         std::make_unique<TestBrowser>(browser_state_.get());
-    id browser_launcher_mock_ =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
     FakeStartupInformation* startup_information_ =
         [[FakeStartupInformation alloc] init];
-    id main_application_delegate_ =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
-    AppState* app_state =
-        [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
-                               startupInformation:startup_information_
-                              applicationDelegate:main_application_delegate_];
+    app_state_ =
+        [[AppState alloc] initWithStartupInformation:startup_information_];
     promos_manager_ = std::make_unique<MockPromosManager>();
     agent_ = [[WhatsNewSceneAgent alloc]
         initWithPromosManager:promos_manager_.get()];
     scene_state_ =
-        [[FakeSceneState alloc] initWithAppState:app_state
+        [[FakeSceneState alloc] initWithAppState:app_state_
                                     browserState:browser_state_.get()];
     scene_state_.scene = static_cast<UIWindowScene*>(
         [[[UIApplication sharedApplication] connectedScenes] anyObject]);
@@ -87,6 +79,8 @@
 
  protected:
   WhatsNewSceneAgent* agent_;
+  // SceneState only weakly holds AppState, so keep it alive here.
+  AppState* app_state_;
   FakeSceneState* scene_state_;
   base::test::TaskEnvironment task_environment_;
   std::unique_ptr<MockPromosManager> promos_manager_;
diff --git a/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.mm b/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.mm
index 1e1c11d5..2ba13af7 100644
--- a/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.mm
+++ b/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.h"
 
 #import "base/no_destructor.h"
-#import "base/strings/stringprintf.h"
+#import "base/strings/strcat.h"
 #import "components/version_info/version_info.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/common/channel_info.h"
@@ -26,9 +26,8 @@
 }
 
 std::string IOSChromeUpdateQueryParamsDelegate::GetExtraParams() {
-  return base::StringPrintf(
-      "&prodchannel=%s&prodversion=%s&lang=%s", GetChannelString().c_str(),
-      version_info::GetVersionNumber().c_str(), GetLang().c_str());
+  return base::StrCat({"&prodchannel=", GetChannelString(), "&prodversion=",
+                       version_info::GetVersionNumber(), "&lang=", GetLang()});
 }
 
 // static
diff --git a/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate_unittest.cc b/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate_unittest.cc
index 3bd652a..19e8f2f 100644
--- a/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate_unittest.cc
+++ b/ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate_unittest.cc
@@ -8,15 +8,13 @@
 
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/strcat.h"
 #include "components/update_client/update_query_params.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/common/channel_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
-using base::StringPrintf;
-
 namespace {
 
 bool Contains(const std::string& source, const std::string& target) {
@@ -30,23 +28,22 @@
 
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("os=%s", update_client::UpdateQueryParams::GetOS())));
+      base::StrCat({"os=", update_client::UpdateQueryParams::GetOS()})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("arch=%s", update_client::UpdateQueryParams::GetArch())));
+      base::StrCat({"arch=", update_client::UpdateQueryParams::GetArch()})));
   EXPECT_TRUE(Contains(
-      params, StringPrintf(
-                  "prod=%s",
-                  update_client::UpdateQueryParams::GetProdIdString(prod_id))));
-  EXPECT_TRUE(Contains(
-      params, StringPrintf("prodchannel=%s", GetChannelString().c_str())));
+      params,
+      base::StrCat({"prod=", update_client::UpdateQueryParams::GetProdIdString(
+                                 prod_id)})));
   EXPECT_TRUE(
-      Contains(params, StringPrintf("prodversion=%s",
-                                    version_info::GetVersionNumber().c_str())));
+      Contains(params, base::StrCat({"prodchannel=", GetChannelString()})));
   EXPECT_TRUE(Contains(
       params,
-      StringPrintf("lang=%s",
-                   IOSChromeUpdateQueryParamsDelegate::GetLang().c_str())));
+      base::StrCat({"prodversion=", version_info::GetVersionNumber()})));
+  EXPECT_TRUE(Contains(
+      params,
+      base::StrCat({"lang=", IOSChromeUpdateQueryParamsDelegate::GetLang()})));
 }
 
 using IOSChromeUpdateQueryParamsDelegateTest = PlatformTest;
diff --git a/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm b/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
index 998c5fef..19f2e76 100644
--- a/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
+++ b/ios/chrome/browser/web/certificate_policy_app_agent_unittest.mm
@@ -10,9 +10,7 @@
 #import "base/test/ios/wait_util.h"
 #import "base/time/time.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
-#import "ios/chrome/app/application_delegate/browser_launcher.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
-#import "ios/chrome/app/main_application_delegate.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
@@ -53,13 +51,9 @@
         cert_(net::ImportCertFromFile(net::GetTestCertsDirectory(),
                                       "ok_cert.pem")),
         status_(net::CERT_STATUS_REVOKED) {
-    // Mocks for AppState dependencies.
-    browser_launcher_mock_ =
-        [OCMockObject mockForProtocol:@protocol(BrowserLauncher)];
+    // Mock for AppState dependencies.
     startup_information_mock_ =
         [OCMockObject mockForProtocol:@protocol(StartupInformation)];
-    main_application_delegate_ =
-        [OCMockObject mockForClass:[MainApplicationDelegate class]];
 
     TestChromeBrowserState::Builder test_cbs_builder;
     chrome_browser_state_ = test_cbs_builder.Build();
@@ -68,9 +62,7 @@
         BrowserListFactory::GetForBrowserState(chrome_browser_state_.get());
 
     app_state_ =
-        [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
-                               startupInformation:startup_information_mock_
-                              applicationDelegate:main_application_delegate_];
+        [[AppState alloc] initWithStartupInformation:startup_information_mock_];
     app_state_.mainBrowserState = chrome_browser_state_.get();
 
     // Create two regular and one OTR browsers.
@@ -244,10 +236,8 @@
   scoped_refptr<net::X509Certificate> cert_;
   net::CertStatus status_;
 
-  // Mocks for AppState dependencies.
-  id browser_launcher_mock_;
+  // Mock for AppState dependencies.
   id startup_information_mock_;
-  id main_application_delegate_;
 };
 
 // Test that updating an empty cache with no webstates results in an empty
diff --git a/ios/chrome/test/app/chrome_test_util.mm b/ios/chrome/test/app/chrome_test_util.mm
index 101cffb2..4f2493d 100644
--- a/ios/chrome/test/app/chrome_test_util.mm
+++ b/ios/chrome/test/app/chrome_test_util.mm
@@ -28,6 +28,7 @@
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state_manager.h"
 #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
diff --git a/ios/chrome/test/app/tab_test_util.mm b/ios/chrome/test/app/tab_test_util.mm
index 5b8146a..ab245c5 100644
--- a/ios/chrome/test/app/tab_test_util.mm
+++ b/ios/chrome/test/app/tab_test_util.mm
@@ -15,6 +15,7 @@
 #import "ios/chrome/browser/shared/coordinator/scene/scene_controller_testing.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 2cfbea7f..8253bc0d 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -43,6 +43,7 @@
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
diff --git a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
index bb4fc86..43dfcd4b 100644
--- a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
+++ b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
@@ -17,6 +17,7 @@
 #import "ios/chrome/app/main_controller.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_provider.h"
+#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/app/settings_test_util.h"
diff --git a/ios/web/js_messaging/web_view_js_utils_unittest.mm b/ios/web/js_messaging/web_view_js_utils_unittest.mm
index 711cb4fc..1ca13ea 100644
--- a/ios/web/js_messaging/web_view_js_utils_unittest.mm
+++ b/ios/web/js_messaging/web_view_js_utils_unittest.mm
@@ -261,9 +261,9 @@
   EXPECT_FALSE(current_list);
 }
 
+// Tests that NSObjectFromValueResult converts nullptr to nil.
 TEST_F(WebViewJsUtilsTest, NSObjectFromNullptr) {
   id wk_result = web::NSObjectFromValueResult(nullptr);
-  // `wk_result` should be nil.
   EXPECT_FALSE(wk_result);
 }
 
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 953b6acb..4fd8c35 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -324,7 +324,7 @@
 // The length of the initial delay during which the "Allow"-button is disabled
 // in the share-this-tab dialog.
 const base::FeatureParam<int> kShareThisTabDialogActivationDelayMs{
-    &kShareThisTabDialog, "activation_delay_ms", 1500};
+    &kShareThisTabDialog, "activation_delay_ms", 500};
 
 // Only used for disabling overlay fullscreen (aka SurfaceView) in Clank.
 BASE_FEATURE(kOverlayFullscreenVideo,
diff --git a/media/base/status_unittest.cc b/media/base/status_unittest.cc
index 3065a33..489576a 100644
--- a/media/base/status_unittest.cc
+++ b/media/base/status_unittest.cc
@@ -459,8 +459,8 @@
   EXPECT_TRUE(ok.has_value());
   // One cannot call ok.code() without an okay type.
 
-  base::Value actual = MediaSerialize(bar);
-  EXPECT_EQ(*actual.FindIntPath("code"), static_cast<int>(bar.code()));
+  base::Value::Dict actual = MediaSerialize(bar).TakeDict();
+  EXPECT_EQ(*actual.FindInt("code"), static_cast<int>(bar.code()));
 }
 
 TEST_F(StatusTest, TypedStatusWithNoDefaultHasOk) {
@@ -487,8 +487,8 @@
   EXPECT_TRUE(ok.has_value());
   EXPECT_EQ(ok.code(), NDStatus::Codes::kOk);
 
-  base::Value actual = MediaSerialize(bar);
-  EXPECT_EQ(*actual.FindIntPath("code"), static_cast<int>(bar.code()));
+  base::Value::Dict actual = MediaSerialize(bar).TakeDict();
+  EXPECT_EQ(*actual.FindInt("code"), static_cast<int>(bar.code()));
 }
 
 TEST_F(StatusTest, Okayness) {
diff --git a/media/cast/OWNERS b/media/cast/OWNERS
index 736f339..866a031 100644
--- a/media/cast/OWNERS
+++ b/media/cast/OWNERS
@@ -1,3 +1,2 @@
 jophba@chromium.org
 mfoltz@chromium.org
-rwkeane@google.com
diff --git a/media/video/gpu_video_accelerator_factories.h b/media/video/gpu_video_accelerator_factories.h
index beb43bd2..a2dbcae 100644
--- a/media/video/gpu_video_accelerator_factories.h
+++ b/media/video/gpu_video_accelerator_factories.h
@@ -59,16 +59,19 @@
 //   runnner, unless otherwise documented below.
 class MEDIA_EXPORT GpuVideoAcceleratorFactories {
  public:
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused
   enum class OutputFormat {
-    UNDEFINED = 0,    // Unset state
-    I420,             // 3 x R8 GMBs
-    NV12_SINGLE_GMB,  // One NV12 GMB
-    NV12_DUAL_GMB,    // One R8, one RG88 GMB
-    XR30,             // 10:10:10:2 BGRX in one GMB (Usually Mac)
-    XB30,             // 10:10:10:2 RGBX in one GMB
-    RGBA,             // One 8:8:8:8 RGBA
-    BGRA,             // One 8:8:8:8 BGRA (Usually Mac)
-    P010,             // One P010 GMB.
+    UNDEFINED = 0,        // Unset state
+    I420 = 1,             // 3 x R8 GMBs
+    NV12_SINGLE_GMB = 2,  // One NV12 GMB
+    NV12_DUAL_GMB = 3,    // One R8, one RG88 GMB
+    XR30 = 4,             // 10:10:10:2 BGRX in one GMB (Usually Mac)
+    XB30 = 5,             // 10:10:10:2 RGBX in one GMB
+    RGBA = 6,             // One 8:8:8:8 RGBA
+    BGRA = 7,             // One 8:8:8:8 BGRA (Usually Mac)
+    P010 = 8,             // One P010 GMB.
+    kMaxValue = P010
   };
 
   enum class Supported {
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index fb7dcaf7..ee8c035 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -283,6 +283,22 @@
   UMA_HISTOGRAM_ENUMERATION("API.StorageAccess.AllowedRequests2", result);
 }
 
+void FireStorageAccessInputHistogram(bool has_opt_in, bool has_grant) {
+  StorageAccessInputState input_state;
+  if (has_opt_in && has_grant) {
+    input_state = StorageAccessInputState::kOptInWithGrant;
+  } else if (has_opt_in && !has_grant) {
+    input_state = StorageAccessInputState::kOptInWithoutGrant;
+  } else if (!has_opt_in && has_grant) {
+    input_state = StorageAccessInputState::kGrantWithoutOptIn;
+  } else if (!has_opt_in && !has_grant) {
+    input_state = StorageAccessInputState::kNoOptInNoGrant;
+  } else {
+    NOTREACHED_NORETURN();
+  }
+  base::UmaHistogramEnumeration("API.StorageAccess.InputState", input_state);
+}
+
 bool DomainIsHostOnly(const std::string& domain_string) {
   return (domain_string.empty() || domain_string[0] != '.');
 }
diff --git a/net/cookies/cookie_util.h b/net/cookies/cookie_util.h
index a000c7e..066ac74f 100644
--- a/net/cookies/cookie_util.h
+++ b/net/cookies/cookie_util.h
@@ -51,6 +51,25 @@
 // allowed or not by the provided |result|.
 NET_EXPORT void FireStorageAccessHistogram(StorageAccessResult result);
 
+// This enum must match the numbering for StorageAccessInputState in
+// histograms/enums.xml. Do not reorder or remove items, only add new items at
+// the end.
+enum class StorageAccessInputState {
+  // The frame-level opt-in was provided, and a permission grant exists.
+  kOptInWithGrant = 0,
+  // The frame-level opt-in was provided, but no permission grant exists.
+  kOptInWithoutGrant = 1,
+  // No frame-level opt-in was provided, but a permission grant exists.
+  kGrantWithoutOptIn = 2,
+  // No frame-level opt-in was provided, and no permission grant exists.
+  kNoOptInNoGrant = 3,
+  kMaxValue = kNoOptInNoGrant,
+};
+// Helper to record a histogram sample for relevant Storage Access API state
+// when cookie settings queries consult the Storage Access API grants.
+NET_EXPORT void FireStorageAccessInputHistogram(bool has_opt_in,
+                                                bool has_grant);
+
 // Returns the effective TLD+1 for a given host. This only makes sense for http
 // and https schemes. For other schemes, the host will be returned unchanged
 // (minus any leading period).
diff --git a/printing/backend/cups_ipp_constants.cc b/printing/backend/cups_ipp_constants.cc
index 5f03904..3bbaf54 100644
--- a/printing/backend/cups_ipp_constants.cc
+++ b/printing/backend/cups_ipp_constants.cc
@@ -19,13 +19,18 @@
 constexpr char kIppPin[] = "job-password";                       // PWG 5100.11
 constexpr char kIppPinEncryption[] = "job-password-encryption";  // PWG 5100.11
 constexpr char kIppPrinterUri[] = "printer-uri";                 // RFC 8011
+constexpr char kIppRequestedAttributes[] = "requested-attributes";  // RFC 8011
 constexpr char kIppRequestingUserName[] = "requesting-user-name";  // RFC 8011
 
+// printer attributes
+constexpr char kIppMediaColDatabase[] = "media-col-database";
+
 // job attributes
 constexpr char kIppCollate[] = "multiple-document-handling";  // PWG 5100.19
 constexpr char kIppCopies[] = CUPS_COPIES;
 constexpr char kIppColor[] = CUPS_PRINT_COLOR_MODE;
 constexpr char kIppMedia[] = CUPS_MEDIA;
+constexpr char kIppMediaCol[] = "media-col";  // PWG 5100.7
 constexpr char kIppDuplex[] = CUPS_SIDES;
 constexpr char kIppResolution[] = "printer-resolution";  // RFC 8011
 
@@ -33,6 +38,16 @@
 constexpr char kCollated[] = "separate-documents-collated-copies";
 constexpr char kUncollated[] = "separate-documents-uncollated-copies";
 
+// media-col collection members (all from PWG 5100.7)
+constexpr char kIppMediaBottomMargin[] = "media-bottom-margin";
+constexpr char kIppMediaLeftMargin[] = "media-left-margin";
+constexpr char kIppMediaRightMargin[] = "media-right-margin";
+constexpr char kIppMediaSize[] = "media-size";
+constexpr char kIppMediaSource[] = "media-source";
+constexpr char kIppMediaTopMargin[] = "media-top-margin";
+constexpr char kIppXDimension[] = "x-dimension";
+constexpr char kIppYDimension[] = "y-dimension";
+
 #if BUILDFLAG(IS_CHROMEOS)
 
 constexpr char kIppDocumentAttributes[] =
@@ -44,6 +59,7 @@
 constexpr char kOptionFalse[] = "false";
 constexpr char kOptionTrue[] = "true";
 
+// client-info
 constexpr char kIppClientInfo[] = "client-info";
 constexpr char kIppClientName[] = "client-name";
 constexpr char kIppClientPatches[] = "client-patches";
diff --git a/printing/backend/cups_ipp_constants.h b/printing/backend/cups_ipp_constants.h
index f107c02..0efbf25 100644
--- a/printing/backend/cups_ipp_constants.h
+++ b/printing/backend/cups_ipp_constants.h
@@ -19,13 +19,18 @@
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppPin[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppPinEncryption[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppPrinterUri[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppRequestedAttributes[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppRequestingUserName[];
 
+// printer attributes
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaColDatabase[];
+
 // job attributes
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppCollate[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppCopies[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppColor[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMedia[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaCol[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppDuplex[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppResolution[];
 
@@ -33,6 +38,16 @@
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kCollated[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kUncollated[];
 
+// media-col collection members
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaBottomMargin[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaLeftMargin[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaRightMargin[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaSize[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaSource[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppMediaTopMargin[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppXDimension[];
+COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppYDimension[];
+
 #if BUILDFLAG(IS_CHROMEOS)
 
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppDocumentAttributes[];
@@ -43,6 +58,7 @@
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kOptionFalse[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kOptionTrue[];
 
+// client-info
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientInfo[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientName[];
 COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientPatches[];
diff --git a/printing/backend/cups_ipp_helper.cc b/printing/backend/cups_ipp_helper.cc
index 34b39c3..ae3f83e7 100644
--- a/printing/backend/cups_ipp_helper.cc
+++ b/printing/backend/cups_ipp_helper.cc
@@ -12,7 +12,9 @@
 
 #include "base/containers/contains.h"
 #include "base/containers/fixed_flat_set.h"
+#include "base/containers/flat_map.h"
 #include "base/logging.h"
+#include "base/numerics/clamped_math.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
@@ -244,23 +246,112 @@
   }
 }
 
+absl::optional<PrinterSemanticCapsAndDefaults::Paper>
+PaperFromMediaColDatabaseEntry(ipp_t* db_entry) {
+  DCHECK(db_entry);
+
+  ipp_t* media_size = ippGetCollection(
+      ippFindAttribute(db_entry, kIppMediaSize, IPP_TAG_BEGIN_COLLECTION), 0);
+  ipp_attribute_t* width_attr =
+      ippFindAttribute(media_size, kIppXDimension, IPP_TAG_INTEGER);
+  ipp_attribute_t* height_attr =
+      ippFindAttribute(media_size, kIppYDimension, IPP_TAG_INTEGER);
+
+  if (!width_attr || !height_attr) {
+    // If x-dimension and y-dimension don't have IPP_TAG_INTEGER, they are
+    // custom size ranges, so we want to skip this "size".
+    return absl::nullopt;
+  }
+
+  int width = ippGetInteger(width_attr, 0);
+  int height = ippGetInteger(height_attr, 0);
+
+  ipp_attribute_t* bottom_attr =
+      ippFindAttribute(db_entry, kIppMediaBottomMargin, IPP_TAG_INTEGER);
+  ipp_attribute_t* left_attr =
+      ippFindAttribute(db_entry, kIppMediaLeftMargin, IPP_TAG_INTEGER);
+  ipp_attribute_t* right_attr =
+      ippFindAttribute(db_entry, kIppMediaRightMargin, IPP_TAG_INTEGER);
+  ipp_attribute_t* top_attr =
+      ippFindAttribute(db_entry, kIppMediaTopMargin, IPP_TAG_INTEGER);
+  DCHECK(bottom_attr);
+  DCHECK(left_attr);
+  DCHECK(right_attr);
+  DCHECK(top_attr);
+  int bottom_margin = ippGetInteger(bottom_attr, 0);
+  int left_margin = ippGetInteger(left_attr, 0);
+  int right_margin = ippGetInteger(right_attr, 0);
+  int top_margin = ippGetInteger(top_attr, 0);
+
+  if (width <= 0 || height <= 0 || bottom_margin < 0 || top_margin < 0 ||
+      left_margin < 0 || right_margin < 0 ||
+      width <= base::ClampedNumeric<int>(left_margin) + right_margin ||
+      height <= base::ClampedNumeric<int>(bottom_margin) + top_margin) {
+    LOG(WARNING) << "Invalid media-col-database entry:"
+                 << " x-dimension=" << width << " y-dimension=" << height
+                 << " media-bottom-margin=" << bottom_margin
+                 << " media-left-margin=" << left_margin
+                 << " media-right-margin=" << right_margin
+                 << " media-top-margin=" << top_margin;
+    return absl::nullopt;
+  }
+
+  PrinterSemanticCapsAndDefaults::Paper paper;
+  paper.size_um =
+      gfx::Size(width * kMicronsPerPwgUnit, height * kMicronsPerPwgUnit);
+  paper.printable_area_um = PrintableAreaFromSizeAndPwgMargins(
+      paper.size_um, bottom_margin, left_margin, right_margin, top_margin);
+
+  return paper;
+}
+
+bool PaperIsBorderless(const PrinterSemanticCapsAndDefaults::Paper& paper) {
+  return paper.printable_area_um.x() == 0 && paper.printable_area_um.y() == 0 &&
+         paper.printable_area_um.width() == paper.size_um.width() &&
+         paper.printable_area_um.height() == paper.size_um.height();
+}
+
 PrinterSemanticCapsAndDefaults::Papers SupportedPapers(
     const CupsPrinter& printer) {
-  std::vector<base::StringPiece> papers =
-      printer.GetSupportedOptionValueStrings(kIppMedia);
-  PrinterSemanticCapsAndDefaults::Papers parsed_papers;
-  parsed_papers.reserve(papers.size());
-  for (base::StringPiece paper : papers) {
-    PrinterSemanticCapsAndDefaults::Paper parsed =
-        ParsePaper(paper, printer.GetMediaMarginsByName(std::string(paper)));
-    // If a paper fails to parse reasonably, we should avoid propagating
-    // it - e.g. CUPS is known to give out empty vendor IDs at times:
-    // https://crbug.com/920295#c23
-    if (!parsed.display_name.empty()) {
-      parsed_papers.push_back(parsed);
+  auto size_compare = [](const gfx::Size& a, const gfx::Size& b) {
+    auto result = a.width() - b.width();
+    if (result == 0) {
+      result = a.height() - b.height();
+    }
+    return result < 0;
+  };
+  std::map<gfx::Size, PrinterSemanticCapsAndDefaults::Paper,
+           decltype(size_compare)>
+      paper_map;
+
+  ipp_attribute_t* attr = printer.GetMediaColDatabase();
+  int count = ippGetCount(attr);
+
+  for (int i = 0; i < count; i++) {
+    ipp_t* db_entry = ippGetCollection(attr, i);
+
+    absl::optional<PrinterSemanticCapsAndDefaults::Paper> paper_opt =
+        PaperFromMediaColDatabaseEntry(db_entry);
+    if (!paper_opt.has_value()) {
+      continue;
+    }
+
+    const auto& paper = paper_opt.value();
+    if (auto existing_entry = paper_map.find(paper.size_um);
+        existing_entry != paper_map.end()) {
+      // Prefer non-borderless versions of paper sizes.
+      if (PaperIsBorderless(existing_entry->second)) {
+        existing_entry->second = paper;
+      }
+    } else {
+      paper_map.emplace(paper.size_um, paper);
     }
   }
 
+  PrinterSemanticCapsAndDefaults::Papers parsed_papers;
+  for (const auto& entry : paper_map) {
+    parsed_papers.push_back(entry.second);
+  }
   return parsed_papers;
 }
 
@@ -340,10 +431,6 @@
 size_t AddInputTray(const CupsOptionProvider& printer,
                     AdvancedCapabilities* caps) {
   size_t previous_size = caps->size();
-  // b/151324273: CUPS doesn't implement media-source in media-col-database like
-  // it should according to the IPP specs. However, it does implement a naked
-  // media-source attribute which we can use until the proper changes can be
-  // made to media-col-database.
   KeywordHandler(printer, "media-source", caps);
   return caps->size() - previous_size;
 }
@@ -361,15 +448,17 @@
 }  // namespace
 
 PrinterSemanticCapsAndDefaults::Paper DefaultPaper(const CupsPrinter& printer) {
-  ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppMedia);
+  ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppMediaCol);
   if (!attr)
     return PrinterSemanticCapsAndDefaults::Paper();
-  const char* const media_name = ippGetString(attr, 0, nullptr);
-  if (!media_name) {
+  ipp_t* media_col_default = ippGetCollection(attr, 0);
+  if (!media_col_default) {
     return PrinterSemanticCapsAndDefaults::Paper();
   }
 
-  return ParsePaper(media_name, printer.GetMediaMarginsByName(media_name));
+  PrinterSemanticCapsAndDefaults::Paper paper;
+  return PaperFromMediaColDatabaseEntry(media_col_default)
+      .value_or(PrinterSemanticCapsAndDefaults::Paper());
 }
 
 void CapsAndDefaultsFromPrinter(const CupsPrinter& printer,
@@ -393,6 +482,37 @@
   ExtractResolutions(printer, printer_info);
 }
 
+gfx::Rect GetPrintableAreaForSize(const CupsPrinter& printer,
+                                  const gfx::Size& size_um) {
+  ipp_attribute_t* attr = printer.GetMediaColDatabase();
+  int count = ippGetCount(attr);
+  gfx::Rect result(0, 0, size_um.width(), size_um.height());
+
+  for (int i = 0; i < count; i++) {
+    ipp_t* db_entry = ippGetCollection(attr, i);
+
+    absl::optional<PrinterSemanticCapsAndDefaults::Paper> paper_opt =
+        PaperFromMediaColDatabaseEntry(db_entry);
+    if (!paper_opt.has_value()) {
+      continue;
+    }
+
+    const auto& paper = paper_opt.value();
+    if (paper.size_um != size_um) {
+      continue;
+    }
+
+    result = paper.printable_area_um;
+
+    // If this is a borderless size, try to find a non-borderless version.
+    if (!PaperIsBorderless(paper)) {
+      return result;
+    }
+  }
+
+  return result;
+}
+
 ScopedIppPtr WrapIpp(ipp_t* ipp) {
   return ScopedIppPtr(ipp, &ippDelete);
 }
diff --git a/printing/backend/cups_ipp_helper.h b/printing/backend/cups_ipp_helper.h
index 37ccd95..b1ef029 100644
--- a/printing/backend/cups_ipp_helper.h
+++ b/printing/backend/cups_ipp_helper.h
@@ -31,6 +31,11 @@
 void CapsAndDefaultsFromPrinter(const CupsPrinter& printer,
                                 PrinterSemanticCapsAndDefaults* printer_info);
 
+// Gets the printer margins for the provided paper size.
+COMPONENT_EXPORT(PRINT_BACKEND)
+gfx::Rect GetPrintableAreaForSize(const CupsPrinter& printer,
+                                  const gfx::Size& size_um);
+
 // Wraps `ipp` in unique_ptr with appropriate deleter
 COMPONENT_EXPORT(PRINT_BACKEND) ScopedIppPtr WrapIpp(ipp_t* ipp);
 
diff --git a/printing/backend/cups_ipp_helper_unittest.cc b/printing/backend/cups_ipp_helper_unittest.cc
index 5a6da71..348d305 100644
--- a/printing/backend/cups_ipp_helper_unittest.cc
+++ b/printing/backend/cups_ipp_helper_unittest.cc
@@ -35,13 +35,6 @@
   MockCupsPrinterWithMarginsAndAttributes() = default;
   ~MockCupsPrinterWithMarginsAndAttributes() override = default;
 
-  // CupsPrinter:
-  CupsMediaMargins GetMediaMarginsByName(
-      const std::string& media_id) const override {
-    const auto margins = margins_.find(media_id);
-    return margins != margins_.end() ? margins->second : CupsMediaMargins();
-  }
-
   // CupsOptionProvider:
   ipp_attribute_t* GetSupportedOptionValues(
       const char* option_name) const override {
@@ -78,17 +71,17 @@
   }
 
   // CupsOptionProvider:
+  ipp_attribute_t* GetMediaColDatabase() const override {
+    return media_col_database_;
+  }
+
+  // CupsOptionProvider:
   bool CheckOptionSupported(const char* name,
                             const char* value) const override {
     NOTREACHED();
     return false;
   }
 
-  void SetMediaMarginsByName(base::StringPiece media_id,
-                             const CupsMediaMargins& margins) {
-    margins_[media_id] = margins;
-  }
-
   void SetSupportedOptions(base::StringPiece name, ipp_attribute_t* attribute) {
     supported_attributes_[name] = attribute;
   }
@@ -97,10 +90,14 @@
     default_attributes_[name] = attribute;
   }
 
+  void SetMediaColDatabase(ipp_attribute_t* attribute) {
+    media_col_database_ = attribute;
+  }
+
  private:
   std::map<base::StringPiece, ipp_attribute_t*> supported_attributes_;
   std::map<base::StringPiece, ipp_attribute_t*> default_attributes_;
-  std::map<base::StringPiece, CupsMediaMargins> margins_;
+  ipp_attribute_t* media_col_database_;
 };
 
 class PrintBackendCupsIppHelperTest : public ::testing::Test {
@@ -145,12 +142,83 @@
                        strings.size(), nullptr, strings.data());
 }
 
+struct media_info {
+  int width;
+  int height;
+  int bottom_margin;
+  int left_margin;
+  int right_margin;
+  int top_margin;
+  std::map<const char*, const char*> keyword_attrs;
+  bool is_range;
+  int width_max;
+  int height_max;
+};
+
+ScopedIppPtr MakeMediaCol(const media_info& info) {
+  ScopedIppPtr media_col = WrapIpp(ippNew());
+  ScopedIppPtr media_size = WrapIpp(ippNew());
+
+  if (info.is_range) {
+    ippAddRange(media_size.get(), IPP_TAG_ZERO, "x-dimension", info.width,
+                info.width_max);
+    ippAddRange(media_size.get(), IPP_TAG_ZERO, "y-dimension", info.height,
+                info.height_max);
+  } else {
+    ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                  "x-dimension", info.width);
+    ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                  "y-dimension", info.height);
+  }
+
+  ippAddCollection(media_col.get(), IPP_TAG_ZERO, "media-size",
+                   media_size.get());
+
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                "media-bottom-margin", info.bottom_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                "media-left-margin", info.left_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                "media-right-margin", info.right_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                "media-top-margin", info.top_margin);
+
+  for (auto& it : info.keyword_attrs) {
+    ippAddString(media_col.get(), IPP_TAG_ZERO, IPP_TAG_KEYWORD, it.first,
+                 nullptr, it.second);
+  }
+
+  return media_col;
+}
+
+ipp_attribute_t* MakeMediaColDefault(ipp_t* ipp, const media_info& info) {
+  ScopedIppPtr media_col = MakeMediaCol(info);
+  return ippAddCollection(ipp, IPP_TAG_ZERO, "TEST_DATA", media_col.get());
+}
+
+ipp_attribute_t* MakeMediaColDatabase(ipp_t* ipp,
+                                      const std::vector<media_info>& media) {
+  std::vector<ScopedIppPtr> collections;
+  std::vector<const ipp_t*> raw_collections;
+
+  for (auto info : media) {
+    ScopedIppPtr entry = MakeMediaCol(info);
+    raw_collections.emplace_back(entry.get());
+    collections.emplace_back(std::move(entry));
+  }
+
+  return ippAddCollections(ipp, IPP_TAG_PRINTER, "TEST_DATA",
+                           raw_collections.size(), raw_collections.data());
+}
+
 TEST_F(PrintBackendCupsIppHelperTest, DefaultPaper) {
-  const CupsPrinter::CupsMediaMargins kMargins = {10, 10, 10, 10};
-  EXPECT_EQ(ParsePaper("", kMargins), DefaultPaper(*printer_));
-  printer_->SetOptionDefault("media", MakeString(ipp_, "iso_a4_210x297mm"));
-  printer_->SetMediaMarginsByName("iso_a4_210x297mm", kMargins);
-  EXPECT_EQ(ParsePaper("iso_a4_210x297mm", kMargins), DefaultPaper(*printer_));
+  EXPECT_EQ(PrinterSemanticCapsAndDefaults::Paper(), DefaultPaper(*printer_));
+  printer_->SetOptionDefault(
+      "media-col",
+      MakeMediaColDefault(ipp_, {21000, 29700, 10, 10, 10, 10, {}}));
+  PrinterSemanticCapsAndDefaults::Paper default_paper = DefaultPaper(*printer_);
+  EXPECT_EQ(default_paper.size_um.width(), 210000);
+  EXPECT_EQ(default_paper.size_um.height(), 297000);
 }
 
 TEST_F(PrintBackendCupsIppHelperTest, CopiesCapable) {
@@ -224,42 +292,47 @@
 }
 
 TEST_F(PrintBackendCupsIppHelperTest, A4PaperSupported) {
-  printer_->SetSupportedOptions(
-      "media", MakeStringCollection(ipp_, {"iso_a4_210x297mm"}));
+  printer_->SetMediaColDatabase(
+      MakeMediaColDatabase(ipp_, {{21000, 29700, 10, 10, 10, 10, {}}}));
 
   PrinterSemanticCapsAndDefaults caps;
   CapsAndDefaultsFromPrinter(*printer_, &caps);
 
   PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
-  // media display name localization is handled more fully in
-  // AssemblePrinterSettings().
-  EXPECT_EQ("iso a4", paper.display_name);
-  EXPECT_EQ("iso_a4_210x297mm", paper.vendor_id);
   EXPECT_EQ(210000, paper.size_um.width());
   EXPECT_EQ(297000, paper.size_um.height());
 }
 
 TEST_F(PrintBackendCupsIppHelperTest, LegalPaperDefault) {
-  printer_->SetOptionDefault("media", MakeString(ipp_, "na_legal_8.5x14in"));
+  // na_legal_8.5x14in
+  printer_->SetOptionDefault(
+      "media-col",
+      MakeMediaColDefault(ipp_, {21590, 35560, 10, 10, 10, 10, {}}));
 
   PrinterSemanticCapsAndDefaults caps;
   CapsAndDefaultsFromPrinter(*printer_, &caps);
-  // media display name localization is handled more fully in
-  // AssemblePrinterSettings().
-  EXPECT_EQ("na legal", caps.default_paper.display_name);
-  EXPECT_EQ("na_legal_8.5x14in", caps.default_paper.vendor_id);
   EXPECT_EQ(215900, caps.default_paper.size_um.width());
   EXPECT_EQ(355600, caps.default_paper.size_um.height());
 }
 
-// Tests that CapsAndDefaultsFromPrinter() does not propagate papers
-// with badly formatted vendor IDs - such papers will not transform into
-// meaningful ParsedPaper instances and are sometimes inimical to
-// ARC++.
-TEST_F(PrintBackendCupsIppHelperTest, OmitPapersWithoutVendorIds) {
-  printer_->SetSupportedOptions(
-      "media", MakeStringCollection(ipp_, {"jis_b5_182x257mm", "invalidsize",
-                                           "", "iso_b5_176x250mm"}));
+// Tests that CapsAndDefaultsFromPrinter() does not propagate papers with
+// invalid sizes or margins to the Chromium print backend.
+TEST_F(PrintBackendCupsIppHelperTest, OmitPapersWithInvalidSizes) {
+  printer_->SetMediaColDatabase(
+      MakeMediaColDatabase(ipp_, {
+                                     {18200, 25700, 100, 100, 100, 100, {}},
+                                     {0, 29700, 100, 100, 100, 100, {}},
+                                     {-1, 29700, 100, 100, 100, 100, {}},
+                                     {21000, 0, 100, 100, 100, 100, {}},
+                                     {21000, -1, 100, 100, 100, 100, {}},
+                                     {21000, 29700, -1, 100, 100, 100, {}},
+                                     {21000, 29700, 100, -1, 100, 100, {}},
+                                     {21000, 29700, 100, 100, -1, 100, {}},
+                                     {21000, 29700, 100, 100, 100, -1, {}},
+                                     {21000, 29700, 100, 10500, 10500, 100, {}},
+                                     {21000, 29700, 14850, 100, 100, 14850, {}},
+                                     {17600, 25000, 100, 100, 100, 100, {}},
+                                 }));
 
   PrinterSemanticCapsAndDefaults caps;
   CapsAndDefaultsFromPrinter(*printer_, &caps);
@@ -269,54 +342,179 @@
   // preceding call to CapsAndDefaultsFromPrinter() will have dropped
   // these invalid sizes.
   ASSERT_EQ(2U, caps.papers.size());
-
-  // While not directly pertinent to this test, we expect a certain
-  // format for the other supported papers.
-  EXPECT_THAT(
-      caps.papers,
-      testing::UnorderedElementsAre(
-          testing::Field(&PrinterSemanticCapsAndDefaults::Paper::display_name,
-                         "jis b5"),
-          testing::Field(&PrinterSemanticCapsAndDefaults::Paper::display_name,
-                         "iso b5")));
+  for (const auto& paper : caps.papers) {
+    EXPECT_NE(21000, paper.size_um.width());
+    EXPECT_NE(29700, paper.size_um.height());
+  }
 }
 
-// Tests that CapsAndDefaultsFromPrinter() does not propagate the
-// special IPP values that CUPS happens to expose to the Chromium print
-// backend.
-TEST_F(PrintBackendCupsIppHelperTest, OmitPapersWithSpecialVendorIds) {
-  // Maintainer's note: there's no reason why a printer would deliver
-  // two discrete sizes for custom_min* and custom_max*; in practice,
-  // we always see the fully qualified custom_m(in|ax)_<DIMENSIONS>
-  // delivered to the Chromium print backend.
-  printer_->SetSupportedOptions(
-      "media",
-      MakeStringCollection(
-          ipp_, {"na_number-11_4.5x10.375in", "custom_max", "custom_min_0x0in",
-                 "na_govt-letter_8x10in", "custom_min",
-                 "custom_max_1000x1000in", "iso_b0_1000x1414mm"}));
+// Tests that CapsAndDefaultsFromPrinter() does not propagate custom size ranges
+// from the media-col-database to the Chromium print backend.
+TEST_F(PrintBackendCupsIppHelperTest, OmitPapersWithSizeRanges) {
+  printer_->SetMediaColDatabase(MakeMediaColDatabase(
+      ipp_, {
+                {11430, 26352, 100, 100, 100, 100, {}},
+                {0, 0, 100, 100, 100, 100, {}, true, 2540000, 2540000},
+                {20320, 25400, 100, 100, 100, 100, {}},
+                {100000, 141400, 100, 100, 100, 100, {}},
+            }));
 
   PrinterSemanticCapsAndDefaults caps;
   CapsAndDefaultsFromPrinter(*printer_, &caps);
 
-  // The printer reports that it supports seven media sizes, four of
-  // which are not meant for users' eyes (``custom_min*'' and
-  // ``custom_max*''). The preceding call to
-  // CapsAndDefaultsFromPrinter() will have dropped these sizes,
-  // refusing to propagate them out of the backend.
+  // The printer reports that it supports four media sizes, one of which is not
+  // meant for users' eyes (the size range). The preceding call to
+  // CapsAndDefaultsFromPrinter() will have dropped these sizes, refusing to
+  // propagate them out of the backend.
   ASSERT_EQ(3U, caps.papers.size());
+}
 
-  // While not directly pertinent to this test, we expect a certain
-  // format for the other supported papers.
-  EXPECT_THAT(
-      caps.papers,
-      testing::UnorderedElementsAre(
-          testing::Field(&PrinterSemanticCapsAndDefaults::Paper::display_name,
-                         "na number-11"),
-          testing::Field(&PrinterSemanticCapsAndDefaults::Paper::display_name,
-                         "na govt-letter"),
-          testing::Field(&PrinterSemanticCapsAndDefaults::Paper::display_name,
-                         "iso b0")));
+// Tests that when the media-col-database contains both bordered and borderless
+// versions of a size, CapsAndDefaultsFromPrinter() takes the bordered version
+// and drops the borderless version.
+TEST_F(PrintBackendCupsIppHelperTest, PreferBorderedSizes) {
+  PrinterSemanticCapsAndDefaults caps;
+
+  printer_->SetMediaColDatabase(
+      MakeMediaColDatabase(ipp_, {
+                                     {21000, 29700, 100, 100, 100, 100, {}},
+                                     {21000, 29700, 0, 0, 0, 0, {}},
+                                 }));
+  CapsAndDefaultsFromPrinter(*printer_, &caps);
+  ASSERT_EQ(1U, caps.papers.size());
+  EXPECT_NE(gfx::Rect(0, 0, 210000, 297000), caps.papers[0].printable_area_um);
+
+  printer_->SetMediaColDatabase(
+      MakeMediaColDatabase(ipp_, {
+                                     {21000, 29700, 0, 0, 0, 0, {}},
+                                     {21000, 29700, 100, 100, 100, 100, {}},
+                                 }));
+  CapsAndDefaultsFromPrinter(*printer_, &caps);
+  ASSERT_EQ(1U, caps.papers.size());
+  EXPECT_NE(gfx::Rect(0, 0, 210000, 297000), caps.papers[0].printable_area_um);
+
+  // If the only available version of a size is borderless, go ahead and use it.
+  // Not sure if any actual printers do this, but it's allowed by the IPP spec.
+  printer_->SetMediaColDatabase(
+      MakeMediaColDatabase(ipp_, {
+                                     {21000, 29700, 0, 0, 0, 0, {}},
+                                 }));
+  CapsAndDefaultsFromPrinter(*printer_, &caps);
+  ASSERT_EQ(1U, caps.papers.size());
+  EXPECT_EQ(gfx::Rect(0, 0, 210000, 297000), caps.papers[0].printable_area_um);
+}
+
+// At the time of this writing, there are no media-source or media-type
+// attributes in the media-col-database that cupsd gives us. However, according
+// to the IPP spec, each paper size *should* have a separate variant for each
+// supported combination of size and type. So make sure behavior doesn't change
+// and we don't create duplicate paper sizes when/if CUPS improves in the
+// future.
+TEST_F(PrintBackendCupsIppHelperTest, NoDuplicateSizes) {
+  printer_->SetMediaColDatabase(MakeMediaColDatabase(
+      ipp_,
+      {
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "stationery"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "stationery"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           500,
+           500,
+           500,
+           500,
+           {{"media-type", "stationery"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "photographic"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           0,
+           0,
+           0,
+           0,
+           {{"media-type", "photographic"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "photographic-high-gloss"},
+            {"media-source", "main"}}},
+          {21000,
+           29700,
+           0,
+           0,
+           0,
+           0,
+           {{"media-type", "photographic-high-gloss"},
+            {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "photographic-glossy"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           0,
+           0,
+           0,
+           0,
+           {{"media-type", "photographic-glossy"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "photographic-semi-gloss"},
+            {"media-source", "main"}}},
+          {21000,
+           29700,
+           0,
+           0,
+           0,
+           0,
+           {{"media-type", "photographic-semi-gloss"},
+            {"media-source", "main"}}},
+          {21000,
+           29700,
+           300,
+           300,
+           300,
+           300,
+           {{"media-type", "photographic-matte"}, {"media-source", "main"}}},
+          {21000,
+           29700,
+           0,
+           0,
+           0,
+           0,
+           {{"media-type", "photographic-matte"}, {"media-source", "main"}}},
+      }));
+
+  PrinterSemanticCapsAndDefaults caps;
+  CapsAndDefaultsFromPrinter(*printer_, &caps);
+
+  ASSERT_EQ(1U, caps.papers.size());
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/printing/backend/cups_printer.cc b/printing/backend/cups_printer.cc
index dc2d4d2..2fb47dc 100644
--- a/printing/backend/cups_printer.cc
+++ b/printing/backend/cups_printer.cc
@@ -26,7 +26,9 @@
 class CupsPrinterImpl : public CupsPrinter {
  public:
   CupsPrinterImpl(http_t* http, ScopedDestination dest)
-      : cups_http_(http), destination_(std::move(dest)) {
+      : cups_http_(http),
+        destination_(std::move(dest)),
+        printer_attributes_(WrapIpp(nullptr)) {
     DCHECK(cups_http_);
     DCHECK(destination_);
 
@@ -117,6 +119,16 @@
     return supported == 1;
   }
 
+  // CupsOptionProvider
+  ipp_attribute_t* GetMediaColDatabase() const override {
+    if (!EnsurePrinterAttributes()) {
+      return nullptr;
+    }
+
+    return ippFindAttribute(printer_attributes_.get(), kIppMediaColDatabase,
+                            IPP_TAG_BEGIN_COLLECTION);
+  }
+
   bool ToPrinterInfo(PrinterBasicInfo* printer_info) const override {
     const cups_dest_t* printer = destination_.get();
 
@@ -275,22 +287,37 @@
     return status == IPP_STATUS_OK;
   }
 
-  CupsMediaMargins GetMediaMarginsByName(
-      const std::string& media_id) const override {
-    cups_size_t cups_media;
-    if (!EnsureDestInfo() ||
-        !cupsGetDestMediaByName(cups_http_, destination_.get(),
-                                dest_info_.get(), media_id.c_str(),
-                                CUPS_MEDIA_FLAGS_DEFAULT, &cups_media)) {
-      return {0, 0, 0, 0};
+ private:
+  // Sends the request to populate `printer_attributes_` if it's not already
+  // populated.
+  bool EnsurePrinterAttributes() const {
+    if (printer_attributes_) {
+      return true;
     }
-    return {cups_media.bottom, cups_media.left, cups_media.right,
-            cups_media.top};
+
+    ScopedIppPtr request = CreateRequest(IPP_OP_GET_PRINTER_ATTRIBUTES, "");
+    // The requested attributes can be changed to "all","media-col-database" if
+    // we want to directly query printer attributes other than
+    // media-col-database in the future.
+    constexpr const char* kRequestedAttributes[] = {kIppMediaColDatabase};
+    ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                  kIppRequestedAttributes, std::size(kRequestedAttributes),
+                  nullptr, kRequestedAttributes);
+
+    // cupsDoRequest() takes ownership of the request and frees it for us.
+    printer_attributes_.reset(
+        cupsDoRequest(cups_http_, request.release(), resource_path_.c_str()));
+
+    if (ippGetStatusCode(printer_attributes_.get()) != IPP_STATUS_OK) {
+      printer_attributes_.reset();
+      return false;
+    }
+
+    return true;
   }
 
- private:
   // internal helper function to initialize an IPP request
-  ScopedIppPtr CreateRequest(ipp_op_t op, const std::string& username) {
+  ScopedIppPtr CreateRequest(ipp_op_t op, const std::string& username) const {
     const char* c_username = username.empty() ? cupsUser() : username.c_str();
 
     ipp_t* request = ippNewRequest(op);
@@ -303,7 +330,9 @@
   }
 
   // internal helper function to copy attributes to an IPP request
-  void CopyAttributeGroup(ipp_t* request, ipp_t* attributes, ipp_tag_t group) {
+  void CopyAttributeGroup(ipp_t* request,
+                          ipp_t* attributes,
+                          ipp_tag_t group) const {
     for (ipp_attribute_t* attr = ippFirstAttribute(attributes); attr;
          attr = ippNextAttribute(attributes)) {
       if (ippGetGroupTag(attr) == group) {
@@ -326,6 +355,9 @@
 
   // resource path used to connect to this printer
   std::string resource_path_;
+
+  // printer attributes that describe the supported options
+  mutable ScopedIppPtr printer_attributes_;
 };
 
 std::unique_ptr<CupsPrinter> CupsPrinter::Create(http_t* http,
diff --git a/printing/backend/cups_printer.h b/printing/backend/cups_printer.h
index b273b529..708344c 100644
--- a/printing/backend/cups_printer.h
+++ b/printing/backend/cups_printer.h
@@ -42,6 +42,10 @@
   // Returns true if the `value` is supported by option `name`.
   virtual bool CheckOptionSupported(const char* name,
                                     const char* value) const = 0;
+
+  // Returns the IPP "media-col-database" attribute for this printer.
+  // ipp_attribute_t* is owned by CupsOptionProvider.
+  virtual ipp_attribute_t* GetMediaColDatabase() const = 0;
 };
 
 // Represents a CUPS printer.
@@ -50,18 +54,6 @@
 // share an http connection which the CupsConnection closes on destruction.
 class COMPONENT_EXPORT(PRINT_BACKEND) CupsPrinter : public CupsOptionProvider {
  public:
-  // Represents the margins that CUPS reports for some given media.
-  // Its members are valued in PWG units (100ths of mm).
-  // This struct approximates a cups_size_t, which is BLRT.
-  // `bottom`, `left`, `right`, and `top` express inward encroachment by
-  // margins, away from the edges of the paper.
-  struct CupsMediaMargins {
-    int bottom;
-    int left;
-    int right;
-    int top;
-  };
-
   ~CupsPrinter() override = default;
 
   // Create a printer with a connection defined by `http` and `dest`.
@@ -124,16 +116,6 @@
   // Cancel the print job `job_id`.  Returns true if the operation succeeded.
   // Returns false if it failed for any reason.
   virtual bool CancelJob(int job_id) = 0;
-
-  // Queries CUPS for the margins of the media named by `media_id`.
-  //
-  // A `media_id` is any vendor ID known to CUPS for a given printer.
-  // Vendor IDs are exemplified by the keys of the big map in
-  // print_media_l10n.cc.
-  //
-  // Returns all zeroes if the CUPS API call fails.
-  virtual CupsMediaMargins GetMediaMarginsByName(
-      const std::string& media_id) const = 0;
 };
 
 }  // namespace printing
diff --git a/printing/backend/mock_cups_printer.h b/printing/backend/mock_cups_printer.h
index 504d97a..0479f79 100644
--- a/printing/backend/mock_cups_printer.h
+++ b/printing/backend/mock_cups_printer.h
@@ -38,13 +38,12 @@
   MOCK_METHOD0(FinishDocument, bool());
   MOCK_METHOD2(CloseJob, ipp_status_t(int job_id, const std::string& username));
   MOCK_METHOD1(CancelJob, bool(int job_id));
-  MOCK_CONST_METHOD1(GetMediaMarginsByName,
-                     CupsMediaMargins(const std::string& media_id));
 
   MOCK_CONST_METHOD1(GetSupportedOptionValues,
                      ipp_attribute_t*(const char* option_name));
   MOCK_CONST_METHOD1(GetSupportedOptionValueStrings,
                      std::vector<base::StringPiece>(const char* option_name));
+  MOCK_CONST_METHOD0(GetMediaColDatabase, ipp_attribute_t*());
   MOCK_CONST_METHOD1(GetDefaultOptionValue,
                      ipp_attribute_t*(const char* option_name));
   MOCK_CONST_METHOD2(CheckOptionSupported,
diff --git a/printing/backend/print_backend_utils.cc b/printing/backend/print_backend_utils.cc
index 549e30a..1f6a059 100644
--- a/printing/backend/print_backend_utils.cc
+++ b/printing/backend/print_backend_utils.cc
@@ -106,52 +106,49 @@
 }
 
 #if BUILDFLAG(USE_CUPS)
-PrinterSemanticCapsAndDefaults::Paper ParsePaper(
-    base::StringPiece value,
-    const CupsPrinter::CupsMediaMargins& margins) {
-  std::vector<base::StringPiece> pieces = GetStringPiecesIfValid(value);
-  if (pieces.empty()) {
-    return PrinterSemanticCapsAndDefaults::Paper();
-  }
-
-  base::StringPiece dimensions = pieces.back();
-
-  PrinterSemanticCapsAndDefaults::Paper paper;
-  paper.vendor_id = std::string(value);
-  paper.size_um = DimensionsToMicrons(dimensions);
-  if (paper.size_um.IsEmpty()) {
-    return PrinterSemanticCapsAndDefaults::Paper();
-  }
-
+gfx::Rect PrintableAreaFromSizeAndPwgMargins(const gfx::Size& size_um,
+                                             int bottom_pwg,
+                                             int left_pwg,
+                                             int right_pwg,
+                                             int top_pwg) {
   // The margins of the printable area are expressed in PWG units (100ths of
-  // mm).
-  int printable_area_left_um = margins.left * kMicronsPerPwgUnit;
-  int printable_area_bottom_um = margins.bottom * kMicronsPerPwgUnit;
+  // mm) in the IPP 'media-col-database' attribute.
+  int printable_area_left_um = left_pwg * kMicronsPerPwgUnit;
+  int printable_area_bottom_um = bottom_pwg * kMicronsPerPwgUnit;
   int printable_area_width_um =
-      paper.size_um.width() -
-      ((margins.left + margins.right) * kMicronsPerPwgUnit);
-  int printable_area_length_um =
-      paper.size_um.height() -
-      ((margins.top + margins.bottom) * kMicronsPerPwgUnit);
-  paper.printable_area_um =
-      gfx::Rect(printable_area_left_um, printable_area_bottom_um,
-                printable_area_width_um, printable_area_length_um);
+      size_um.width() - ((left_pwg + right_pwg) * kMicronsPerPwgUnit);
+  int printable_area_height_um =
+      size_um.height() - ((top_pwg + bottom_pwg) * kMicronsPerPwgUnit);
+  return gfx::Rect(printable_area_left_um, printable_area_bottom_um,
+                   printable_area_width_um, printable_area_height_um);
+}
 
-  // Default to the paper size if printable area is empty.
-  // We've seen some drivers have a printable area that goes out of bounds
-  // of the paper size. In those cases, set the printable area to be the
-  // size. (See crbug.com/1412305.)
-  const gfx::Rect size_um_rect = gfx::Rect(paper.size_um);
-  if (paper.printable_area_um.IsEmpty() ||
-      !size_um_rect.Contains(paper.printable_area_um)) {
-    paper.printable_area_um = size_um_rect;
-  }
+void PwgMarginsFromSizeAndPrintableArea(const gfx::Size& size_um,
+                                        const gfx::Rect& printable_area_um,
+                                        int* bottom_pwg,
+                                        int* left_pwg,
+                                        int* right_pwg,
+                                        int* top_pwg) {
+  DCHECK(bottom_pwg);
+  DCHECK(left_pwg);
+  DCHECK(right_pwg);
+  DCHECK(top_pwg);
 
-  // Omits the final token describing the media dimensions.
-  pieces.pop_back();
-  paper.display_name = base::JoinString(pieces, " ");
+  // These values in microns were obtained in the first place by converting
+  // from PWG units, so we can losslessly convert them back.
+  int bottom_um = printable_area_um.y();
+  int left_um = printable_area_um.x();
+  int right_um = size_um.width() - printable_area_um.right();
+  int top_um = size_um.height() - printable_area_um.bottom();
+  DCHECK_EQ(bottom_um % kMicronsPerPwgUnit, 0);
+  DCHECK_EQ(left_um % kMicronsPerPwgUnit, 0);
+  DCHECK_EQ(right_um % kMicronsPerPwgUnit, 0);
+  DCHECK_EQ(top_um % kMicronsPerPwgUnit, 0);
 
-  return paper;
+  *bottom_pwg = bottom_um / kMicronsPerPwgUnit;
+  *left_pwg = left_um / kMicronsPerPwgUnit;
+  *right_pwg = right_um / kMicronsPerPwgUnit;
+  *top_pwg = top_um / kMicronsPerPwgUnit;
 }
 #endif  // BUILDFLAG(USE_CUPS)
 
diff --git a/printing/backend/print_backend_utils.h b/printing/backend/print_backend_utils.h
index a879a3c..2c4b66ab 100644
--- a/printing/backend/print_backend_utils.h
+++ b/printing/backend/print_backend_utils.h
@@ -33,18 +33,26 @@
 gfx::Size ParsePaperSize(base::StringPiece value);
 
 #if BUILDFLAG(USE_CUPS)
-// Parses the media name expressed by `value` into a Paper. Returns an
-// empty Paper if `value` does not contain the display name nor the dimension,
-// `value` contains a prefix of media sizes not meant for users' eyes, or if the
-// paper size is empty.
-// `margins` is used to calculate the Paper's printable area.
-// We don't handle l10n here. We do populate the display_name member with the
-// prettified vendor ID, but fully expect the caller to clobber this if a better
-// localization exists.
+// Calculates a paper's printable area in microns from its size in microns and
+// its four margins in PWG units.
 COMPONENT_EXPORT(PRINT_BACKEND)
-PrinterSemanticCapsAndDefaults::Paper ParsePaper(
-    base::StringPiece value,
-    const CupsPrinter::CupsMediaMargins& margins);
+gfx::Rect PrintableAreaFromSizeAndPwgMargins(const gfx::Size& size_um,
+                                             int bottom_pwg,
+                                             int left_pwg,
+                                             int right_pwg,
+                                             int top_pwg);
+
+// Calculates a paper's four margins in PWG units from its size and printable
+// area in microns. Since the size and printable area were converted from PWG
+// units in the first place, the margins in PWG units can be reconstructed
+// losslessly.
+COMPONENT_EXPORT(PRINT_BACKEND)
+void PwgMarginsFromSizeAndPrintableArea(const gfx::Size& size_um,
+                                        const gfx::Rect& printable_area_um,
+                                        int* bottom_pwg,
+                                        int* left_pwg,
+                                        int* right_pwg,
+                                        int* top_pwg);
 #endif  // BUILDFLAG(USE_CUPS)
 
 }  // namespace printing
diff --git a/printing/backend/print_backend_utils_unittest.cc b/printing/backend/print_backend_utils_unittest.cc
index 07057c3..ee0bb7d 100644
--- a/printing/backend/print_backend_utils_unittest.cc
+++ b/printing/backend/print_backend_utils_unittest.cc
@@ -56,93 +56,27 @@
 
 #if BUILDFLAG(USE_CUPS)
 
-TEST(PrintBackendUtilsCupsTest, ParsePaperA4) {
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {500, 500, 500, 500};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("iso_a4_210x297mm", kMargins);
-  EXPECT_EQ(gfx::Size(210000, 297000), paper.size_um);
-  EXPECT_EQ("iso_a4_210x297mm", paper.vendor_id);
-  EXPECT_EQ("iso a4", paper.display_name);
-  EXPECT_EQ(gfx::Rect(5000, 5000, 200000, 287000), paper.printable_area_um);
+TEST(PrintBackendUtilsCupsTest, PrintableAreaFromMarginsA4) {
+  // margins in PWG units (1 PWG unit = 1/100 mm = 10 um)
+  int bottom = 100;
+  int left = 200;
+  int right = 300;
+  int top = 400;
+  gfx::Size size_um = {210000, 297000};
+  gfx::Rect printable_area_um =
+      PrintableAreaFromSizeAndPwgMargins(size_um, bottom, left, right, top);
+  EXPECT_EQ(gfx::Rect(2000, 1000, 205000, 292000), printable_area_um);
 }
 
-TEST(PrintBackendUtilsCupsTest, ParsePaperNaLetter) {
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {500, 500, 500, 500};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("na_letter_8.5x11in", kMargins);
-  EXPECT_EQ(gfx::Size(215900, 279400), paper.size_um);
-  EXPECT_EQ("na_letter_8.5x11in", paper.vendor_id);
-  EXPECT_EQ("na letter", paper.display_name);
-  EXPECT_EQ(gfx::Rect(5000, 5000, 205900, 269400), paper.printable_area_um);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperNaIndex4x6) {
-  // Note that "na_index-4x6_4x6in" has a dimension within the media name. Test
-  // that parsing is not affected.
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {500, 500, 500, 500};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("na_index-4x6_4x6in", kMargins);
-  EXPECT_EQ(gfx::Size(101600, 152400), paper.size_um);
-  EXPECT_EQ("na_index-4x6_4x6in", paper.vendor_id);
-  EXPECT_EQ("na index-4x6", paper.display_name);
-  EXPECT_EQ(gfx::Rect(5000, 5000, 91600, 142400), paper.printable_area_um);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperNaNumber10) {
-  // Test that a paper size with a fractional dimension is not affected by
-  // rounding errors.
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {1000, 1000, 1000, 1000};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("na_number-10_4.125x9.5in", kMargins);
-  EXPECT_EQ(gfx::Size(104775, 241300), paper.size_um);
-  EXPECT_EQ("na_number-10_4.125x9.5in", paper.vendor_id);
-  EXPECT_EQ("na number-10", paper.display_name);
-  EXPECT_EQ(gfx::Rect(10000, 10000, 84775, 221300), paper.printable_area_um);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperBadUnit) {
-  PrinterSemanticCapsAndDefaults::Paper paper_bad =
-      ParsePaper("bad_unit_666x666bad", CupsPrinter::CupsMediaMargins());
-  EXPECT_EQ(PrinterSemanticCapsAndDefaults::Paper(), paper_bad);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperBadOneDimension) {
-  PrinterSemanticCapsAndDefaults::Paper paper_bad =
-      ParsePaper("bad_one_dimension_666mm", CupsPrinter::CupsMediaMargins());
-  EXPECT_EQ(PrinterSemanticCapsAndDefaults::Paper(), paper_bad);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperOutOfBoundsMargins) {
-  // Given invalid margins, the printable area cannot be calculated correctly.
-  // The printable area should be set to the paper size as default.
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {100, 100, 300000, 100};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("iso_a4_210x297mm", kMargins);
-  EXPECT_EQ(gfx::Size(210000, 297000), paper.size_um);
-  EXPECT_EQ("iso_a4_210x297mm", paper.vendor_id);
-  EXPECT_EQ("iso a4", paper.display_name);
-  EXPECT_EQ(gfx::Rect(0, 0, 210000, 297000), paper.printable_area_um);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperEmptyPrintableArea) {
-  // If the printable area is empty, the printable area should be set to the
-  // paper size.
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {29700, 0, 0, 0};
-  PrinterSemanticCapsAndDefaults::Paper paper =
-      ParsePaper("iso_a4_210x297mm", kMargins);
-  EXPECT_EQ(gfx::Size(210000, 297000), paper.size_um);
-  EXPECT_EQ("iso_a4_210x297mm", paper.vendor_id);
-  EXPECT_EQ("iso a4", paper.display_name);
-  EXPECT_EQ(gfx::Rect(0, 0, 210000, 297000), paper.printable_area_um);
-}
-
-TEST(PrintBackendUtilsCupsTest, ParsePaperEmptySizeWithPrintableArea) {
-  // If the paper size is empty, the Paper should be invalid, even when provided
-  // a printable area.
-  constexpr CupsPrinter::CupsMediaMargins kMargins = {1000, 1000, 1000, 1000};
-  PrinterSemanticCapsAndDefaults::Paper paper_bad =
-      ParsePaper("bad_unit_666x666bad", kMargins);
-  EXPECT_EQ(PrinterSemanticCapsAndDefaults::Paper(), paper_bad);
+TEST(PrintBackendUtilsCupsTest, MarginsFromPrintableAreaA4) {
+  int bottom, left, right, top;
+  PwgMarginsFromSizeAndPrintableArea({210000, 297000},
+                                     {2000, 1000, 205000, 292000}, &bottom,
+                                     &left, &right, &top);
+  EXPECT_EQ(100, bottom);
+  EXPECT_EQ(200, left);
+  EXPECT_EQ(300, right);
+  EXPECT_EQ(400, top);
 }
 
 #endif  // BUILDFLAG(USE_CUPS)
diff --git a/printing/printing_context_chromeos.cc b/printing/printing_context_chromeos.cc
index 68b0f869..f288d189 100644
--- a/printing/printing_context_chromeos.cc
+++ b/printing/printing_context_chromeos.cc
@@ -22,6 +22,7 @@
 #include "printing/backend/cups_ipp_constants.h"
 #include "printing/backend/cups_ipp_helper.h"
 #include "printing/backend/cups_printer.h"
+#include "printing/backend/print_backend_utils.h"
 #include "printing/buildflags/buildflags.h"
 #include "printing/client_info_helpers.h"
 #include "printing/metafile.h"
@@ -96,48 +97,53 @@
                     raw_option_values.size(), raw_option_values.data());
 }
 
+// Construct the IPP media-col attribute specifying media size, margins, source,
+// etc., and add it to 'options'.
+void EncodeMediaCol(ipp_t* options,
+                    const gfx::Size& size_um,
+                    const gfx::Rect& printable_area_um,
+                    const std::string& source) {
+  // The size and printable area in microns were calculated from the size and
+  // margins in PWG units, so we can losslessly convert them back.
+  DCHECK_EQ(size_um.width() % kMicronsPerPwgUnit, 0);
+  DCHECK_EQ(size_um.height() % kMicronsPerPwgUnit, 0);
+  int width = size_um.width() / kMicronsPerPwgUnit;
+  int height = size_um.height() / kMicronsPerPwgUnit;
+  int bottom_margin = 0, left_margin = 0, right_margin = 0, top_margin = 0;
+  PwgMarginsFromSizeAndPrintableArea(size_um, printable_area_um, &bottom_margin,
+                                     &left_margin, &right_margin, &top_margin);
+
+  ScopedIppPtr media_col = WrapIpp(ippNew());
+  ScopedIppPtr media_size = WrapIpp(ippNew());
+  ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER, kIppXDimension,
+                width);
+  ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER, kIppYDimension,
+                height);
+  ippAddCollection(media_col.get(), IPP_TAG_ZERO, kIppMediaSize,
+                   media_size.get());
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                kIppMediaBottomMargin, bottom_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                kIppMediaLeftMargin, left_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                kIppMediaRightMargin, right_margin);
+  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                kIppMediaTopMargin, top_margin);
+  if (!source.empty()) {
+    ippAddString(media_col.get(), IPP_TAG_ZERO, IPP_TAG_KEYWORD,
+                 kIppMediaSource, nullptr, source.c_str());
+  }
+
+  ippAddCollection(options, IPP_TAG_JOB, kIppMediaCol, media_col.get());
+}
+
 std::string GetCollateString(bool collate) {
   return collate ? kCollated : kUncollated;
 }
 
-// Given an integral `value` expressed in PWG units (1/100 mm), returns
-// the same value expressed in device units.
-int PwgUnitsToDeviceUnits(int value, float micrometers_per_device_unit) {
-  return ConvertUnitFloat(value, micrometers_per_device_unit, 10);
-}
-
-// Given a `media_size`, the specification of the media's `margins`, and
-// the number of micrometers per device unit, returns the rectangle
-// bounding the apparent printable area of said media.
-gfx::Rect RepresentPrintableArea(const gfx::Size& media_size,
-                                 const CupsPrinter::CupsMediaMargins& margins,
-                                 float micrometers_per_device_unit) {
-  // These values express inward encroachment by margins, away from the
-  // edges of the `media_size`.
-  int left_bound =
-      PwgUnitsToDeviceUnits(margins.left, micrometers_per_device_unit);
-  int bottom_bound =
-      PwgUnitsToDeviceUnits(margins.bottom, micrometers_per_device_unit);
-  int right_bound =
-      PwgUnitsToDeviceUnits(margins.right, micrometers_per_device_unit);
-  int top_bound =
-      PwgUnitsToDeviceUnits(margins.top, micrometers_per_device_unit);
-
-  // These values express the bounding box of the printable area on the
-  // page.
-  int printable_width = media_size.width() - (left_bound + right_bound);
-  int printable_height = media_size.height() - (top_bound + bottom_bound);
-
-  if (printable_width > 0 && printable_height > 0) {
-    return {left_bound, bottom_bound, printable_width, printable_height};
-  }
-
-  return {0, 0, media_size.width(), media_size.height()};
-}
-
 void SetPrintableArea(PrintSettings* settings,
                       const PrintSettings::RequestedMedia& media,
-                      const CupsPrinter::CupsMediaMargins& margins) {
+                      const gfx::Rect& printable_area_um) {
   if (!media.size_microns.IsEmpty()) {
     float device_microns_per_device_unit =
         static_cast<float>(kMicronsPerInch) / settings->device_units_per_inch();
@@ -145,8 +151,11 @@
         gfx::Size(media.size_microns.width() / device_microns_per_device_unit,
                   media.size_microns.height() / device_microns_per_device_unit);
 
-    gfx::Rect paper_rect = RepresentPrintableArea(
-        paper_size, margins, device_microns_per_device_unit);
+    gfx::Rect paper_rect =
+        gfx::Rect(printable_area_um.x() / device_microns_per_device_unit,
+                  printable_area_um.y() / device_microns_per_device_unit,
+                  printable_area_um.width() / device_microns_per_device_unit,
+                  printable_area_um.height() / device_microns_per_device_unit);
     settings->SetPrinterPrintableArea(paper_size, paper_rect,
                                       /*landscape_needs_flip=*/true);
   }
@@ -154,7 +163,8 @@
 
 }  // namespace
 
-ScopedIppPtr SettingsToIPPOptions(const PrintSettings& settings) {
+ScopedIppPtr SettingsToIPPOptions(const PrintSettings& settings,
+                                  const gfx::Rect& printable_area_um) {
   ScopedIppPtr scoped_options = WrapIpp(ippNew());
   ipp_t* options = scoped_options.get();
 
@@ -179,9 +189,6 @@
   // color
   ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, kIppColor, nullptr,
                GetIppColorModelForModel(settings.color()).c_str());
-  // paper size
-  ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, kIppMedia, nullptr,
-               settings.requested_media().vendor_id.c_str());
   // copies
   ippAddInteger(options, IPP_TAG_JOB, IPP_TAG_INTEGER, kIppCopies,
                 settings.copies());
@@ -203,11 +210,17 @@
   }
 
   std::map<std::string, std::vector<int>> multival;
+  std::string media_source;
   for (const auto& setting : settings.advanced_settings()) {
     const std::string& key = setting.first;
     const std::string& value = setting.second.GetString();
-    if (value.empty())
+    if (value.empty()) {
       continue;
+    }
+    if (key == kIppMediaSource) {
+      media_source = value;
+      continue;
+    }
 
     // Check for multivalue enum ("attribute/value").
     size_t pos = key.find('/');
@@ -227,6 +240,11 @@
     }
   }
 
+  // Construct the IPP media-col attribute specifying media size, margins,
+  // source, etc.
+  EncodeMediaCol(options, settings.requested_media().size_microns,
+                 printable_area_um, media_source);
+
   // Add multivalue enum options.
   for (const auto& it : multival) {
     ippAddIntegers(options, IPP_TAG_JOB, IPP_TAG_ENUM, it.first.c_str(),
@@ -325,10 +343,7 @@
   media.vendor_id = paper.vendor_id;
   media.size_microns = paper.size_um;
   settings_->set_requested_media(media);
-
-  CupsPrinter::CupsMediaMargins margins =
-      printer_->GetMediaMarginsByName(paper.vendor_id);
-  SetPrintableArea(settings_.get(), media, margins);
+  SetPrintableArea(settings_.get(), media, paper.printable_area_um);
 
   return mojom::ResultCode::kSuccess;
 }
@@ -387,10 +402,10 @@
     settings_->set_requested_media(media);
   }
 
-  CupsPrinter::CupsMediaMargins margins =
-      printer_->GetMediaMarginsByName(media.vendor_id);
-  SetPrintableArea(settings_.get(), media, margins);
-  ipp_options_ = SettingsToIPPOptions(*settings_);
+  gfx::Rect printable_area_um =
+      GetPrintableAreaForSize(*printer_, media.size_microns);
+  SetPrintableArea(settings_.get(), media, printable_area_um);
+  ipp_options_ = SettingsToIPPOptions(*settings_, printable_area_um);
   send_user_info_ = settings_->send_user_info();
   if (send_user_info_) {
     DCHECK(printer_);
diff --git a/printing/printing_context_chromeos.h b/printing/printing_context_chromeos.h
index 4950f9f..40940114 100644
--- a/printing/printing_context_chromeos.h
+++ b/printing/printing_context_chromeos.h
@@ -67,7 +67,8 @@
 // This has the side effect of recording UMA for advanced attributes usage,
 // so only call once per job.
 COMPONENT_EXPORT(PRINTING)
-ScopedIppPtr SettingsToIPPOptions(const PrintSettings& settings);
+ScopedIppPtr SettingsToIPPOptions(const PrintSettings& settings,
+                                  const gfx::Rect& printable_area_um);
 
 }  // namespace printing
 
diff --git a/printing/printing_context_chromeos_unittest.cc b/printing/printing_context_chromeos_unittest.cc
index 7654413..d45de7d2 100644
--- a/printing/printing_context_chromeos_unittest.cc
+++ b/printing/printing_context_chromeos_unittest.cc
@@ -35,6 +35,9 @@
 constexpr char kDocumentName[] = "document name";
 constexpr char16_t kDocumentName16[] = u"document name";
 
+constexpr gfx::Size kDefaultPaperSize = {215900, 279400};
+constexpr char kDefaultPaperName[] = "some_vendor_id";
+
 class MockCupsConnection : public CupsConnection {
  public:
   MOCK_METHOD1(GetDests, bool(std::vector<std::unique_ptr<CupsPrinter>>&));
@@ -76,6 +79,7 @@
     settings->set_duplex_mode(mojom::DuplexMode::kLongEdge);
     settings->set_username(kUsername);
     printing_context_->UpdatePrintSettingsFromPOD(std::move(settings));
+    settings_.set_requested_media({kDefaultPaperSize, kDefaultPaperName});
   }
 
   ipp_attribute_t* GetAttribute(ipp_t* attributes,
@@ -97,20 +101,20 @@
 
   void TestStringOptionValue(const char* attr_name,
                              const char* expected_value) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     auto* attr = GetAttribute(attributes.get(), attr_name);
     EXPECT_STREQ(expected_value, ippGetString(attr, 0, nullptr));
   }
 
   void TestIntegerOptionValue(const char* attr_name, int expected_value) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     auto* attr = GetAttribute(attributes.get(), attr_name);
     EXPECT_EQ(expected_value, ippGetInteger(attr, 0));
   }
 
   void TestOctetStringOptionValue(const char* attr_name,
                                   base::span<const char> expected_value) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     auto* attr = GetAttribute(attributes.get(), attr_name);
     int length;
     void* value = ippGetOctetString(attr, 0, &length);
@@ -122,7 +126,7 @@
   void TestResolutionOptionValue(const char* attr_name,
                                  int expected_x_res,
                                  int expected_y_res) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     auto* attr = GetAttribute(attributes.get(), attr_name);
     ipp_res_t unit;
     int y_res;
@@ -132,18 +136,47 @@
     EXPECT_EQ(expected_y_res, y_res);
   }
 
+  void TestMediaColValue(const gfx::Size& expected_size,
+                         int expected_bottom_margin,
+                         int expected_left_margin,
+                         int expected_right_margin,
+                         int expected_top_margin) {
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
+    ipp_t* media_col =
+        ippGetCollection(GetAttribute(attributes.get(), kIppMediaCol), 0);
+    ipp_t* media_size =
+        ippGetCollection(GetAttribute(media_col, kIppMediaSize), 0);
+
+    int width = ippGetInteger(GetAttribute(media_size, kIppXDimension), 0);
+    int height = ippGetInteger(GetAttribute(media_size, kIppYDimension), 0);
+    EXPECT_EQ(expected_size.width(), width);
+    EXPECT_EQ(expected_size.height(), height);
+
+    int bottom =
+        ippGetInteger(GetAttribute(media_col, kIppMediaBottomMargin), 0);
+    int left = ippGetInteger(GetAttribute(media_col, kIppMediaLeftMargin), 0);
+    int right = ippGetInteger(GetAttribute(media_col, kIppMediaRightMargin), 0);
+    int top = ippGetInteger(GetAttribute(media_col, kIppMediaTopMargin), 0);
+
+    EXPECT_EQ(expected_bottom_margin, bottom);
+    EXPECT_EQ(expected_left_margin, left);
+    EXPECT_EQ(expected_right_margin, right);
+    EXPECT_EQ(expected_top_margin, top);
+  }
+
   bool HasAttribute(const char* attr_name) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     return !!ippFindAttribute(attributes.get(), attr_name, IPP_TAG_ZERO);
   }
 
   int GetAttrValueCount(const char* attr_name) const {
-    auto attributes = SettingsToIPPOptions(settings_);
+    auto attributes = SettingsToIPPOptions(settings_, printable_area_);
     auto* attr = GetAttribute(attributes.get(), attr_name);
     return ippGetCount(attr);
   }
 
   TestPrintSettings settings_;
+  gfx::Rect printable_area_;
 
   // PrintingContext::Delegate methods.
   gfx::NativeView GetParentView() override { return nullptr; }
@@ -169,11 +202,12 @@
   TestStringOptionValue(kIppDuplex, "two-sided-short-edge");
 }
 
-TEST_F(PrintingContextTest, SettingsToIPPOptions_Media) {
-  TestStringOptionValue(kIppMedia, "");
+TEST_F(PrintingContextTest, SettingsToIPPOptions_MediaCol) {
   settings_.set_requested_media(
       {gfx::Size(297000, 420000), "iso_a3_297x420mm"});
-  TestStringOptionValue(kIppMedia, "iso_a3_297x420mm");
+  printable_area_ =
+      gfx::Rect(2000, 1000, 297000 - (2000 + 3000), 420000 - (1000 + 4000));
+  TestMediaColValue(gfx::Size(29700, 42000), 100, 200, 300, 400);
 }
 
 TEST_F(PrintingContextTest, SettingsToIPPOptions_Copies) {
@@ -285,7 +319,7 @@
       "a.1-B_");
   settings_.set_client_infos({client_info});
 
-  auto attributes = SettingsToIPPOptions(settings_);
+  auto attributes = SettingsToIPPOptions(settings_, printable_area_);
   auto* attr = ippFindAttribute(attributes.get(), kIppClientInfo,
                                 IPP_TAG_BEGIN_COLLECTION);
   auto* client_info_collection = ippGetCollection(attr, 0);
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index eba9f9ae..9e7ae58 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -440,46 +440,6 @@
   return base::FeatureList::IsEnabled(features::kRendererAppContainer);
 }
 
-void SetJobMemoryLimit(Sandbox sandbox_type, TargetConfig* config) {
-  // Trigger feature list initialization here to ensure no population bias in
-  // the experimental and control groups.
-  [[maybe_unused]] const bool high_renderer_limits =
-      base::FeatureList::IsEnabled(
-          sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
-
-#if defined(ARCH_CPU_64_BITS)
-  size_t memory_limit = static_cast<size_t>(kDataSizeLimit);
-
-  if (sandbox_type == Sandbox::kGpu || sandbox_type == Sandbox::kRenderer) {
-    constexpr uint64_t GB = 1024 * 1024 * 1024;
-    // Allow the GPU/RENDERER process's sandbox to access more physical memory
-    // if it's available on the system.
-    //
-    // Renderer processes are allowed to access 16 GB; the GPU process, up
-    // to 64 GB.
-    uint64_t physical_memory = base::SysInfo::AmountOfPhysicalMemory();
-    if (sandbox_type == Sandbox::kGpu && physical_memory > 64 * GB) {
-      memory_limit = 64 * GB;
-    } else if (sandbox_type == Sandbox::kGpu && physical_memory > 32 * GB) {
-      memory_limit = 32 * GB;
-    } else if (physical_memory > 16 * GB) {
-      memory_limit = 16 * GB;
-    } else {
-      memory_limit = 8 * GB;
-    }
-
-    if (sandbox_type == Sandbox::kRenderer && high_renderer_limits) {
-      // Set limit to 1Tb.
-      memory_limit = 1024 * GB;
-    }
-  }
-
-  config->SetJobMemoryLimit(memory_limit);
-#else
-  return;
-#endif
-}
-
 // Generate a unique sandbox AC profile for the appcontainer based on the SHA1
 // hash of the appcontainer_id. This does not need to be secure so using SHA1
 // isn't a security concern.
@@ -791,7 +751,10 @@
   if (ret != SBOX_ALL_OK)
     return ret;
 
-  SetJobMemoryLimit(sandbox_type, config);
+  absl::optional<size_t> memory_limit = GetJobMemoryLimit(sandbox_type);
+  if (memory_limit) {
+    config->SetJobMemoryLimit(*memory_limit);
+  }
   return SBOX_ALL_OK;
 }
 
@@ -1136,5 +1099,45 @@
   return stream.str();
 }
 
+// static
+absl::optional<size_t> SandboxWin::GetJobMemoryLimit(Sandbox sandbox_type) {
+  // Trigger feature list initialization here to ensure no population bias in
+  // the experimental and control groups.
+  [[maybe_unused]] const bool high_renderer_limits =
+      base::FeatureList::IsEnabled(
+          sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
+
+#if defined(ARCH_CPU_64_BITS)
+  size_t memory_limit = static_cast<size_t>(kDataSizeLimit);
+
+  if (sandbox_type == Sandbox::kGpu || sandbox_type == Sandbox::kRenderer) {
+    constexpr uint64_t GB = 1024 * 1024 * 1024;
+    // Allow the GPU/RENDERER process's sandbox to access more physical memory
+    // if it's available on the system.
+    //
+    // Renderer processes are allowed to access 16 GB; the GPU process, up
+    // to 64 GB.
+    uint64_t physical_memory = base::SysInfo::AmountOfPhysicalMemory();
+    if (sandbox_type == Sandbox::kGpu && physical_memory > 64 * GB) {
+      memory_limit = 64 * GB;
+    } else if (sandbox_type == Sandbox::kGpu && physical_memory > 32 * GB) {
+      memory_limit = 32 * GB;
+    } else if (physical_memory > 16 * GB) {
+      memory_limit = 16 * GB;
+    } else {
+      memory_limit = 8 * GB;
+    }
+
+    if (sandbox_type == Sandbox::kRenderer && high_renderer_limits) {
+      // Set limit to 1Tb.
+      memory_limit = 1024 * GB;
+    }
+  }
+  return memory_limit;
+#else
+  return absl::nullopt;
+#endif
+}
+
 }  // namespace policy
 }  // namespace sandbox
diff --git a/sandbox/policy/win/sandbox_win.h b/sandbox/policy/win/sandbox_win.h
index d8842ebd..f45b798 100644
--- a/sandbox/policy/win/sandbox_win.h
+++ b/sandbox/policy/win/sandbox_win.h
@@ -13,11 +13,13 @@
 #include "base/functional/callback_forward.h"
 #include "base/process/launch.h"
 #include "base/process/process_handle.h"
+#include "build/build_config.h"
 #include "sandbox/policy/export.h"
 #include "sandbox/policy/sandbox_delegate.h"
 #include "sandbox/policy/sandbox_type.h"
 #include "sandbox/win/src/sandbox_types.h"
 #include "sandbox/win/src/security_level.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 class CommandLine;
@@ -119,6 +121,12 @@
   static std::string GetSandboxTagForDelegate(
       base::StringPiece prefix,
       sandbox::mojom::Sandbox sandbox_type);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(SandboxWinTest, GetJobMemoryLimit);
+
+  static absl::optional<size_t> GetJobMemoryLimit(
+      sandbox::mojom::Sandbox sandbox_type);
 };
 
 // Add a block list DLL to a configuration |config| based on the name of the DLL
diff --git a/sandbox/policy/win/sandbox_win_unittest.cc b/sandbox/policy/win/sandbox_win_unittest.cc
index 82beafb8..b70b480 100644
--- a/sandbox/policy/win/sandbox_win_unittest.cc
+++ b/sandbox/policy/win/sandbox_win_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/notreached.h"
 #include "base/path_service.h"
 #include "base/scoped_native_library.h"
+#include "base/test/scoped_amount_of_physical_memory_override.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/win/security_descriptor.h"
 #include "base/win/sid.h"
@@ -104,8 +105,9 @@
     } else {
       app_container_ = AppContainerBase::Open(package_name);
     }
-    if (!app_container_)
+    if (!app_container_) {
       return SBOX_ERROR_CREATE_APPCONTAINER;
+    }
     return SBOX_ALL_OK;
   }
 
@@ -144,8 +146,9 @@
     const base::ScopedTempDir& temp_dir,
     const std::initializer_list<std::wstring>& capabilities,
     base::FilePath* path) {
-  if (!base::CreateTemporaryFileInDir(temp_dir.GetPath(), path))
+  if (!base::CreateTemporaryFileInDir(temp_dir.GetPath(), path)) {
     return false;
+  }
 
   base::win::SecurityDescriptor sd;
   CHECK(sd.SetDaclEntry(base::win::WellKnownSid::kWorld,
@@ -267,8 +270,9 @@
   // Unlike the other tests below that merely test App Container behavior, and
   // can rely on RS1 version check, the GPU App Container feature is gated on
   // RS5. See sandbox::features::IsAppContainerSandboxSupported.
-  if (base::win::GetVersion() < base::win::Version::WIN10_RS5)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS5) {
     return;
+  }
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   EXPECT_FALSE(SandboxWin::IsAppContainerEnabledForSandbox(
       command_line, sandbox::mojom::Sandbox::kGpu));
@@ -281,8 +285,9 @@
 }
 
 TEST_F(SandboxWinTest, AppContainerAccessCheckFail) {
-  if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
     return;
+  }
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   scoped_refptr<AppContainerBase> profile;
   ResultCode result = CreateAppContainerProfile(
@@ -292,8 +297,9 @@
 }
 
 TEST_F(SandboxWinTest, AppContainerCheckProfile) {
-  if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
     return;
+  }
   constexpr wchar_t kInternetClient[] = L"internetClient";
   constexpr wchar_t kPrivateNetworkClientServer[] =
       L"privateNetworkClientServer";
@@ -353,8 +359,9 @@
 }
 
 TEST_F(SandboxWinTest, AppContainerCheckProfileDisableLpac) {
-  if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
     return;
+  }
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   base::test::ScopedFeatureList features;
   features.InitAndDisableFeature(features::kGpuLPAC);
@@ -367,8 +374,9 @@
 }
 
 TEST_F(SandboxWinTest, AppContainerCheckProfileAddCapabilities) {
-  if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
     return;
+  }
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   command_line.AppendSwitchASCII(switches::kAddGpuAppContainerCaps,
                                  "  cap1   ,   cap2   ,");
@@ -531,5 +539,95 @@
   ASSERT_EQ(ResultCode::SBOX_ERROR_UNSANDBOXED_PROCESS, result);
 }
 
+TEST_F(SandboxWinTest, GetJobMemoryLimit) {
+  constexpr uint64_t k8GB = 8192;
+#if defined(ARCH_CPU_64_BITS)
+  constexpr uint64_t kGB = 1024 * 1024 * 1024;
+  constexpr uint64_t k65GB = 66560;
+  constexpr uint64_t k33GB = 33792;
+  constexpr uint64_t k17GB = 17408;
+
+  // Test GPU with physical memory > 64GB.
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k65GB);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 64 * kGB);
+  }
+
+  // Test GPU with physical memory > 32GB
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k33GB);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 32 * kGB);
+  }
+
+  // Test GPU with physical memory > 16GB
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k17GB);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 16 * kGB);
+  }
+
+  // Test GPU with physical memory < 16GB
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 8 * kGB);
+  }
+
+  // Test Renderer with physical memory > 16GB
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k17GB);
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndDisableFeature(
+        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 16 * kGB);
+  }
+
+  // Test Renderer with physical memory < 16GB
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndDisableFeature(
+        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 8 * kGB);
+  }
+
+  // Test Renderer with high renderer limits enabled.
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndEnableFeature(
+        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
+    EXPECT_TRUE(memory_limit.has_value());
+    EXPECT_EQ(memory_limit, 1024 * kGB);
+  }
+#else
+  // Test 32-bit processes don't get a limit.
+  {
+    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
+    absl::optional<size_t> memory_limit =
+        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
+    EXPECT_FALSE(memory_limit.has_value());
+  }
+#endif  // defined(ARCH_CPU_64_BITS)
+}
+
 }  // namespace policy
 }  // namespace sandbox
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index d5299ee..ebb05cc 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -243,8 +243,16 @@
     // setting to `CONTENT_SETTING_BLOCK` so as not to accidentally change the
     // setting from `CONTENT_SETTING_SESSION_ONLY` to `CONTENT_SETTING_ALLOW` or
     // vice versa.
-    if (ShouldConsiderStorageAccessGrants(overrides) &&
-        IsAllowedByStorageAccessGrant(url, first_party_url)) {
+
+    bool has_storage_access_opt_in =
+        ShouldConsiderStorageAccessGrants(overrides);
+    bool has_storage_access_permission_grant =
+        IsAllowedByStorageAccessGrant(url, first_party_url);
+    net::cookie_util::FireStorageAccessInputHistogram(
+        /*has_opt_in=*/has_storage_access_opt_in,
+        /*has_grant=*/has_storage_access_permission_grant);
+
+    if (has_storage_access_opt_in && has_storage_access_permission_grant) {
       storage_access_result = net::cookie_util::StorageAccessResult::
           ACCESS_ALLOWED_STORAGE_ACCESS_GRANT;
     } else if (ShouldConsiderTopLevelStorageAccessGrants(overrides) &&
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 714af2d..384ea9b9 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -30,6 +30,9 @@
 constexpr char kAllowedRequestsHistogram[] =
     "API.StorageAccess.AllowedRequests2";
 
+constexpr char kStorageAccessInputStateHistogram[] =
+    "API.StorageAccess.InputState";
+
 constexpr char kDomainURL[] = "http://example.com";
 constexpr char kURL[] = "http://foo.com";
 constexpr char kOtherURL[] = "http://other.com";
@@ -234,11 +237,9 @@
   EXPECT_EQ(settings.GetCookieSetting(GURL(kURL), GURL(kOtherURL),
                                       GetCookieSettingOverrides(), nullptr),
             CONTENT_SETTING_ALLOW);
-  histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-  histogram_tester.ExpectBucketCount(
+  histogram_tester.ExpectUniqueSample(
       kAllowedRequestsHistogram,
-      static_cast<int>(net::cookie_util::StorageAccessResult::ACCESS_ALLOWED),
-      1);
+      net::cookie_util::StorageAccessResult::ACCESS_ALLOWED, 1);
 }
 
 TEST_P(CookieSettingsTest, GetCookieSettingBlockThirdParty) {
@@ -299,25 +300,22 @@
   EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithEitherOverride(CONTENT_SETTING_ALLOW));
-  histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-  histogram_tester.ExpectBucketCount(
-      kAllowedRequestsHistogram,
-      static_cast<int>(BlockedStorageAccessResultWithEitherOverride()), 1);
+  histogram_tester.ExpectUniqueSample(
+      kAllowedRequestsHistogram, BlockedStorageAccessResultWithEitherOverride(),
+      1);
 
   // Invalid pair the |top_level_url| granting access to |url| is now
   // being loaded under |url| as the top level url.
   EXPECT_EQ(settings.GetCookieSetting(top_level_url, url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithForceAllowThirdPartyCookies());
-  histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 2);
+
+  histogram_tester.ExpectBucketCount(kAllowedRequestsHistogram,
+                                     net::cookie_util::StorageAccessResult::
+                                         ACCESS_ALLOWED_STORAGE_ACCESS_GRANT,
+                                     IsStorageAccessGrantEligible() ? 1 : 0);
   histogram_tester.ExpectBucketCount(
-      kAllowedRequestsHistogram,
-      static_cast<int>(net::cookie_util::StorageAccessResult::
-                           ACCESS_ALLOWED_STORAGE_ACCESS_GRANT),
-      IsStorageAccessGrantEligible() ? 1 : 0);
-  histogram_tester.ExpectBucketCount(
-      kAllowedRequestsHistogram,
-      static_cast<int>(BlockedStorageAccessResultWithEitherOverride()),
+      kAllowedRequestsHistogram, BlockedStorageAccessResultWithEitherOverride(),
       IsStorageAccessGrantEligible() ? 1 : 2);
 
   // Invalid pairs where a |third_url| is used.
@@ -327,6 +325,12 @@
   EXPECT_EQ(settings.GetCookieSetting(third_url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithForceAllowThirdPartyCookies());
+  histogram_tester.ExpectBucketCount(
+      kStorageAccessInputStateHistogram,
+      IsStorageAccessGrantEligible()
+          ? net::cookie_util::StorageAccessInputState::kOptInWithoutGrant
+          : net::cookie_util::StorageAccessInputState::kNoOptInNoGrant,
+      3);
 
   // If third-party cookies are blocked, SAA grant takes precedence over
   // possible override to force allow 3PCs.
@@ -336,10 +340,16 @@
     EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                         GetCookieSettingOverrides(), nullptr),
               SettingWithEitherOverride(CONTENT_SETTING_ALLOW));
-    histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-    histogram_tester_2.ExpectBucketCount(
+    histogram_tester_2.ExpectUniqueSample(
         kAllowedRequestsHistogram,
-        static_cast<int>(BlockedStorageAccessResultWithEitherOverride()), 1);
+        BlockedStorageAccessResultWithEitherOverride(), 1);
+
+    histogram_tester_2.ExpectUniqueSample(
+        kStorageAccessInputStateHistogram,
+        IsStorageAccessGrantEligible()
+            ? net::cookie_util::StorageAccessInputState::kOptInWithGrant
+            : net::cookie_util::StorageAccessInputState::kGrantWithoutOptIn,
+        1);
   }
 
   // If cookies are globally blocked, SAA grants and 3PC override
@@ -352,11 +362,10 @@
     EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                         GetCookieSettingOverrides(), nullptr),
               CONTENT_SETTING_BLOCK);
-    histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-    histogram_tester_2.ExpectBucketCount(
+    histogram_tester_2.ExpectUniqueSample(
         kAllowedRequestsHistogram,
-        static_cast<int>(net::cookie_util::StorageAccessResult::ACCESS_BLOCKED),
-        1);
+        net::cookie_util::StorageAccessResult::ACCESS_BLOCKED, 1);
+    histogram_tester_2.ExpectTotalCount(kStorageAccessInputStateHistogram, 0);
   }
 }
 
@@ -385,12 +394,9 @@
   EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithEitherOverrideForTopLevel());
-  histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-  histogram_tester.ExpectBucketCount(
+  histogram_tester.ExpectUniqueSample(
       kAllowedRequestsHistogram,
-      static_cast<int>(
-          BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
-      1);
+      BlockedStorageAccessResultWithEitherOverrideForTopLevel(), 1);
 
   // Check the cookie setting that does not match the top-level storage access
   // grant--the |top_level_url| granting access to |url| is now being loaded
@@ -403,13 +409,12 @@
   // and the page-level variant.
   histogram_tester.ExpectBucketCount(
       kAllowedRequestsHistogram,
-      static_cast<int>(net::cookie_util::StorageAccessResult::
-                           ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT),
+      net::cookie_util::StorageAccessResult::
+          ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT,
       IsTopLevelStorageAccessGrantEligible() ? 1 : 0);
   histogram_tester.ExpectBucketCount(
       kAllowedRequestsHistogram,
-      static_cast<int>(
-          BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
+      BlockedStorageAccessResultWithEitherOverrideForTopLevel(),
       IsTopLevelStorageAccessGrantEligible() ? 1 : 2);
 
   // Check the cookie setting that does not match the top-level storage access
@@ -428,12 +433,9 @@
     EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                         GetCookieSettingOverrides(), nullptr),
               SettingWithEitherOverrideForTopLevel());
-    histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-    histogram_tester_2.ExpectBucketCount(
+    histogram_tester_2.ExpectUniqueSample(
         kAllowedRequestsHistogram,
-        static_cast<int>(
-            BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
-        1);
+        BlockedStorageAccessResultWithEitherOverrideForTopLevel(), 1);
   }
 
   // If cookies are globally blocked, Top-Level Storage Access grants and 3PC
@@ -445,11 +447,9 @@
     EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                         GetCookieSettingOverrides(), nullptr),
               CONTENT_SETTING_BLOCK);
-    histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
-    histogram_tester_2.ExpectBucketCount(
+    histogram_tester_2.ExpectUniqueSample(
         kAllowedRequestsHistogram,
-        static_cast<int>(net::cookie_util::StorageAccessResult::ACCESS_BLOCKED),
-        1);
+        net::cookie_util::StorageAccessResult::ACCESS_BLOCKED, 1);
   }
 }
 
@@ -512,9 +512,13 @@
   settings.set_storage_access_grants(
       {CreateSetting(url.host(), top_level_url.host(), CONTENT_SETTING_ALLOW)});
 
+  base::HistogramTester histogram_tester;
+
   EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             CONTENT_SETTING_BLOCK);
+
+  histogram_tester.ExpectTotalCount(kStorageAccessInputStateHistogram, 0);
 }
 
 // Once a grant expires access should no longer be given.
@@ -532,12 +536,19 @@
       {CreateSetting(url.host(), top_level_url.host(), CONTENT_SETTING_ALLOW,
                      expiration_time)});
 
+  base::HistogramTester histogram_tester;
   // When requesting our setting for the embedder/top-level combination our
   // grant is for access should be allowed. For any other domain pairs access
   // should still be blocked.
   EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithEitherOverride(CONTENT_SETTING_ALLOW));
+  histogram_tester.ExpectUniqueSample(
+      kStorageAccessInputStateHistogram,
+      IsStorageAccessGrantEligible()
+          ? net::cookie_util::StorageAccessInputState::kOptInWithGrant
+          : net::cookie_util::StorageAccessInputState::kGrantWithoutOptIn,
+      1);
 
   // If we fastforward past the expiration of our grant the result should be
   // CONTENT_SETTING_BLOCK now.
@@ -545,6 +556,12 @@
   EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
                                       GetCookieSettingOverrides(), nullptr),
             SettingWithForceAllowThirdPartyCookies());
+  histogram_tester.ExpectBucketCount(
+      kStorageAccessInputStateHistogram,
+      IsStorageAccessGrantEligible()
+          ? net::cookie_util::StorageAccessInputState::kOptInWithoutGrant
+          : net::cookie_util::StorageAccessInputState::kNoOptInNoGrant,
+      1);
 }
 
 TEST_P(CookieSettingsTest, CreateDeleteCookieOnExitPredicateNoSettings) {
diff --git a/services/network/public/cpp/resource_request.cc b/services/network/public/cpp/resource_request.cc
index 6d42ae6..f59007a 100644
--- a/services/network/public/cpp/resource_request.cc
+++ b/services/network/public/cpp/resource_request.cc
@@ -251,6 +251,7 @@
          destination == request.destination &&
          request_body == request.request_body &&
          keepalive == request.keepalive &&
+         shared_storage_writable == request.shared_storage_writable &&
          has_user_gesture == request.has_user_gesture &&
          enable_load_timing == request.enable_load_timing &&
          enable_upload_progress == request.enable_upload_progress &&
diff --git a/services/network/public/cpp/resource_request.h b/services/network/public/cpp/resource_request.h
index ee82c5c1..b4d389b1 100644
--- a/services/network/public/cpp/resource_request.h
+++ b/services/network/public/cpp/resource_request.h
@@ -161,6 +161,7 @@
   bool keepalive = false;
   bool browsing_topics = false;
   bool ad_auction_headers = false;
+  bool shared_storage_writable = false;
   bool has_user_gesture = false;
   bool enable_load_timing = false;
   bool enable_upload_progress = false;
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 7a4d54a..272e941f 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -212,8 +212,6 @@
 //
 // Remove these as we update our sites.
 
-#define SK_LEGACY_LAYER_BOUNDS_EXPANSION  // skbug.com/12083, skbug.com/12303
-
 // Workaround for poor anisotropic mipmap quality,
 // pending Skia ripmap support.
 // (https://bugs.chromium.org/p/skia/issues/detail?id=4863)
diff --git a/sql/database.cc b/sql/database.cc
index cf6f6f1..ff070e1 100644
--- a/sql/database.cc
+++ b/sql/database.cc
@@ -1675,6 +1675,11 @@
                                const char* column_name) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  if (!db_) {
+    DCHECK(poisoned_) << "Illegal use of Database without a db";
+    return false;
+  }
+
   // sqlite3_table_column_metadata uses out-params to return column definition
   // details, such as the column type and whether it allows NULL values. These
   // aren't needed to compute the current method's result, so we pass in nullptr
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 5225296..b0d2c730 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -27671,7 +27671,7 @@
               "cpu": "x86-64",
               "device_os": null,
               "device_type": null,
-              "os": "Ubuntu-18.04",
+              "os": "Ubuntu-22.04",
               "pool": "chromium.tests.avd"
             }
           ],
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 0dd4685..32da1092f 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5730,9 +5730,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5743,8 +5743,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -5895,9 +5895,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5908,8 +5908,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -6042,9 +6042,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6055,8 +6055,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 8425afe..6f7fc7b 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -25491,9 +25491,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25504,8 +25504,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -25656,9 +25656,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25669,8 +25669,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -25803,9 +25803,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25816,8 +25816,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index ad01fe4..e424010 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41382,9 +41382,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41394,8 +41394,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -41547,9 +41547,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41559,8 +41559,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -41694,9 +41694,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41706,8 +41706,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -43171,9 +43171,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43183,8 +43183,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -43336,9 +43336,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43348,8 +43348,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -43483,9 +43483,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43495,8 +43495,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -44231,9 +44231,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44243,8 +44243,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index bb30e60..9ebc8f8 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18080,12 +18080,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18096,8 +18096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -18265,12 +18265,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18281,8 +18281,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
@@ -18427,12 +18427,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 115.0.5772.0",
+        "description": "Run with ash-chrome version 115.0.5773.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18443,8 +18443,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v115.0.5772.0",
-              "revision": "version:115.0.5772.0"
+              "location": "lacros_version_skew_tests_v115.0.5773.0",
+              "revision": "version:115.0.5773.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 7b1b119..d334e5f2 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -284,7 +284,7 @@
           "hard_timeout": 21600,
           "io_timeout": 21600,
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
+          "shards": 8
         },
         "trigger_script": {
           "args": [
diff --git a/testing/buildbot/chromium.perf.pinpoint.json b/testing/buildbot/chromium.perf.pinpoint.json
index ca774351..00ecf59 100644
--- a/testing/buildbot/chromium.perf.pinpoint.json
+++ b/testing/buildbot/chromium.perf.pinpoint.json
@@ -197,7 +197,7 @@
           "hard_timeout": 21600,
           "io_timeout": 21600,
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
+          "shards": 8
         },
         "trigger_script": {
           "args": [
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 099d19e..cb63660 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1445,8 +1445,6 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
-              # TODO(crbug.com/1412588): Move to jammy.
-              'os': 'Ubuntu-18.04',
             },
           ],
         },
@@ -1484,8 +1482,6 @@
             {
               # use 8-core to shorten runtime
               'cores': '8',
-              # TODO(crbug.com/1412588): Move to jammy.
-              'os': 'Ubuntu-18.04',
             },
           ],
         },
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index e52d97ce..3e7b7bc3 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5772.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v115.0.5773.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 115.0.5772.0',
+    'description': 'Run with ash-chrome version 115.0.5773.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v115.0.5772.0',
-          'revision': 'version:115.0.5772.0',
+          'location': 'lacros_version_skew_tests_v115.0.5773.0',
+          'revision': 'version:115.0.5773.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 9ff080a..0044e28b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4100,13 +4100,6 @@
             ],
             "experiments": [
                 {
-                    "name": "Panorama",
-                    "enable_features": [
-                        "CustomizeChromeColorExtraction",
-                        "CustomizeChromeSidePanel"
-                    ]
-                },
-                {
                     "name": "Panorama_In_Product_Help",
                     "enable_features": [
                         "CustomizeChromeColorExtraction",
@@ -9435,22 +9428,6 @@
             ]
         }
     ],
-    "OsFeedback": [
-        {
-            "platforms": [
-                "chromeos",
-                "chromeos_lacros"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20221130",
-                    "enable_features": [
-                        "OsFeedback"
-                    ]
-                }
-            ]
-        }
-    ],
     "OutOfProcessPrintDriversPrintPreview": [
         {
             "platforms": [
diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/BUILD.gn b/third_party/abseil-cpp/absl/time/internal/cctz/BUILD.gn
index 6796a22..5ec76440 100644
--- a/third_party/abseil-cpp/absl/time/internal/cctz/BUILD.gn
+++ b/third_party/abseil-cpp/absl/time/internal/cctz/BUILD.gn
@@ -44,6 +44,11 @@
     "//third_party/abseil-cpp/absl/base:config",
   ]
   if (is_fuchsia) {
+    cflags = [
+      # Remove when fixed in Fuchsia SDK:
+      # https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=127301
+      "-Wno-sign-conversion",
+    ]
     deps += [
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.intl:fuchsia.intl_hlcpp",
       "//third_party/fuchsia-sdk/sdk/pkg/async",
diff --git a/third_party/blink/common/frame/view_transition_state_mojom_traits.cc b/third_party/blink/common/frame/view_transition_state_mojom_traits.cc
index 3ff12fce8..c88f87b 100644
--- a/third_party/blink/common/frame/view_transition_state_mojom_traits.cc
+++ b/third_party/blink/common/frame/view_transition_state_mojom_traits.cc
@@ -19,7 +19,9 @@
       !data.ReadViewportMatrix(&out->viewport_matrix) ||
       !data.ReadOverflowRectInLayoutSpace(
           &out->overflow_rect_in_layout_space) ||
-      !data.ReadSnapshotId(&out->snapshot_id)) {
+      !data.ReadSnapshotId(&out->snapshot_id) ||
+      !data.ReadCapturedRectInLayoutSpace(
+          &out->captured_rect_in_layout_space)) {
     return false;
   }
 
diff --git a/third_party/blink/common/manifest/manifest_util.cc b/third_party/blink/common/manifest/manifest_util.cc
index 58910fb..abada12 100644
--- a/third_party/blink/common/manifest/manifest_util.cc
+++ b/third_party/blink/common/manifest/manifest_util.cc
@@ -150,12 +150,8 @@
 }
 
 GURL GetIdFromManifest(const mojom::Manifest& manifest) {
-  if (manifest.id.has_value()) {
-    // Generate the formatted id by <start_url_origin>/<manifest_id>.
-    GURL manifest_id(manifest.start_url.DeprecatedGetOriginAsURL().spec() +
-                     base::UTF16ToUTF8(manifest.id.value()));
-    DCHECK(manifest_id.is_valid());
-    return manifest_id;
+  if (manifest.id.is_valid()) {
+    return manifest.id;
   }
   return manifest.start_url;
 }
diff --git a/third_party/blink/common/page/page_zoom.cc b/third_party/blink/common/page/page_zoom.cc
index 0bb248b..85e64081 100644
--- a/third_party/blink/common/page/page_zoom.cc
+++ b/third_party/blink/common/page/page_zoom.cc
@@ -6,8 +6,11 @@
 
 #include <cmath>
 
+#include "build/build_config.h"
+
 namespace blink {
 
+#if !BUILDFLAG(IS_ANDROID)
 // The minimum and maximum amount of page zoom that is possible, independent
 // of other factors such as device scale and page scale (pinch). Historically,
 // these values came from WebKitLegacy/mac/WebView/WebView.mm where they are
@@ -15,6 +18,16 @@
 // changed to use different limits.
 const double kMinimumPageZoomFactor = 0.25;
 const double kMaximumPageZoomFactor = 5.0;
+#else
+// On Android, both OS-level font size and desktop site preferences are
+// considered when calculating zoom factor. Requesting desktop site can
+// increase zoom by 10% (see: |kDefaultRequestDesktopSiteZoomScale|). At the
+// OS-level, we support a range of 85% - 200%, and at the browser-level we
+// support 50% - 300%. The max we support is therefore: 3.0 * 1.1 * 2 = 6.6,
+// and the min is 0.5 * .85 = .425 (depending on settings).
+const double kMinimumPageZoomFactor = 0.425;
+const double kMaximumPageZoomFactor = 6.6;
+#endif
 
 // Change the zoom factor by 20% for each zoom level increase from the user.
 // Historically, this value came from WebKit in
diff --git a/third_party/blink/common/permissions_policy/permissions_policy.cc b/third_party/blink/common/permissions_policy/permissions_policy.cc
index 67c3f8ea5..b90359e 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy.cc
@@ -176,6 +176,15 @@
         mojom::PermissionsPolicyFeature::kBrowsingTopicsBackwardCompatible);
   }
 
+  // Note that currently permissions for `sharedStorageWritable` are checked
+  // using `IsFeatureEnabledForSubresourceRequestAssumingOptIn()`, since a
+  // `network::ResourceRequest` is not available at the call site and
+  // `blink::ResourceRequest` should not be used in blink public APIs.
+  if (request.shared_storage_writable) {
+    DCHECK(base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI));
+    opt_in_features.insert(mojom::PermissionsPolicyFeature::kSharedStorage);
+  }
+
   return IsFeatureEnabledForOriginImpl(feature, origin, opt_in_features);
 }
 
@@ -330,6 +339,12 @@
   return feature_state;
 }
 
+const mojom::PermissionsPolicyFeature
+    PermissionsPolicy::defined_opt_in_features_[] = {
+        mojom::PermissionsPolicyFeature::kBrowsingTopics,
+        mojom::PermissionsPolicyFeature::kBrowsingTopicsBackwardCompatible,
+        mojom::PermissionsPolicyFeature::kSharedStorage};
+
 PermissionsPolicy::PermissionsPolicy(
     url::Origin origin,
     const PermissionsPolicyFeatureList& feature_list)
@@ -431,6 +446,18 @@
   return origin_.IsSameOriginWith(origin);
 }
 
+bool PermissionsPolicy::IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+    mojom::PermissionsPolicyFeature feature,
+    const url::Origin& origin) const {
+  CHECK(base::Contains(defined_opt_in_features_, feature));
+
+  // Make an opt-in features set containing exactly `feature`, as we're not
+  // given access to the full request to derive any other opt-in features.
+  std::set<mojom::PermissionsPolicyFeature> opt_in_features({feature});
+
+  return IsFeatureEnabledForOriginImpl(feature, origin, opt_in_features);
+}
+
 // Implements Permissions Policy 9.7: Define an inherited policy for feature in
 // browsing context and 9.8: Define an inherited policy for feature in container
 // at origin.
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc b/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
index c9e2e51..233a81f 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
@@ -83,6 +83,14 @@
         origin, feature_list_, required_permissions_to_load);
   }
 
+  bool IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+      PermissionsPolicy* policy,
+      mojom::PermissionsPolicyFeature feature,
+      const url::Origin& origin) const {
+    return policy->IsFeatureEnabledForSubresourceRequestAssumingOptIn(feature,
+                                                                      origin);
+  }
+
   bool PolicyContainsInheritedValue(const PermissionsPolicy* policy,
                                     mojom::PermissionsPolicyFeature feature) {
     return policy->inherited_policies_.find(feature) !=
@@ -1623,49 +1631,93 @@
 
 // A cross-origin subresource request that explicitly sets the browsingTopics
 // flag should have the browsing-topics permission as long as it passes
-// allowlist check, regardless of the feature's default state.
+// allowlist check, regardless of the feature's default state. Similarly for the
+// sharedStorageWritable flag.
 TEST_F(PermissionsPolicyTest,
-       ProposedTestIsBrowsingTopicsFeatureEnabledForSubresourceRequest) {
+       ProposedTestIsFeatureEnabledForSubresourceRequest) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(blink::features::kBrowsingTopics);
+  feature_list.InitWithFeatures(
+      {blink::features::kBrowsingTopics, blink::features::kSharedStorageAPI},
+      /*disabled_features=*/{});
 
-  network::ResourceRequest request_without_topics_opt_in;
+  network::ResourceRequest request_without_any_opt_in;
 
   network::ResourceRequest request_with_topics_opt_in;
   request_with_topics_opt_in.browsing_topics = true;
 
+  network::ResourceRequest request_with_shared_storage_opt_in;
+  request_with_shared_storage_opt_in.shared_storage_writable = true;
+
+  network::ResourceRequest request_with_both_opt_in;
+  request_with_both_opt_in.browsing_topics = true;
+  request_with_both_opt_in.shared_storage_writable = true;
+
   {
-    // +-------------------------------------------------+
-    // |(1)Origin A                                      |
-    // |No Policy                                        |
-    // |                                                 |
-    // | fetch(<Origin B's url>, {browsingTopics: true}) |
-    // +-------------------------------------------------+
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |No Policy                                               |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {browsingTopics: true})        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin B's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
 
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
         request_with_topics_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_both_opt_in));
+
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
         request_with_topics_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_both_opt_in));
   }
 
   {
-    // +-------------------------------------------------+
-    // |(1)Origin A                                      |
-    // |Permissions-Policy: browsing-topics=(self)       |
-    // |                                                 |
-    // | fetch(<Origin B's url>, {browsingTopics: true}) |
-    // +-------------------------------------------------+
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: browsing-topics=(self),             |
+    // |                    shared-storage=(self)               |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {browsingTopics: true})        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin B's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
@@ -1673,29 +1725,65 @@
                                /*allowed_origins=*/{},
                                /*self_if_matches=*/origin_a_,
                                /*matches_all_origins=*/false,
+                               /*matches_opaque_src=*/false},
+                              {mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/origin_a_,
+                               /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
         request_with_topics_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_both_opt_in));
+
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
         request_with_topics_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_shared_storage_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_both_opt_in));
   }
 
   {
-    // +-------------------------------------------------+
-    // |(1)Origin A                                      |
-    // |Permissions-Policy: browsing-topics=(none)       |
-    // |                                                 |
-    // | fetch(<Origin B's url>, {browsingTopics: true}) |
-    // +-------------------------------------------------+
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: browsing-topics=(none),             |
+    // |                    shared-storage=(none)               |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {browsingTopics: true})        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin B's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
@@ -1703,29 +1791,65 @@
                                /*allowed_origins=*/{},
                                /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/false,
+                               /*matches_opaque_src=*/false},
+                              {mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/absl::nullopt,
+                               /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
         request_with_topics_opt_in));
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_shared_storage_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
         request_with_topics_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_shared_storage_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_both_opt_in));
   }
 
   {
-    // +-------------------------------------------------+
-    // |(1)Origin A                                      |
-    // |Permissions-Policy: browsing-topics=*            |
-    // |                                                 |
-    // | fetch(<Origin B's url>, {browsingTopics: true}) |
-    // +-------------------------------------------------+
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: browsing-topics=*,                  |
+    // |                    shared-storage=*                    |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {browsingTopics: true})        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin B's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
@@ -1733,60 +1857,308 @@
                                /*allowed_origins=*/{},
                                /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/true,
+                               /*matches_opaque_src=*/false},
+                              {mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/absl::nullopt,
+                               /*matches_all_origins=*/true,
                                /*matches_opaque_src=*/false}}});
 
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
         request_with_topics_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_without_topics_opt_in));
+        request_without_any_opt_in));
     EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
         mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
         request_with_topics_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_both_opt_in));
   }
 
   {
-    // +-------------------------------------------------+
-    // |(1)Origin A                                      |
-    // |Permissions-Policy: browsing-topics=(Origin B)   |
-    // |                                                 |
-    // | fetch(<Origin B's url>, {browsingTopics: true}) |
-    // | fetch(<Origin C's url>, {browsingTopics: true}) |
-    // +-------------------------------------------------+
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: browsing-topics=(Origin B),         |
+    // |                    shared-storage=(Origin B)           |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {browsingTopics: true})        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin B's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // | fetch(<Origin C's url>, {browsingTopics: true})        |
+    // | fetch(<Origin C's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin C's url>, {browsingTopics: true,         |
+    // |                          sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
-    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::
-                                   kBrowsingTopics, /*allowed_origins=*/
-                               {blink::OriginWithPossibleWildcards(
-                                   origin_b_,
-                                   /*has_subdomain_wildcard=*/false)},
+    policy->SetHeaderPolicy(
+        {{{mojom::PermissionsPolicyFeature::
+               kBrowsingTopics, /*allowed_origins=*/
+           {blink::OriginWithPossibleWildcards(
+               origin_b_,
+               /*has_subdomain_wildcard=*/false)},
+           /*self_if_matches=*/absl::nullopt,
+           /*matches_all_origins=*/false,
+           /*matches_opaque_src=*/false},
+          {mojom::PermissionsPolicyFeature::kSharedStorage, /*allowed_origins=*/
+           {blink::OriginWithPossibleWildcards(
+               origin_b_,
+               /*has_subdomain_wildcard=*/false)},
+           /*self_if_matches=*/absl::nullopt,
+           /*matches_all_origins=*/false,
+           /*matches_opaque_src=*/false}}});
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_topics_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_shared_storage_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_topics_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_without_any_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_shared_storage_opt_in));
+    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_c_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_c_,
+        request_with_topics_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_c_,
+        request_with_both_opt_in));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_c_,
+        request_without_any_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_c_,
+        request_with_shared_storage_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_c_,
+        request_with_both_opt_in));
+  }
+}
+
+// A cross-origin subresource request that explicitly sets the
+// sharedStorageWritable flag should have the Shared Storage permission as long
+// as it passes the allowlist check, regardless of the feature's default state.
+TEST_F(PermissionsPolicyTest,
+       ProposedTestIsFeatureEnabledForSubresourceRequestAssumingOptIn) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures({blink::features::kSharedStorageAPI},
+                                /*disabled_features=*/{});
+
+  {
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |No Policy                                               |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
+
+    std::unique_ptr<PermissionsPolicy> policy =
+        CreateFromParentPolicy(nullptr, origin_a_);
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_a_));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_b_));
+  }
+
+  {
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: shared-storage=(self)              |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
+
+    std::unique_ptr<PermissionsPolicy> policy =
+        CreateFromParentPolicy(nullptr, origin_a_);
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/origin_a_,
+                               /*matches_all_origins=*/false,
+                               /*matches_opaque_src=*/false}}});
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_a_));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_));
+    EXPECT_FALSE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_b_));
+  }
+
+  {
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: shared-storage=(none)              |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
+
+    std::unique_ptr<PermissionsPolicy> policy =
+        CreateFromParentPolicy(nullptr, origin_a_);
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
                                /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
-    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_without_topics_opt_in));
-    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_a_,
-        request_with_topics_opt_in));
-    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_without_topics_opt_in));
-    EXPECT_TRUE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_b_,
-        request_with_topics_opt_in));
-    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_c_,
-        request_without_topics_opt_in));
-    EXPECT_FALSE(policy->IsFeatureEnabledForSubresourceRequest(
-        mojom::PermissionsPolicyFeature::kBrowsingTopics, origin_c_,
-        request_with_topics_opt_in));
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_));
+    EXPECT_FALSE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_a_));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_));
+    EXPECT_FALSE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_b_));
+  }
+
+  {
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: shared-storage=*                   |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
+
+    std::unique_ptr<PermissionsPolicy> policy =
+        CreateFromParentPolicy(nullptr, origin_a_);
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kSharedStorage,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/absl::nullopt,
+                               /*matches_all_origins=*/true,
+                               /*matches_opaque_src=*/false}}});
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_a_));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_b_));
+  }
+
+  {
+    // +--------------------------------------------------------+
+    // |(1)Origin A                                             |
+    // |Permissions-Policy: shared-storage=(Origin B)          |
+    // |                                                        |
+    // | fetch(<Origin B's url>, {sharedStorageWritable: true}) |
+    // | fetch(<Origin C's url>, {sharedStorageWritable: true}) |
+    // +--------------------------------------------------------+
+
+    std::unique_ptr<PermissionsPolicy> policy =
+        CreateFromParentPolicy(nullptr, origin_a_);
+    policy->SetHeaderPolicy(
+        {{{mojom::PermissionsPolicyFeature::kSharedStorage, /*allowed_origins=*/
+           {blink::OriginWithPossibleWildcards(
+               origin_b_,
+               /*has_subdomain_wildcard=*/false)},
+           /*self_if_matches=*/absl::nullopt,
+           /*matches_all_origins=*/false,
+           /*matches_opaque_src=*/false}}});
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_a_));
+    EXPECT_FALSE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_a_));
+
+    EXPECT_TRUE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_b_));
+    EXPECT_TRUE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_b_));
+
+    EXPECT_FALSE(policy->IsFeatureEnabledForOrigin(
+        mojom::PermissionsPolicyFeature::kSharedStorage, origin_c_));
+    EXPECT_FALSE(IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+        policy.get(), mojom::PermissionsPolicyFeature::kSharedStorage,
+        origin_c_));
   }
 }
 
diff --git a/third_party/blink/public/common/frame/view_transition_state.h b/third_party/blink/public/common/frame/view_transition_state.h
index 71be049e..0d11a435 100644
--- a/third_party/blink/public/common/frame/view_transition_state.h
+++ b/third_party/blink/public/common/frame/view_transition_state.h
@@ -12,6 +12,8 @@
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/geometry/transform.h"
 
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
 namespace blink {
 
 struct BLINK_COMMON_EXPORT ViewTransitionElement {
@@ -22,6 +24,7 @@
   viz::ViewTransitionElementResourceId snapshot_id;
   int32_t paint_order = 0;
   bool is_root = false;
+  absl::optional<gfx::RectF> captured_rect_in_layout_space;
 };
 
 struct BLINK_COMMON_EXPORT ViewTransitionState {
diff --git a/third_party/blink/public/common/frame/view_transition_state_mojom_traits.h b/third_party/blink/public/common/frame/view_transition_state_mojom_traits.h
index 01f81a4..177baf7 100644
--- a/third_party/blink/public/common/frame/view_transition_state_mojom_traits.h
+++ b/third_party/blink/public/common/frame/view_transition_state_mojom_traits.h
@@ -50,6 +50,11 @@
     return r.is_root;
   }
 
+  static const absl::optional<gfx::RectF>& captured_rect_in_layout_space(
+      const blink::ViewTransitionElement& r) {
+    return r.captured_rect_in_layout_space;
+  }
+
   static bool Read(blink::mojom::ViewTransitionElementDataView r,
                    blink::ViewTransitionElement* out);
 };
diff --git a/third_party/blink/public/common/permissions_policy/permissions_policy.h b/third_party/blink/public/common/permissions_policy/permissions_policy.h
index a4ebff1..16f25e65 100644
--- a/third_party/blink/public/common/permissions_policy/permissions_policy.h
+++ b/third_party/blink/public/common/permissions_policy/permissions_policy.h
@@ -26,6 +26,9 @@
 
 namespace blink {
 
+class Request;
+class ResourceRequest;
+
 // Permissions Policy is a mechanism for controlling the availability of web
 // platform features in a frame, including all embedded frames. It can be used
 // to remove features, automatically refuse API permission requests, or modify
@@ -249,8 +252,13 @@
       mojom::PermissionsPolicyFeature feature) const;
 
  private:
+  friend class Request;
+  friend class ResourceRequest;
   friend class PermissionsPolicyTest;
 
+  // List of features that have an explicit opt-in mechanism.
+  static const mojom::PermissionsPolicyFeature defined_opt_in_features_[];
+
   PermissionsPolicy(url::Origin origin,
                     const PermissionsPolicyFeatureList& feature_list);
   static std::unique_ptr<PermissionsPolicy> CreateFromParentPolicy(
@@ -278,6 +286,13 @@
       const url::Origin& origin,
       const std::set<mojom::PermissionsPolicyFeature>& opt_in_features) const;
 
+  // Returns whether or not the given feature is enabled by this policy for a
+  // specific origin, given that the feature is an opt-in feature, and the
+  // subresource request for which we are querying has opted-into this feature.
+  bool IsFeatureEnabledForSubresourceRequestAssumingOptIn(
+      mojom::PermissionsPolicyFeature feature,
+      const url::Origin& origin) const;
+
   bool InheritedValueForFeature(
       const PermissionsPolicy* parent_policy,
       std::pair<mojom::PermissionsPolicyFeature,
diff --git a/third_party/blink/public/mojom/frame/view_transition_state.mojom b/third_party/blink/public/mojom/frame/view_transition_state.mojom
index 4ec7e46..ca71192 100644
--- a/third_party/blink/public/mojom/frame/view_transition_state.mojom
+++ b/third_party/blink/public/mojom/frame/view_transition_state.mojom
@@ -17,6 +17,7 @@
   viz.mojom.ViewTransitionElementResourceId snapshot_id;
   int32 paint_order;
   bool is_root;
+  gfx.mojom.RectF? captured_rect_in_layout_space;
 
   // TODO(khushalsagar): Add writing mode.
 };
diff --git a/third_party/blink/public/mojom/manifest/manifest.mojom b/third_party/blink/public/mojom/manifest/manifest.mojom
index eb42235..db7d777 100644
--- a/third_party/blink/public/mojom/manifest/manifest.mojom
+++ b/third_party/blink/public/mojom/manifest/manifest.mojom
@@ -29,8 +29,8 @@
 
   mojo_base.mojom.String16? description;
 
-  // Defaults to start_url with origin stripped when id field is not present.
-  mojo_base.mojom.String16? id;
+  // This is empty if the start_url is empty.
+  url.mojom.Url id;
 
   url.mojom.Url start_url;
 
diff --git a/third_party/blink/public/mojom/subapps/sub_apps_service.mojom b/third_party/blink/public/mojom/subapps/sub_apps_service.mojom
index 68e11984..66e64f6 100644
--- a/third_party/blink/public/mojom/subapps/sub_apps_service.mojom
+++ b/third_party/blink/public/mojom/subapps/sub_apps_service.mojom
@@ -9,7 +9,7 @@
   kFailure,
 };
 
-// `unhashed_app_id_path` is the sub app id generated according to
+// `manifest_id_path` is the sub app id generated according to
 // https://www.w3.org/TR/appmanifest/#dfn-identity, but only the path component
 // of the full url.
 // `install_url_path` is a path to a page hosting or containing a link to the
@@ -18,12 +18,12 @@
 // collision in the Android mojo bindings (interface name "SubAppsService" plus
 // method name "Add" plus suffix "Params" create the colliding name).
 struct SubAppsServiceAddParameters {
-  string unhashed_app_id_path;
+  string manifest_id_path;
   string install_url_path;
 };
 
 struct SubAppsServiceAddResult {
-  string unhashed_app_id_path;
+  string manifest_id_path;
   SubAppsServiceResultCode result_code;
 };
 
@@ -33,12 +33,12 @@
 };
 
 struct SubAppsServiceListResultEntry {
-  string unhashed_app_id_path;
+  string manifest_id_path;
   string app_name;
 };
 
 struct SubAppsServiceRemoveResult {
-  string unhashed_app_id_path;
+  string manifest_id_path;
   SubAppsServiceResultCode result_code;
 };
 
@@ -60,5 +60,5 @@
 
   // Uninstalls the sub-apps represented by the given argument previously
   // installed via the `Add` method by the same app making the current API call.
-  Remove(array<string> unhashed_app_id_paths) => (array<SubAppsServiceRemoveResult> result);
+  Remove(array<string> manifest_id_paths) => (array<SubAppsServiceRemoveResult> result);
 };
diff --git a/third_party/blink/renderer/core/animation/BUILD.gn b/third_party/blink/renderer/core/animation/BUILD.gn
index 77c01692..08f992de 100644
--- a/third_party/blink/renderer/core/animation/BUILD.gn
+++ b/third_party/blink/renderer/core/animation/BUILD.gn
@@ -283,6 +283,8 @@
     "timeline_inset.h",
     "timeline_offset.cc",
     "timeline_offset.h",
+    "timeline_range.cc",
+    "timeline_range.h",
     "timing.cc",
     "timing.h",
     "timing_calculations.h",
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index 87bb21d..7401190 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -51,6 +51,7 @@
 #include "third_party/blink/renderer/core/animation/pending_animations.h"
 #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
 #include "third_party/blink/renderer/core/animation/scroll_timeline_util.h"
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
 #include "third_party/blink/renderer/core/animation/timing_calculations.h"
 #include "third_party/blink/renderer/core/css/cssom/css_unit_values.h"
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
@@ -947,12 +948,6 @@
 
   reset_current_time_on_resume_ = false;
 
-  // Set the timeline if needed for resolving timeline offsets in kefyrames.
-  if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect())) {
-    ViewTimeline* view_timeline = DynamicTo<ViewTimeline>(timeline);
-    keyframe_effect->Model()->SetViewTimelineIfRequired(view_timeline);
-  }
-
   if (timeline && !timeline->IsMonotonicallyIncreasing()) {
     ApplyPendingPlaybackRate();
     AnimationTimeDelta boundary_time =
@@ -1151,12 +1146,20 @@
   if (new_effect && new_effect->GetAnimation())
     new_effect->GetAnimation()->setEffect(nullptr);
 
+  // Clear timeline offsets for old effect.
+  ResolveTimelineOffsets(TimelineRange());
+
   // 6. Let the associated effect of the animation be the new effect.
   if (old_effect)
     old_effect->Detach();
   content_ = new_effect;
   if (new_effect)
     new_effect->Attach(this);
+
+  // Resolve timeline offsets for new effect.
+  ResolveTimelineOffsets(timeline() ? timeline()->GetTimelineRange()
+                                    : TimelineRange());
+
   SetOutdated();
 
   // 7. Run the procedure to update an animation’s finished state for animation
@@ -1175,9 +1178,6 @@
     if (KeyframeEffect* keyframe_effect =
             DynamicTo<KeyframeEffect>(new_effect)) {
       keyframe_effect->SetIgnoreCSSKeyframes();
-      // Set the timeline if needed for resolving timeline offsets in kefyrames.
-      ViewTimeline* view_timeline = DynamicTo<ViewTimeline>(timeline());
-      keyframe_effect->Model()->SetViewTimelineIfRequired(view_timeline);
     }
   }
 
@@ -2291,7 +2291,8 @@
   }
 
   double relative_offset =
-      boundary ? view_timeline->ToFractionalOffset(boundary.value())
+      boundary ? view_timeline->GetTimelineRange().ToFractionalOffset(
+                     boundary.value())
                : default_offset;
   AnimationTimeDelta duration = timeline_->GetDuration().value();
   start_time_ = duration * relative_offset;
@@ -2332,6 +2333,34 @@
   NotifyProbe();
 }
 
+namespace {
+
+double ResolveAnimationRange(const absl::optional<TimelineOffset>& offset,
+                             const TimelineRange& timeline_range,
+                             double default_value) {
+  if (offset.has_value()) {
+    return timeline_range.ToFractionalOffset(offset.value());
+  }
+  if (timeline_range.IsEmpty()) {
+    return 0;
+  }
+  return default_value;
+}
+
+}  // namespace
+
+bool Animation::ResolveTimelineOffsets(const TimelineRange& timeline_range) {
+  if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect())) {
+    double range_start = ResolveAnimationRange(
+        GetRangeStartInternal(), timeline_range, /* default_value */ 0);
+    double range_end = ResolveAnimationRange(
+        GetRangeEndInternal(), timeline_range, /* default_value */ 1);
+    return keyframe_effect->Model()->ResolveTimelineOffsets(
+        timeline_range, range_start, range_end);
+  }
+  return false;
+}
+
 void Animation::CancelAnimationOnCompositor() {
   if (HasActiveAnimationsOnCompositor()) {
     To<KeyframeEffect>(content_.Get())
diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h
index 03af6ee..413c7ca94 100644
--- a/third_party/blink/renderer/core/animation/animation.h
+++ b/third_party/blink/renderer/core/animation/animation.h
@@ -58,6 +58,7 @@
 class Element;
 class PaintArtifactCompositor;
 class TreeScope;
+class TimelineRange;
 
 class CORE_EXPORT Animation : public EventTargetWithInlineData,
                               public ActiveScriptWrappable<Animation>,
@@ -244,6 +245,8 @@
 
   void OnRangeUpdate();
 
+  bool ResolveTimelineOffsets(const TimelineRange&);
+
   Document* GetDocument() const;
 
   V8CSSNumberish* startTime() const;
diff --git a/third_party/blink/renderer/core/animation/animation_timeline.cc b/third_party/blink/renderer/core/animation/animation_timeline.cc
index e4dc23a9..4de390d 100644
--- a/third_party/blink/renderer/core/animation/animation_timeline.cc
+++ b/third_party/blink/renderer/core/animation/animation_timeline.cc
@@ -24,6 +24,7 @@
 void AnimationTimeline::AnimationAttached(Animation* animation) {
   DCHECK(!animations_.Contains(animation));
   animations_.insert(animation);
+  animation->ResolveTimelineOffsets(GetTimelineRange());
 }
 
 void AnimationTimeline::AnimationDetached(Animation* animation) {
@@ -31,6 +32,7 @@
   animations_needing_update_.erase(animation);
   if (animation->Outdated())
     outdated_animation_count_--;
+  animation->ResolveTimelineOffsets(GetTimelineRange());
 }
 
 bool CompareAnimations(const Member<Animation>& left,
diff --git a/third_party/blink/renderer/core/animation/animation_timeline.h b/third_party/blink/renderer/core/animation/animation_timeline.h
index 579a988..17a61539 100644
--- a/third_party/blink/renderer/core/animation/animation_timeline.h
+++ b/third_party/blink/renderer/core/animation/animation_timeline.h
@@ -9,6 +9,7 @@
 #include "base/time/time.h"
 #include "cc/animation/animation_timeline.h"
 #include "third_party/blink/renderer/core/animation/animation.h"
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -86,6 +87,9 @@
     return AnimationTimeDelta();
   }
 
+  // See class TimelineRange.
+  virtual TimelineRange GetTimelineRange() const { return TimelineRange(); }
+
   Document* GetDocument() const { return document_; }
   virtual void AnimationAttached(Animation*);
   virtual void AnimationDetached(Animation*);
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index a9d56e2..10a5fbb 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -219,16 +219,15 @@
     // inherited_time_.
     double relative_offset;
     if (timeline->IsViewTimeline()) {
+      TimelineRange timeline_range = timeline->GetTimelineRange();
       // TODO(kevers): Support animation-range for a non-view scroll-timeline.
       if (playback_rate_ >= 0) {
         relative_offset =
-            range_start ? DynamicTo<ViewTimeline>(timeline)->ToFractionalOffset(
-                              range_start.value())
+            range_start ? timeline_range.ToFractionalOffset(range_start.value())
                         : 0;
       } else {
         relative_offset =
-            range_end ? DynamicTo<ViewTimeline>(timeline)->ToFractionalOffset(
-                            range_end.value())
+            range_end ? timeline_range.ToFractionalOffset(range_end.value())
                       : 1;
       }
     } else {
@@ -314,13 +313,10 @@
 };
 
 // A keyframe can have an offset as a fixed percent or as a
-// <timeline-range percent>. In the later case, we resolve as a fixed
-// percent, though this value can change as layout changes. Setting the
-// resolved offset is best effort and will be fixed or ignored later if it
-// still cannot be resolved.
-bool SetOffsets(Keyframe& keyframe,
-                const KeyframeOffset& offset,
-                const AnimationTimeline* timeline) {
+// <timeline-range percent>. In the later case, we store the specified
+// offset on the Keyframe, and delay the resolution that offset until later.
+// (See ResolveTimelineOffset).
+bool SetOffsets(Keyframe& keyframe, const KeyframeOffset& offset) {
   if (offset.name == TimelineOffset::NamedRange::kNone) {
     keyframe.SetOffset(offset.percent);
     return false;
@@ -328,13 +324,7 @@
 
   TimelineOffset timeline_offset(offset.name,
                                  Length::Percent(100 * offset.percent));
-  if (timeline && timeline->IsViewTimeline() && timeline->IsResolved()) {
-    double fractional_offset =
-        To<ViewTimeline>(timeline)->ToFractionalOffset(timeline_offset);
-    keyframe.SetOffset(fractional_offset);
-  } else {
-    keyframe.SetOffset(absl::nullopt);
-  }
+  keyframe.SetOffset(absl::nullopt);
   keyframe.SetTimelineOffset(timeline_offset);
   return true;
 }
@@ -353,7 +343,6 @@
     TimingFunction* default_timing_function,
     WritingMode writing_mode,
     TextDirection text_direction,
-    AnimationTimeline* timeline,
     bool& has_named_range_keyframes) {
   StringKeyframeVector keyframes;
   const HeapVector<Member<StyleRuleKeyframe>>& style_keyframes =
@@ -364,7 +353,7 @@
     const Vector<KeyframeOffset>& offsets = style_keyframe->Keys();
     DCHECK(!offsets.empty());
 
-    has_named_range_keyframes |= SetOffsets(*keyframe, offsets[0], timeline);
+    has_named_range_keyframes |= SetOffsets(*keyframe, offsets[0]);
     keyframe->SetEasing(default_timing_function);
     const CSSPropertyValueSet& properties = style_keyframe->Properties();
     for (unsigned j = 0; j < properties.PropertyCount(); j++) {
@@ -410,7 +399,7 @@
     // The last keyframe specified at a given offset is used.
     for (wtf_size_t j = 1; j < offsets.size(); ++j) {
       StringKeyframe* clone = To<StringKeyframe>(keyframe->Clone());
-      has_named_range_keyframes |= SetOffsets(*clone, offsets[j], timeline);
+      has_named_range_keyframes |= SetOffsets(*clone, offsets[j]);
       keyframes.push_back(clone);
     }
   }
@@ -462,8 +451,7 @@
     const AtomicString& name,
     TimingFunction* default_timing_function,
     EffectModel::CompositeOperation composite,
-    size_t animation_index,
-    AnimationTimeline* timeline) {
+    size_t animation_index) {
   // The algorithm for constructing string keyframes for a CSS animation is
   // covered in the following spec:
   // https://drafts.csswg.org/css-animations-2/#keyframes
@@ -515,7 +503,7 @@
   keyframes = ProcessKeyframesRule(
       keyframes_rule, find_result.tree_scope, element.GetDocument(),
       parent_style, default_timing_function, writing_direction.GetWritingMode(),
-      writing_direction.Direction(), timeline, has_named_range_keyframes);
+      writing_direction.Direction(), has_named_range_keyframes);
 
   absl::optional<double> last_offset;
   wtf_size_t merged_frame_count = 0;
@@ -654,9 +642,6 @@
     UseCounter::Count(element.GetDocument(),
                       WebFeature::kCSSAnimationsStackedNeutralKeyframe);
   }
-  if (has_named_range_keyframes) {
-    model->SetViewTimelineIfRequired(DynamicTo<ViewTimeline>(timeline));
-  }
 
   return model;
 }
@@ -1735,7 +1720,7 @@
                   CreateKeyframeEffectModel(
                       resolver, element, animating_element, writing_direction,
                       parent_style, name, keyframe_timing_function.get(),
-                      composite, i, timeline),
+                      composite, i),
                   timing, animation_proxy),
               specified_timing, keyframes_rule, timeline,
               animation_data->PlayStateList(), range_start, range_end);
@@ -1757,7 +1742,7 @@
                 CreateKeyframeEffectModel(resolver, element, animating_element,
                                           writing_direction, parent_style, name,
                                           keyframe_timing_function.get(),
-                                          composite, i, timeline),
+                                          composite, i),
                 timing, animation_proxy),
             specified_timing, keyframes_rule, timeline,
             animation_data->PlayStateList(), range_start, range_end);
diff --git a/third_party/blink/renderer/core/animation/keyframe.cc b/third_party/blink/renderer/core/animation/keyframe.cc
index 717cef2..d33749ac 100644
--- a/third_party/blink/renderer/core/animation/keyframe.cc
+++ b/third_party/blink/renderer/core/animation/keyframe.cc
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range_offset.h"
 #include "third_party/blink/renderer/core/animation/effect_model.h"
 #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
-#include "third_party/blink/renderer/core/animation/view_timeline.h"
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
 #include "third_party/blink/renderer/core/css/cssom/css_unit_value.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
@@ -57,7 +57,7 @@
                            EffectModel::CompositeOperationToString(composite_));
 }
 
-bool Keyframe::ResolveTimelineOffset(const ViewTimeline* view_timeline,
+bool Keyframe::ResolveTimelineOffset(const TimelineRange& timeline_range,
                                      double range_start,
                                      double range_end) {
   if (!timeline_offset_) {
@@ -65,7 +65,7 @@
   }
 
   double relative_offset =
-      view_timeline->ToFractionalOffset(timeline_offset_.value());
+      timeline_range.ToFractionalOffset(timeline_offset_.value());
   double range = range_end - range_start;
   if (!range) {
     if (offset_) {
@@ -107,14 +107,4 @@
   return false;
 }
 
-bool Keyframe::ResetOffsetResolvedFromTimeline() {
-  if (!timeline_offset_.has_value()) {
-    return false;
-  }
-
-  offset_.reset();
-  computed_offset_ = kNullComputedOffset;
-  return true;
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/keyframe.h b/third_party/blink/renderer/core/animation/keyframe.h
index 21ea67e..70475bdaa 100644
--- a/third_party/blink/renderer/core/animation/keyframe.h
+++ b/third_party/blink/renderer/core/animation/keyframe.h
@@ -25,8 +25,8 @@
 class Element;
 class ComputedStyle;
 class CompositorKeyframeValue;
+class TimelineRange;
 class V8ObjectBuilder;
-class ViewTimeline;
 
 // A base class representing an animation keyframe.
 //
@@ -144,16 +144,12 @@
   // we sort by original index of the keyframe if specified.
   static bool LessThan(const Member<Keyframe>& a, const Member<Keyframe>& b);
 
-  // Compute the offset if dependent on a view timeline.  Returns true if the
+  // Compute the offset if dependent on a timeline range.  Returns true if the
   // offset changed.
-  bool ResolveTimelineOffset(const ViewTimeline* view_timeline,
+  bool ResolveTimelineOffset(const TimelineRange&,
                              double range_start,
                              double range_end);
 
-  // Resets the offset if it depends on a view timeline.  Returns true if the
-  // offset was reset.
-  bool ResetOffsetResolvedFromTimeline();
-
   // Add the properties represented by this keyframe to the given V8 object.
   //
   // Subclasses should override this to add the (property, value) pairs they
@@ -261,9 +257,9 @@
   absl::optional<double> computed_offset_;
   // Offsets of the form <name> <percent>. These offsets are layout depending
   // and need to be re-resolved on a style change affecting the corresponding
-  // view timeline. If the effect is not associated with an animation that is
-  // attached to a view-timeline, then the offset and computed offset will be
-  // null.
+  // timeline range. If the effect is not associated with an animation that is
+  // attached to a timeline with a non-empty timeline range,
+  // then the offset and computed offset will be null.
   absl::optional<TimelineOffset> timeline_offset_;
 
   // The original index in the keyframe list is used to resolve ties in the
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect_model.cc b/third_party/blink/renderer/core/animation/keyframe_effect_model.cc
index b7017e0..7453408 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect_model.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect_model.cc
@@ -378,54 +378,10 @@
   return changed;
 }
 
-void KeyframeEffectModelBase::SetViewTimelineIfRequired(
-    const ViewTimeline* timeline) {
-  if (view_timeline_ == timeline) {
-    return;
-  }
-
-  bool has_timeline_offset_in_keyframe = false;
-  for (const auto& keyframe : keyframes_) {
-    if (keyframe->GetTimelineOffset()) {
-      has_timeline_offset_in_keyframe = true;
-      break;
-    }
-  }
-
-  if (!has_timeline_offset_in_keyframe) {
-    // Keyframes are essentially immutable once the keyframe model is
-    // constructed. Thus, we should never be in a position where
-    // has_timeline_offset_in_keyframe changes from true to false between
-    // checks, and we should never have a set view timeline that needs to be
-    // cleared.
-    DCHECK(!view_timeline_);
-    return;
-  }
-
-  if (view_timeline_ && !timeline) {
-    // Clear offsets that are resolved from timeline offsets.
-    bool needs_update = false;
-    for (const auto& keyframe : keyframes_) {
-      needs_update |= keyframe->ResetOffsetResolvedFromTimeline();
-    }
-
-    if (needs_update) {
-      std::stable_sort(keyframes_.begin(), keyframes_.end(),
-                       &Keyframe::LessThan);
-      ClearCachedData();
-    }
-  }
-  view_timeline_ = timeline;
-  if (timeline) {
-    timeline->ResolveTimelineOffsets();
-  }
-}
-
 void KeyframeEffectModelBase::Trace(Visitor* visitor) const {
   visitor->Trace(keyframes_);
   visitor->Trace(keyframe_groups_);
   visitor->Trace(interpolation_effect_);
-  visitor->Trace(view_timeline_);
   EffectModel::Trace(visitor);
 }
 
@@ -537,21 +493,29 @@
   }
 }
 
-bool KeyframeEffectModelBase::ResolveTimelineOffsets(double range_start,
-                                                     double range_end) {
-  if (!view_timeline_) {
+bool KeyframeEffectModelBase::ResolveTimelineOffsets(
+    const TimelineRange& timeline_range,
+    double range_start,
+    double range_end) {
+  if (timeline_range == last_timeline_range_ &&
+      last_range_start_ == range_start && last_range_end_ == range_end) {
     return false;
   }
 
   bool needs_update = false;
   for (const auto& keyframe : keyframes_) {
     needs_update |=
-        keyframe->ResolveTimelineOffset(view_timeline_, range_start, range_end);
+        keyframe->ResolveTimelineOffset(timeline_range, range_start, range_end);
   }
   if (needs_update) {
     std::stable_sort(keyframes_.begin(), keyframes_.end(), &Keyframe::LessThan);
     ClearCachedData();
   }
+
+  last_timeline_range_ = timeline_range;
+  last_range_start_ = range_start;
+  last_range_end_ = range_end;
+
   return needs_update;
 }
 
@@ -560,6 +524,10 @@
   interpolation_effect_->Clear();
   last_fraction_ = std::numeric_limits<double>::quiet_NaN();
   needs_compositor_keyframes_snapshot_ = true;
+
+  last_timeline_range_ = absl::nullopt;
+  last_range_start_ = absl::nullopt;
+  last_range_end_ = absl::nullopt;
 }
 
 bool KeyframeEffectModelBase::IsReplaceOnly() const {
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect_model.h b/third_party/blink/renderer/core/animation/keyframe_effect_model.h
index fd5b00f..ce6b04b 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect_model.h
+++ b/third_party/blink/renderer/core/animation/keyframe_effect_model.h
@@ -40,6 +40,7 @@
 #include "third_party/blink/renderer/core/animation/interpolation_effect.h"
 #include "third_party/blink/renderer/core/animation/property_handle.h"
 #include "third_party/blink/renderer/core/animation/string_keyframe.h"
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
 #include "third_party/blink/renderer/core/animation/transition_keyframe.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/animation/timing_function.h"
@@ -171,12 +172,12 @@
 
   virtual KeyframeEffectModelBase* Clone() = 0;
 
-  void SetViewTimelineIfRequired(const ViewTimeline* timeline);
-
   // Ensure timeline offsets are properly resolved. If any of the offsets
   // changed, the keyframes are resorted and cached data is cleared. Returns
   // true if one or more offsets were affected.
-  bool ResolveTimelineOffsets(double range_start, double range_end);
+  bool ResolveTimelineOffsets(const TimelineRange&,
+                              double range_start,
+                              double range_end);
 
   void Trace(Visitor*) const override;
 
@@ -243,7 +244,11 @@
   mutable bool has_revert_ = false;
   mutable bool has_named_range_keyframes_ = false;
 
-  Member<const ViewTimeline> view_timeline_;
+  // The timeline and animation ranges last used to resolve
+  // named range offsets. (See ResolveTimelineOffsets).
+  absl::optional<TimelineRange> last_timeline_range_;
+  absl::optional<double> last_range_start_;
+  absl::optional<double> last_range_end_;
 
   friend class KeyframeEffectModelTest;
 };
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.cc b/third_party/blink/renderer/core/animation/scroll_timeline.cc
index f2adccf..f73c2a0 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.cc
@@ -111,26 +111,24 @@
   if (attachment) {
     attachments_.push_back(attachment);
   }
-  UpdateResolvedSource();
+}
+
+bool ScrollTimeline::IsResolved() const {
+  return ComputeIsResolved(ResolvedSource());
 }
 
 bool ScrollTimeline::IsActive() const {
   return timeline_state_snapshotted_.phase != TimelinePhase::kInactive;
 }
 
-bool ScrollTimeline::ComputeIsResolved() const {
-  if (!CurrentAttachment()) {
-    return false;
-  }
-  LayoutBox* layout_box =
-      resolved_source_ ? resolved_source_->GetLayoutBox() : nullptr;
-  return layout_box && layout_box->IsScrollContainer();
-}
-
 absl::optional<ScrollOffsets> ScrollTimeline::GetResolvedScrollOffsets() const {
   return timeline_state_snapshotted_.scroll_offsets;
 }
 
+absl::optional<ScrollOffsets> ScrollTimeline::GetResolvedViewOffsets() const {
+  return timeline_state_snapshotted_.view_offsets;
+}
+
 // TODO(crbug.com/1336260): Since phase can only be kActive or kInactive and
 // currentTime  can only be null if phase is inactive or before the first
 // snapshot we can probably drop phase.
@@ -160,22 +158,40 @@
   return MakeGarbageCollected<V8CSSNumberish>(CSSUnitValues::percent(100));
 }
 
+Element* ScrollTimeline::RetainingElement() const {
+  if (attachment_type_ == TimelineAttachment::kLocal) {
+    return CurrentAttachment()->GetReferenceElement();
+  }
+  // TODO(crbug.com/1425939): Remove this branch.
+  //
+  // The attachment concept is going away [1], at which point only local
+  // timelines will be reachable from JS, so we don't care about non-local
+  // timelines.
+  //
+  // A new concept similar to non-local timelines will be introduced, but such
+  // timelines will not be exposed to JS, and therefore the strong reference in
+  // blink::CSSAnimations is enough to keep the timeline alive.
+  //
+  // [1] https://github.com/w3c/csswg-drafts/issues/7759
+  return nullptr;
+}
+
 // TODO(crbug.com/1060384): This section is missing from the spec rewrite.
 // Resolved to remove the before and after phases in
 // https://github.com/w3c/csswg-drafts/issues/7240.
 // https://drafts.csswg.org/scroll-animations-1/#current-time-algorithm
 ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() {
   TimelineState state;
-  UpdateResolvedSource();
+  state.resolved_source = ComputeResolvedSource();
 
   // 1. If scroll timeline is inactive, return an unresolved time value.
   // https://github.com/WICG/scroll-animations/issues/31
   // https://wicg.github.io/scroll-animations/#current-time-algorithm
-  if (!IsResolved()) {
+  if (!ComputeIsResolved(state.resolved_source)) {
     return state;
   }
-  DCHECK(resolved_source_);
-  LayoutBox* layout_box = resolved_source_->GetLayoutBox();
+  DCHECK(state.resolved_source);
+  LayoutBox* layout_box = state.resolved_source->GetLayoutBox();
 
   // Layout box and scrollable area must exist since the timeline is active.
   DCHECK(layout_box);
@@ -272,6 +288,12 @@
   return AnimationTimeDelta();
 }
 
+TimelineRange ScrollTimeline::GetTimelineRange() const {
+  absl::optional<ScrollOffsets> scroll_offsets = GetResolvedScrollOffsets();
+  return scroll_offsets.has_value() ? TimelineRange(scroll_offsets.value())
+                                    : TimelineRange();
+}
+
 void ScrollTimeline::ServiceAnimations(TimingUpdateReason reason) {
   // When scroll timeline goes from inactive to active the animations may need
   // to be started and possibly composited.
@@ -294,33 +316,26 @@
 }
 
 bool ScrollTimeline::CheckIfNeedsValidation() {
-  bool resolved = ComputeIsResolved();
-  if (resolved != is_resolved_) {
+  Node* resolved_source = ComputeResolvedSource();
+
+  if (CheckIfSubjectNeedsValidation(resolved_source)) {
     return true;
   }
 
-  Node* source =
-      CurrentAttachment()
-          ? ResolveSource(CurrentAttachment()->ComputeSourceNoLayout())
-          : nullptr;
-  if (source != resolved_source_) {
-    return true;
-  }
-  DCHECK(!resolved || source);
+  absl::optional<ScrollOffset> min_scroll_offset;
+  absl::optional<ScrollOffset> max_scroll_offset;
 
-  if (resolved) {
-    LayoutBox* layout_box = source->GetLayoutBox();
+  if (ComputeIsResolved(resolved_source)) {
+    LayoutBox* layout_box = resolved_source->GetLayoutBox();
     PaintLayerScrollableArea* scrollable_area = layout_box->GetScrollableArea();
-    ScrollOffset min_scroll_offset = scrollable_area->MinimumScrollOffset();
-    ScrollOffset max_scroll_offset = scrollable_area->MaximumScrollOffset();
-    // Scroll range is cached during the snapshot update. If the values do not
-    // align, the timeline state is no longer valid.
-    if (min_scroll_offset != minimum_scroll_offset_ ||
-        max_scroll_offset != maximum_scroll_offset_) {
-      return true;
-    }
+    min_scroll_offset = scrollable_area->MinimumScrollOffset();
+    max_scroll_offset = scrollable_area->MaximumScrollOffset();
   }
-  return false;
+
+  // Scroll range is cached during the snapshot update. If the values do not
+  // align, the timeline state is no longer valid.
+  return min_scroll_offset != minimum_scroll_offset_ ||
+         max_scroll_offset != maximum_scroll_offset_;
 }
 
 void ScrollTimeline::ScheduleNextService() {
@@ -345,8 +360,9 @@
 }
 
 void ScrollTimeline::AnimationAttached(Animation* animation) {
-  if (resolved_source_ && !HasAnimations())
-    resolved_source_->RegisterScrollTimeline(this);
+  if (RetainingElement() && !HasAnimations()) {
+    RetainingElement()->RegisterScrollTimeline(this);
+  }
 
   AnimationTimeline::AnimationAttached(animation);
 }
@@ -354,39 +370,33 @@
 void ScrollTimeline::AnimationDetached(Animation* animation) {
   AnimationTimeline::AnimationDetached(animation);
 
-  if (resolved_source_ && !HasAnimations())
-    resolved_source_->UnregisterScrollTimeline(this);
+  if (RetainingElement() && !HasAnimations()) {
+    RetainingElement()->UnregisterScrollTimeline(this);
+  }
 }
 
 void ScrollTimeline::WorkletAnimationAttached(WorkletAnimationBase* worklet) {
-  if (!resolved_source_)
+  if (!ResolvedSource()) {
     return;
+  }
   attached_worklet_animations_.insert(worklet);
 }
 
-void ScrollTimeline::UpdateResolvedSource() {
+Node* ScrollTimeline::ComputeResolvedSource() const {
   if (!CurrentAttachment()) {
-    is_resolved_ = ComputeIsResolved();
-    return;
+    return nullptr;
   }
+  return ResolveSource(CurrentAttachment()->ComputeSourceNoLayout());
+}
 
-  Node* old_resolved_source = resolved_source_.Get();
-  resolved_source_ =
-      ResolveSource(CurrentAttachment()->ComputeSourceNoLayout());
-  is_resolved_ = ComputeIsResolved();
-
-  if (old_resolved_source == resolved_source_.Get() || !HasAnimations())
-    return;
-
-  if (old_resolved_source)
-    old_resolved_source->UnregisterScrollTimeline(this);
-
-  if (resolved_source_)
-    resolved_source_->RegisterScrollTimeline(this);
+bool ScrollTimeline::ComputeIsResolved(Node* resolved_source) {
+  LayoutBox* layout_box =
+      resolved_source ? resolved_source->GetLayoutBox() : nullptr;
+  return layout_box && layout_box->IsScrollContainer();
 }
 
 void ScrollTimeline::Trace(Visitor* visitor) const {
-  visitor->Trace(resolved_source_);
+  visitor->Trace(timeline_state_snapshotted_);
   visitor->Trace(attached_worklet_animations_);
   visitor->Trace(attachments_);
   AnimationTimeline::Trace(visitor);
@@ -422,13 +432,15 @@
 }
 
 bool ScrollTimeline::ValidateSnapshot() {
-  auto state = ComputeTimelineState();
-  if (ValidateTimelineOffsets() && timeline_state_snapshotted_ == state) {
+  // ValidateTimelineOffsets will resolve timeline offsets according to
+  // data in `timeline_state_snapshotted_`, so that needs to be updated
+  // before the call.
+  TimelineState old_state = timeline_state_snapshotted_;
+  timeline_state_snapshotted_ = ComputeTimelineState();
+  if (ValidateTimelineOffsets() && old_state == timeline_state_snapshotted_) {
     return true;
   }
 
-  timeline_state_snapshotted_ = state;
-
   // Mark an attached animation's target as dirty if the play state is running
   // or finished. Idle animations are not in effect and the effect of a paused
   // animation is not impacted by timeline staleness.
@@ -481,7 +493,7 @@
 
   ToScrollTimeline(compositor_timeline_.get())
       ->UpdateScrollerIdAndScrollOffsets(
-          scroll_timeline_util::GetCompositorScrollElementId(resolved_source_),
+          scroll_timeline_util::GetCompositorScrollElementId(ResolvedSource()),
           GetResolvedScrollOffsets());
 }
 
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.h b/third_party/blink/renderer/core/animation/scroll_timeline.h
index 9a7c6c60..9588f92c 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.h
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.h
@@ -68,7 +68,7 @@
 
   // ScrollTimeline is not resolved if source is null, does not currently
   // have a CSS layout box, or if its layout box is not a scroll container.
-  bool IsResolved() const override { return is_resolved_; }
+  bool IsResolved() const override;
 
   // ScrollTimeline is not active if not resolved or if the current time is
   // unresolved (e.g. before the timeline ticks).
@@ -89,6 +89,8 @@
     return CalculateIntrinsicIterationDuration(nullptr, timing);
   }
 
+  TimelineRange GetTimelineRange() const override;
+
   AnimationTimeDelta ZeroTime() override { return AnimationTimeDelta(); }
 
   void ServiceAnimations(TimingUpdateReason) override;
@@ -106,11 +108,14 @@
   // exists). This can differ from |source| when defaulting to the
   // Document's scrollingElement, and it may be null if the document was
   // removed before the ScrollTimeline was created.
-  Node* ResolvedSource() const { return resolved_source_; }
+  Node* ResolvedSource() const {
+    return timeline_state_snapshotted_.resolved_source;
+  }
 
-  // Return the latest resolved scroll offsets. This will be empty when
+  // Return the latest resolved scroll/view offsets. This will be empty when
   // timeline is inactive.
   absl::optional<ScrollOffsets> GetResolvedScrollOffsets() const;
+  absl::optional<ScrollOffsets> GetResolvedViewOffsets() const;
 
   float GetResolvedZoom() const { return timeline_state_snapshotted_.zoom; }
 
@@ -165,9 +170,13 @@
 
   PhaseAndTime CurrentPhaseAndTime() override;
 
-  void UpdateResolvedSource();
+  Node* ComputeResolvedSource() const;
+  static bool ComputeIsResolved(Node* resolved_source);
 
   struct TimelineState {
+    DISALLOW_NEW();
+
+   public:
     // TODO(crbug.com/1338167): Remove phase as it can be inferred from
     // current_time.
     TimelinePhase phase = TimelinePhase::kInactive;
@@ -177,6 +186,8 @@
     absl::optional<ScrollOffsets> view_offsets;
     // Zoom factor applied to the scroll offsets.
     float zoom = 1.0f;
+    // The scroller driving the timeline.
+    Member<Node> resolved_source;
 
     bool HasConsistentLayout(const TimelineState& other) const {
       return scroll_offsets == other.scroll_offsets && zoom == other.zoom &&
@@ -186,7 +197,12 @@
     bool operator==(const TimelineState& other) const {
       return phase == other.phase && current_time == other.current_time &&
              scroll_offsets == other.scroll_offsets && zoom == other.zoom &&
-             view_offsets == other.view_offsets;
+             view_offsets == other.view_offsets &&
+             resolved_source == other.resolved_source;
+    }
+
+    void Trace(blink::Visitor* visitor) const {
+      visitor->Trace(resolved_source);
     }
   };
 
@@ -206,6 +222,9 @@
   bool CheckIfNeedsValidation() override;
 
   virtual bool ValidateTimelineOffsets() { return true; }
+  virtual bool CheckIfSubjectNeedsValidation(Node* resolved_source) const {
+    return false;
+  }
 
   bool ComputeIsResolved() const;
 
@@ -213,11 +232,15 @@
   FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, MultipleScrollOffsetsClamping);
   FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, ResolveScrollOffsets);
 
+  // The retaining element is the element responsible for keeping
+  // the timeline alive while animations are attached.
+  //
+  // See Node::[Un]RegisterScrollTimeline.
+  Element* RetainingElement() const;
+
   TimelineState ComputeTimelineState();
 
   TimelineAttachment attachment_type_;
-  Member<Node> resolved_source_;
-  bool is_resolved_ = false;
   absl::optional<ScrollOffset> minimum_scroll_offset_;
   absl::optional<ScrollOffset> maximum_scroll_offset_;
 
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_test.cc b/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
index d7607e87..f30ba7e6 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
@@ -61,6 +61,13 @@
     GetPage().Animator().ServiceScriptedAnimations(new_time);
   }
 
+  wtf_size_t TimelinesCount() const {
+    return GetDocument()
+        .GetDocumentAnimations()
+        .GetTimelinesForTesting()
+        .size();
+  }
+
   wtf_size_t AnimationsCount() const {
     wtf_size_t count = 0;
     for (auto timeline :
@@ -73,30 +80,39 @@
 
 class TestScrollTimeline : public ScrollTimeline {
  public:
-  TestScrollTimeline(Document* document, Element* source)
+  TestScrollTimeline(Document* document, Element* source, bool snapshot = true)
       : ScrollTimeline(document,
                        TimelineAttachment::kLocal,
                        ScrollTimeline::ReferenceType::kSource,
                        source,
                        ScrollAxis::kVertical) {
-    UpdateSnapshot();
+    if (snapshot) {
+      UpdateSnapshot();
+    }
   }
 
   void Trace(Visitor* visitor) const override {
     ScrollTimeline::Trace(visitor);
   }
+
+  // UpdateSnapshot has 'protected' visibility.
+  void UpdateSnapshotForTesting() { UpdateSnapshot(); }
 };
 
 class TestViewTimeline : public ViewTimeline {
  public:
-  TestViewTimeline(Document* document, Element* subject)
+  TestViewTimeline(Document* document, Element* subject, bool snapshot = true)
       : ViewTimeline(document,
                      TimelineAttachment::kLocal,
                      subject,
                      ScrollAxis::kVertical,
                      TimelineInset()) {
-    UpdateSnapshot();
+    if (snapshot) {
+      UpdateSnapshot();
+    }
   }
+
+  void UpdateSnapshotForTesting() { UpdateSnapshot(); }
 };
 
 TEST_F(ScrollTimelineTest, CurrentTimeIsNullIfSourceIsNotScrollable) {
@@ -754,6 +770,60 @@
   EXPECT_EQ(0u, scroll_timeline->GetAnimations().size());
 }
 
+TEST_F(ScrollTimelineTest, WeakViewTimelines) {
+  SetBodyInnerHTML(R"HTML(
+    <div id='scroller'>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+    </div>
+  )HTML");
+
+  wtf_size_t base_count = TimelinesCount();
+
+  StaticElementList* list = GetDocument().QuerySelectorAll("#scroller > div");
+  ASSERT_TRUE(list);
+  EXPECT_EQ(10u, list->length());
+
+  HeapVector<Member<Animation>> animations;
+
+  for (wtf_size_t i = 0; i < list->length(); ++i) {
+    Element* element = list->item(i);
+    Animation* animation = CreateTestAnimation(
+        MakeGarbageCollected<TestViewTimeline>(&GetDocument(), element));
+    animation->play();
+    animations.push_back(animation);
+  }
+
+  SimulateFrame();
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(base_count + 10u, TimelinesCount());
+
+  // With all animations canceled, there should be no reason for the timelines
+  // to persist anymore.
+  for (const Member<Animation>& animation : animations) {
+    animation->cancel();
+  }
+  animations.clear();
+
+  // SimulateFrame needed to lose all strong references the animations,
+  // see ScrollTimelineTest.WeakReferences.
+  SimulateFrame();
+  UpdateAllLifecyclePhasesForTest();
+
+  ThreadState::Current()->CollectAllGarbageForTesting();
+
+  EXPECT_EQ(base_count, TimelinesCount());
+}
+
 TEST_F(ScrollTimelineTest, ScrollTimelineOffsetZoom) {
   using ScrollOffsets = cc::ScrollTimeline::ScrollOffsets;
 
@@ -864,4 +934,69 @@
   }
 }
 
+TEST_F(ScrollTimelineTest, ScrollTimelineGetTimelineRange) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #scroller {
+        overflow-y: auto;
+        width: 100px;
+        height: 100px;
+      }
+      .spacer {
+        height: 400px;
+      }
+    }
+    </style>
+    <div id='scroller'>
+      <div class='spacer'></div>
+    </div>
+  )HTML");
+
+  auto* timeline = MakeGarbageCollected<TestScrollTimeline>(
+      &GetDocument(), GetElementById("scroller"), /* snapshot */ false);
+
+  // GetTimelineRange before taking a snapshot.
+  EXPECT_TRUE(timeline->GetTimelineRange().IsEmpty());
+
+  timeline->UpdateSnapshotForTesting();
+  EXPECT_EQ(TimelineRange(TimelineRange::ScrollOffsets(0, 300)),
+            timeline->GetTimelineRange());
+}
+
+TEST_F(ScrollTimelineTest, ViewTimelineGetTimelineRange) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #scroller {
+        overflow-y: auto;
+        width: 100px;
+        height: 100px;
+        border: 20px solid black;
+      }
+      .spacer {
+        height: 200px;
+      }
+      #subject {
+        height: 100px;
+      }
+    }
+    </style>
+    <div id='scroller'>
+      <div class='spacer'></div>
+      <div id='subject'></div>
+      <div class='spacer'></div>
+    </div>
+  )HTML");
+
+  auto* timeline = MakeGarbageCollected<TestViewTimeline>(
+      &GetDocument(), GetElementById("subject"), /* snapshot */ false);
+
+  // GetTimelineRange before taking a snapshot.
+  EXPECT_TRUE(timeline->GetTimelineRange().IsEmpty());
+
+  timeline->UpdateSnapshotForTesting();
+  EXPECT_EQ(TimelineRange(TimelineRange::ScrollOffsets(100, 300),
+                          /* subject_size */ 100),
+            timeline->GetTimelineRange());
+}
+
 }  //  namespace blink
diff --git a/third_party/blink/renderer/core/animation/timeline_range.cc b/third_party/blink/renderer/core/animation/timeline_range.cc
new file mode 100644
index 0000000..a9bf48c
--- /dev/null
+++ b/third_party/blink/renderer/core/animation/timeline_range.cc
@@ -0,0 +1,116 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
+
+#include "third_party/blink/renderer/core/animation/timeline_offset.h"
+#include "third_party/blink/renderer/core/animation/timing_calculations.h"
+#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
+#include "third_party/blink/renderer/platform/geometry/length_functions.h"
+
+namespace blink {
+
+bool TimelineRange::IsEmpty() const {
+  return LessThanOrEqualToWithinEpsilon(offsets_.end - offsets_.start, 0.0);
+}
+
+double TimelineRange::ToFractionalOffset(
+    const TimelineOffset& timeline_offset) const {
+  if (IsEmpty()) {
+    // This is either a monotonic timeline or an inactive ScrollTimeline.
+    return 0.0;
+  }
+  double full_range_size = offsets_.end - offsets_.start;
+
+  ScrollOffsets range(0, 0);
+
+  if (subject_size_ == 0) {
+    // This is a non-view ScrollTimeline, or it can also be a ViewTimeline
+    // that happens have subject with size=0.
+    range = {offsets_.start, offsets_.end};
+  } else {
+    range = ConvertNamedRange(timeline_offset.name);
+  }
+
+  DCHECK_GT(full_range_size, 0);
+
+  double offset =
+      range.start + MinimumValueForLength(timeline_offset.offset,
+                                          LayoutUnit(range.end - range.start));
+  return (offset - offsets_.start) / full_range_size;
+}
+
+TimelineRange::ScrollOffsets TimelineRange::ConvertNamedRange(
+    NamedRange named_range) const {
+  // https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
+  double align_subject_start_view_end = offsets_.start;
+  double align_subject_end_view_start = offsets_.end;
+  double align_subject_start_view_start =
+      align_subject_end_view_start - subject_size_;
+  double align_subject_end_view_end =
+      align_subject_start_view_end + subject_size_;
+
+  switch (named_range) {
+    case TimelineOffset::NamedRange::kNone:
+    case TimelineOffset::NamedRange::kCover:
+      // Represents the full range of the view progress timeline:
+      //   0% progress represents the position at which the start border edge of
+      //   the element’s principal box coincides with the end edge of its view
+      //   progress visibility range.
+      //   100% progress represents the position at which the end border edge of
+      //   the element’s principal box coincides with the start edge of its view
+      //   progress visibility range.
+      return {align_subject_start_view_end, align_subject_end_view_start};
+
+    case TimelineOffset::NamedRange::kContain:
+      // Represents the range during which the principal box is either fully
+      // contained by, or fully covers, its view progress visibility range
+      // within the scrollport.
+      // 0% progress represents the earlier position at which:
+      //   1. the start border edge of the element’s principal box coincides
+      //      with the start edge of its view progress visibility range.
+      //   2. the end border edge of the element’s principal box coincides with
+      //      the end edge of its view progress visibility range.
+      // 100% progress represents the later position at which:
+      //   1. the start border edge of the element’s principal box coincides
+      //      with the start edge of its view progress visibility range.
+      //   2. the end border edge of the element’s principal box coincides with
+      //      the end edge of its view progress visibility range.
+      return {
+          std::min(align_subject_start_view_start, align_subject_end_view_end),
+          std::max(align_subject_start_view_start, align_subject_end_view_end)};
+
+    case TimelineOffset::NamedRange::kEntry:
+      // Represents the range during which the principal box is entering the
+      // view progress visibility range.
+      //   0% is equivalent to 0% of the cover range.
+      //   100% is equivalent to 0% of the contain range.
+      return {
+          align_subject_start_view_end,
+          std::min(align_subject_start_view_start, align_subject_end_view_end)};
+
+    case TimelineOffset::NamedRange::kEntryCrossing:
+      // Represents the range during which the principal box is crossing the
+      // entry edge of the viewport.
+      //   0% is equivalent to 0% of the cover range.
+      return {align_subject_start_view_end, align_subject_end_view_end};
+
+    case TimelineOffset::NamedRange::kExit:
+      // Represents the range during which the principal box is exiting the view
+      // progress visibility range.
+      //   0% is equivalent to 100% of the contain range.
+      //   100% is equivalent to 100% of the cover range.
+      return {
+          std::max(align_subject_start_view_start, align_subject_end_view_end),
+          align_subject_end_view_start};
+
+    case TimelineOffset::NamedRange::kExitCrossing:
+      // Represents the range during which the principal box is exiting the view
+      // progress visibility range.
+      //   100% is equivalent to 100% of the cover range.
+      return {align_subject_start_view_start, align_subject_end_view_start};
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/timeline_range.h b/third_party/blink/renderer/core/animation/timeline_range.h
new file mode 100644
index 0000000..98bcee1
--- /dev/null
+++ b/third_party/blink/renderer/core/animation/timeline_range.h
@@ -0,0 +1,73 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
+
+#include "cc/animation/scroll_timeline.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_timeline_range.h"
+#include "third_party/blink/renderer/core/core_export.h"
+
+namespace blink {
+
+struct TimelineOffset;
+
+// A TimelineRange represents a given scroll range within an associated
+// scroller's minimum/maximum scroll. This is useful for ViewTimelines
+// in particular, because they represent exactly that: a segment of a (non-view)
+// ScrollTimeline.
+//
+// The primary job of TimelineRange is to convert offsets within an animation
+// attachment range [1] (represented by TimelineOffset values) to fractional
+// offsets within the TimelineRange. See TimelineRange::ToFractionalOffset.
+//
+// It may be helpful to think about a TimelineRange (which is timeline-specific)
+// as a sub-range of the scroller's full range, and an animation attachment
+// range (which is animation specific) as a sub-range of that TimelineRange.
+//
+// - For ViewTimelines, the start/end offsets will correspond to the scroll
+//   range that would cause the scrollport to intersect with the subject
+//   element's box.
+// - For (non-view) ScrollTimelines, the start/end offset is always the
+//   same as the minimum/maximum scroll.
+// - For monotonic timelines, the TimelineRange is always empty.
+//
+// [1]
+// https://drafts.csswg.org/scroll-animations-1/#named-range-animation-declaration
+class CORE_EXPORT TimelineRange {
+ public:
+  using ScrollOffsets = cc::ScrollTimeline::ScrollOffsets;
+  using NamedRange = V8TimelineRange::Enum;
+
+  TimelineRange() = default;
+  // The subject_size is the size of the subject element for ViewTimelines.
+  // It should be zero for other timelines.
+  explicit TimelineRange(ScrollOffsets offsets, double subject_size = 0)
+      : offsets_(offsets), subject_size_(subject_size) {}
+
+  bool operator==(const TimelineRange& other) const {
+    return offsets_ == other.offsets_ && subject_size_ == other.subject_size_;
+  }
+
+  bool operator!=(const TimelineRange& other) const {
+    return !(*this == other);
+  }
+
+  bool IsEmpty() const;
+
+  // Converts an offset within some animation attachment range to a fractional
+  // offset within this TimelineRange.
+  double ToFractionalOffset(const TimelineOffset&) const;
+
+ private:
+  // https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
+  ScrollOffsets ConvertNamedRange(NamedRange) const;
+
+  ScrollOffsets offsets_;
+  double subject_size_ = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_TIMELINE_RANGE_H_
diff --git a/third_party/blink/renderer/core/animation/view_timeline.cc b/third_party/blink/renderer/core/animation/view_timeline.cc
index 92a6812b6..bb49a54 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.cc
+++ b/third_party/blink/renderer/core/animation/view_timeline.cc
@@ -235,11 +235,7 @@
               ? nullptr
               : MakeGarbageCollected<ViewTimelineAttachment>(subject,
                                                              axis,
-                                                             inset)) {
-  // Ensure that the timeline stays alive as long as the subject.
-  if (subject)
-    subject->RegisterScrollTimeline(this);
-}
+                                                             inset)) {}
 
 AnimationTimeDelta ViewTimeline::CalculateIntrinsicIterationDuration(
     const Animation* animation,
@@ -281,33 +277,46 @@
   return AnimationTimeDelta();
 }
 
+TimelineRange ViewTimeline::GetTimelineRange() const {
+  absl::optional<ScrollOffsets> scroll_offsets = GetResolvedScrollOffsets();
+  absl::optional<ScrollOffsets> view_offsets = GetResolvedViewOffsets();
+
+  if (!scroll_offsets.has_value() || !view_offsets.has_value()) {
+    return TimelineRange();
+  }
+
+  double subject_size = view_offsets->end - view_offsets->start;
+  return TimelineRange(scroll_offsets.value(), subject_size);
+}
+
 void ViewTimeline::CalculateOffsets(PaintLayerScrollableArea* scrollable_area,
                                     ScrollOrientation physical_orientation,
                                     TimelineState* state) const {
   // Do not call this method with an unresolved timeline.
   // Called from ScrollTimeline::ComputeTimelineState, which has safeguard.
   // Any new call sites will require a similar safeguard.
-  DCHECK(IsResolved());
+  DCHECK(state->resolved_source);
+  DCHECK(ComputeIsResolved(state->resolved_source));
   DCHECK(subject());
 
-  subject_position_ = SubjectPosition();
+  subject_position_ = SubjectPosition(state->resolved_source);
   subject_size_ = SubjectSize();
 
   DCHECK(subject_position_);
-  target_offset_ = physical_orientation == kHorizontalScroll
-                       ? subject_position_->x()
-                       : subject_position_->y();
+  double target_offset = physical_orientation == kHorizontalScroll
+                             ? subject_position_->x()
+                             : subject_position_->y();
 
   DCHECK(subject_size_);
+  double target_size;
   LayoutUnit viewport_size;
   if (physical_orientation == kHorizontalScroll) {
-    target_size_ = subject_size_->Width().ToDouble();
+    target_size = subject_size_->Width().ToDouble();
     viewport_size = scrollable_area->LayoutContentRect().Width();
   } else {
-    target_size_ = subject_size_->Height().ToDouble();
+    target_size = subject_size_->Height().ToDouble();
     viewport_size = scrollable_area->LayoutContentRect().Height();
   }
-  viewport_size_ = viewport_size.ToDouble();
 
   Element* source = CurrentAttachment()->ComputeSourceNoLayout();
   DCHECK(source);
@@ -334,16 +343,18 @@
   // source box, whereas "start offset" refers to the start of the timeline,
   // and similarly for end side/offset.
   // [1] https://drafts.csswg.org/css-writing-modes-4/#css-start
-  end_side_inset_ = ComputeInset(inset.GetEnd(), viewport_size);
-  start_side_inset_ = ComputeInset(inset.GetStart(), viewport_size);
+  double end_side_inset = ComputeInset(inset.GetEnd(), viewport_size);
+  double start_side_inset = ComputeInset(inset.GetStart(), viewport_size);
 
-  double start_offset = target_offset_ - viewport_size_ + end_side_inset_;
-  double end_offset = target_offset_ + target_size_ - start_side_inset_;
+  double viewport_size_double = viewport_size.ToDouble();
+
+  double start_offset = target_offset - viewport_size_double + end_side_inset;
+  double end_offset = target_offset + target_size - start_side_inset;
 
   state->scroll_offsets =
       absl::make_optional<ScrollOffsets>(start_offset, end_offset);
   state->view_offsets = absl::make_optional<ScrollOffsets>(
-      target_offset_, target_offset_ + target_size_);
+      target_offset, target_offset + target_size);
 }
 
 absl::optional<LayoutSize> ViewTimeline::SubjectSize() const {
@@ -358,12 +369,13 @@
   return subject_layout_box->Size();
 }
 
-absl::optional<gfx::PointF> ViewTimeline::SubjectPosition() const {
-  if (!subject() || !ResolvedSource()) {
+absl::optional<gfx::PointF> ViewTimeline::SubjectPosition(
+    Node* resolved_source) const {
+  if (!subject() || !resolved_source) {
     return absl::nullopt;
   }
   LayoutBox* subject_layout_box = subject()->GetLayoutBox();
-  LayoutBox* source_layout_box = ResolvedSource()->GetLayoutBox();
+  LayoutBox* source_layout_box = resolved_source->GetLayoutBox();
   if (!subject_layout_box || !source_layout_box) {
     return absl::nullopt;
   }
@@ -469,101 +481,7 @@
 
 double ViewTimeline::ToFractionalOffset(
     const TimelineOffset& timeline_offset) const {
-  // https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
-  double align_subject_start_view_end =
-      target_offset_ - viewport_size_ + end_side_inset_;
-  double align_subject_end_view_start =
-      target_offset_ + target_size_ - start_side_inset_;
-  double align_subject_start_view_start =
-      align_subject_end_view_start - target_size_;
-  double align_subject_end_view_end =
-      align_subject_start_view_end + target_size_;
-  // Timeline is inactive if scroll range is zero.
-  double range = align_subject_end_view_start - align_subject_start_view_end;
-  if (!range) {
-    return 0;
-  }
-
-  double range_start = 0;
-  double range_end = 0;
-  switch (timeline_offset.name) {
-    case TimelineOffset::NamedRange::kNone:
-    case TimelineOffset::NamedRange::kCover:
-      // Represents the full range of the view progress timeline:
-      //   0% progress represents the position at which the start border edge of
-      //   the element’s principal box coincides with the end edge of its view
-      //   progress visibility range.
-      //   100% progress represents the position at which the end border edge of
-      //   the element’s principal box coincides with the start edge of its view
-      //   progress visibility range.
-      range_start = align_subject_start_view_end;
-      range_end = align_subject_end_view_start;
-      break;
-
-    case TimelineOffset::NamedRange::kContain:
-      // Represents the range during which the principal box is either fully
-      // contained by, or fully covers, its view progress visibility range
-      // within the scrollport.
-      // 0% progress represents the earlier position at which:
-      //   1. the start border edge of the element’s principal box coincides
-      //      with the start edge of its view progress visibility range.
-      //   2. the end border edge of the element’s principal box coincides with
-      //      the end edge of its view progress visibility range.
-      // 100% progress represents the later position at which:
-      //   1. the start border edge of the element’s principal box coincides
-      //      with the start edge of its view progress visibility range.
-      //   2. the end border edge of the element’s principal box coincides with
-      //      the end edge of its view progress visibility range.
-      range_start =
-          std::min(align_subject_start_view_start, align_subject_end_view_end);
-      range_end =
-          std::max(align_subject_start_view_start, align_subject_end_view_end);
-      break;
-
-    case TimelineOffset::NamedRange::kEntry:
-      // Represents the range during which the principal box is entering the
-      // view progress visibility range.
-      //   0% is equivalent to 0% of the cover range.
-      //   100% is equivalent to 0% of the contain range.
-      range_start = align_subject_start_view_end;
-      range_end =
-          std::min(align_subject_start_view_start, align_subject_end_view_end);
-      break;
-
-    case TimelineOffset::NamedRange::kEntryCrossing:
-      // Represents the range during which the principal box is crossing the
-      // entry edge of the viewport.
-      //   0% is equivalent to 0% of the cover range.
-      range_start = align_subject_start_view_end;
-      range_end = align_subject_end_view_end;
-      break;
-
-    case TimelineOffset::NamedRange::kExit:
-      // Represents the range during which the principal box is exiting the view
-      // progress visibility range.
-      //   0% is equivalent to 100% of the contain range.
-      //   100% is equivalent to 100% of the cover range.
-      range_start =
-          std::max(align_subject_start_view_start, align_subject_end_view_end);
-      range_end = align_subject_end_view_start;
-      break;
-
-    case TimelineOffset::NamedRange::kExitCrossing:
-      // Represents the range during which the principal box is exiting the view
-      // progress visibility range.
-      //   100% is equivalent to 100% of the cover range.
-      range_start = align_subject_start_view_start;
-      range_end = align_subject_end_view_start;
-      break;
-  }
-
-  DCHECK(range_end >= range_start);
-  DCHECK_GT(range, 0);
-
-  double offset =
-      range_start + MinimumValueForLength(timeline_offset.offset,
-                                          LayoutUnit(range_end - range_start));
-  return (offset - align_subject_start_view_end) / range;
+  return GetTimelineRange().ToFractionalOffset(timeline_offset);
 }
 
 CSSNumericValue* ViewTimeline::startOffset() const {
@@ -594,50 +512,20 @@
   return !has_keyframe_update;
 }
 
-bool ViewTimeline::CheckIfNeedsValidation() {
-  if (ScrollTimeline::CheckIfNeedsValidation()) {
-    return true;
-  }
-
-  if (subject_size_ != SubjectSize()) {
-    return true;
-  }
-
-  if (subject_position_ != SubjectPosition()) {
-    return true;
-  }
-
-  return false;
+bool ViewTimeline::CheckIfSubjectNeedsValidation(Node* resolved_source) const {
+  return subject_size_ != SubjectSize() ||
+         subject_position_ != SubjectPosition(resolved_source);
 }
 
 bool ViewTimeline::ResolveTimelineOffsets() const {
+  TimelineRange timeline_range = GetTimelineRange();
   bool has_keyframe_update = false;
   for (Animation* animation : GetAnimations()) {
-    if (auto* effect = DynamicTo<KeyframeEffect>(animation->effect())) {
-      double range_start =
-          animation->GetRangeStartInternal()
-              ? ToFractionalOffset(animation->GetRangeStartInternal().value())
-              : 0;
-      double range_end =
-          animation->GetRangeEndInternal()
-              ? ToFractionalOffset(animation->GetRangeEndInternal().value())
-              : 1;
-      if (effect->Model()->ResolveTimelineOffsets(range_start, range_end)) {
-        has_keyframe_update = true;
-      }
-    }
+    has_keyframe_update |= animation->ResolveTimelineOffsets(timeline_range);
   }
   return has_keyframe_update;
 }
 
-Animation* ViewTimeline::Play(AnimationEffect* effect,
-                              ExceptionState& exception_state) {
-  if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect)) {
-    keyframe_effect->Model()->SetViewTimelineIfRequired(this);
-  }
-  return AnimationTimeline::Play(effect, exception_state);
-}
-
 void ViewTimeline::Trace(Visitor* visitor) const {
   visitor->Trace(style_dependant_start_inset_);
   visitor->Trace(style_dependant_end_inset_);
diff --git a/third_party/blink/renderer/core/animation/view_timeline.h b/third_party/blink/renderer/core/animation/view_timeline.h
index 78017ca..a5afada 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.h
+++ b/third_party/blink/renderer/core/animation/view_timeline.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
 #include "third_party/blink/renderer/core/animation/timeline_inset.h"
+#include "third_party/blink/renderer/core/animation/timeline_range.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -50,6 +51,8 @@
       const absl::optional<TimelineOffset>& rangeEnd,
       const Timing&) override;
 
+  TimelineRange GetTimelineRange() const override;
+
   // IDL API implementation.
   Element* subject() const;
 
@@ -60,18 +63,11 @@
 
   const TimelineInset& GetInset() const;
 
-  // Converts a delay that is expressed as a (phase,percentage) pair to
-  // a fractional offset.
-  double ToFractionalOffset(const TimelineOffset& timeline_offset) const;
-
   CSSNumericValue* startOffset() const;
   CSSNumericValue* endOffset() const;
 
   bool ResolveTimelineOffsets() const;
 
-  Animation* Play(AnimationEffect*,
-                  ExceptionState& = ASSERT_NO_EXCEPTION) override;
-
   void Trace(Visitor*) const override;
 
  protected:
@@ -81,20 +77,16 @@
 
   // ScrollSnapshotClient:
   void UpdateSnapshot() override;
-  bool CheckIfNeedsValidation() override;
 
   bool ValidateTimelineOffsets() override;
+  bool CheckIfSubjectNeedsValidation(Node* resolved_source) const override;
 
   absl::optional<LayoutSize> SubjectSize() const;
-  absl::optional<gfx::PointF> SubjectPosition() const;
+  absl::optional<gfx::PointF> SubjectPosition(Node* resolved_source) const;
 
  private:
-  // Cache values to make timeline phase conversions more efficient.
-  mutable double target_offset_;
-  mutable double target_size_;
-  mutable double viewport_size_;
-  mutable double start_side_inset_;
-  mutable double end_side_inset_;
+  double ToFractionalOffset(const TimelineOffset& timeline_offset) const;
+
   // Cache values for post-layout validation check.  If the subject position or
   // size changes, then the range boundaries are stale.
   mutable absl::optional<LayoutSize> subject_size_;
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.cc b/third_party/blink/renderer/core/execution_context/execution_context.cc
index 1f694184..e7941e1 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.cc
+++ b/third_party/blink/renderer/core/execution_context/execution_context.cc
@@ -105,39 +105,6 @@
 }
 
 // static
-ExecutionContext* ExecutionContext::ForCurrentRealm(
-    const v8::FunctionCallbackInfo<v8::Value>& info) {
-  return ToExecutionContext(info.GetIsolate()->GetCurrentContext());
-}
-
-// static
-ExecutionContext* ExecutionContext::ForCurrentRealm(
-    const v8::PropertyCallbackInfo<v8::Value>& info) {
-  auto ctx = info.GetIsolate()->GetCurrentContext();
-  if (ctx.IsEmpty())
-    return nullptr;
-  return ToExecutionContext(ctx);
-}
-
-// static
-ExecutionContext* ExecutionContext::ForRelevantRealm(
-    const v8::FunctionCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::Context> context;
-  if (!info.Holder()->GetCreationContext().ToLocal(&context))
-    return nullptr;
-  return ToExecutionContext(context);
-}
-
-// static
-ExecutionContext* ExecutionContext::ForRelevantRealm(
-    const v8::PropertyCallbackInfo<v8::Value>& info) {
-  v8::Local<v8::Context> context;
-  if (!info.Holder()->GetCreationContext().ToLocal(&context))
-    return nullptr;
-  return ToExecutionContext(context);
-}
-
-// static
 CodeCacheHost* ExecutionContext::GetCodeCacheHostFromContext(
     ExecutionContext* execution_context) {
   DCHECK_NE(execution_context, nullptr);
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h
index 4f40837..55a7d64 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.h
+++ b/third_party/blink/renderer/core/execution_context/execution_context.h
@@ -147,17 +147,6 @@
   static ExecutionContext* From(const ScriptState*);
   static ExecutionContext* From(v8::Local<v8::Context>);
 
-  // Returns the ExecutionContext of the current realm.
-  static ExecutionContext* ForCurrentRealm(
-      const v8::FunctionCallbackInfo<v8::Value>&);
-  static ExecutionContext* ForCurrentRealm(
-      const v8::PropertyCallbackInfo<v8::Value>&);
-  // Returns the ExecutionContext of the relevant realm for the receiver object.
-  static ExecutionContext* ForRelevantRealm(
-      const v8::FunctionCallbackInfo<v8::Value>&);
-  static ExecutionContext* ForRelevantRealm(
-      const v8::PropertyCallbackInfo<v8::Value>&);
-
   // Returns the CodeCacheHost interface associated with the execution
   // context. This could return nullptr if there is no CodeCacheHost associated
   // with the current execution context.
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 0a99f27..d498ed96 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
@@ -383,6 +383,10 @@
     return nullptr;
   }
 
+  // Tell the debugger about the attempt to create a canvas context
+  // even if it will fail, to ease debugging.
+  probe::DidCreateCanvasContext(&GetDocument());
+
   // If this context is cross-origin, it should prefer to use the low-power GPU
   LocalFrame* frame = GetDocument().GetFrame();
   CanvasContextCreationAttributesCore recomputed_attributes = attributes;
@@ -403,8 +407,6 @@
 
   context_creation_was_blocked_ = false;
 
-  probe::DidCreateCanvasContext(&GetDocument());
-
   if (IsWebGL())
     UpdateMemoryUsage();
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
index 1091208b..a630c0f 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
@@ -808,6 +808,12 @@
       true);
 }
 
+void InspectorDOMDebuggerAgent::DidCreateOffscreenCanvasContext() {
+  PauseOnNativeEventIfNeeded(
+      PreparePauseOnNativeEventData(kCanvasContextCreatedEventName, nullptr),
+      true);
+}
+
 void InspectorDOMDebuggerAgent::DidAddBreakpoint() {
   if (enabled_.Get())
     return;
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.h
index 613aba3..aa4d8800 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.h
@@ -107,6 +107,7 @@
   void WillModifyDOMAttr(Element*, const AtomicString&, const AtomicString&);
   void WillSendXMLHttpOrFetchNetworkRequest(const String& url);
   void DidCreateCanvasContext();
+  void DidCreateOffscreenCanvasContext();
   void DidFireWebGLError(const String& error_name);
   void DidFireWebGLWarning();
   void DidFireWebGLErrorOrWarning(const String& message);
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index a1fdcc8..86bdfedc 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1920,8 +1920,31 @@
   RecalcVisualOverflow();
 }
 
-#if DCHECK_IS_ON()
 void LayoutObject::InvalidateVisualOverflow() {
+  if (!IsInLayoutNGInlineFormattingContext() && !IsLayoutNGObject() &&
+      !IsLayoutBlock() && !NeedsLayout()) {
+    // TODO(crbug.com/1128199): This is still needed because
+    // RecalcVisualOverflow() does not actually compute the visual overflow
+    // for inline elements (legacy layout). However in LayoutNG
+    // RecalcInlineChildrenInkOverflow() is called and visual overflow is
+    // recomputed properly so we don't need this (see crbug.com/1043927).
+    SetNeedsLayoutAndIntrinsicWidthsRecalc(
+        layout_invalidation_reason::kStyleChange);
+  } else {
+    if (IsInLayoutNGInlineFormattingContext() && !NeedsLayout()) {
+      if (auto* text = DynamicTo<LayoutText>(this)) {
+        text->InvalidateVisualOverflow();
+      }
+    }
+    PaintingLayer()->SetNeedsVisualOverflowRecalc();
+    // TODO(crbug.com/1385848): This looks like an over-invalidation.
+    // visual overflow change should not require checking for layout change.
+    SetShouldCheckForPaintInvalidation();
+  }
+}
+
+#if DCHECK_IS_ON()
+void LayoutObject::InvalidateVisualOverflowForDCheck() {
   if (auto* box = DynamicTo<LayoutBox>(this)) {
     for (const NGPhysicalBoxFragment& fragment : box->PhysicalFragments())
       fragment.GetMutableForPainting().InvalidateInkOverflow();
@@ -2600,27 +2623,9 @@
   }
 
   if (diff.NeedsRecomputeVisualOverflow()) {
-    if (!IsInLayoutNGInlineFormattingContext() && !IsLayoutNGObject() &&
-        !IsLayoutBlock() && !NeedsLayout()) {
-      // TODO(crbug.com/1128199): This is still needed because
-      // RecalcVisualOverflow() does not actually compute the visual overflow
-      // for inline elements (legacy layout). However in LayoutNG
-      // RecalcInlineChildrenInkOverflow() is called and visual overflow is
-      // recomputed properly so we don't need this (see crbug.com/1043927).
-      SetNeedsLayoutAndIntrinsicWidthsRecalc(
-          layout_invalidation_reason::kStyleChange);
-    } else {
-      if (IsInLayoutNGInlineFormattingContext() && !NeedsLayout()) {
-        if (auto* text = DynamicTo<LayoutText>(this))
-          text->InvalidateVisualOverflow();
-      }
-      PaintingLayer()->SetNeedsVisualOverflowRecalc();
-      // TODO(crbug.com/1385848): This looks like an over-invalidation.
-      // visual overflow change should not require checking for layout change.
-      SetShouldCheckForPaintInvalidation();
-    }
-#if DCHECK_IS_ON()
     InvalidateVisualOverflow();
+#if DCHECK_IS_ON()
+    InvalidateVisualOverflowForDCheck();
 #endif
   }
 
@@ -4944,7 +4949,7 @@
       box_model_object->Layer()->SetNeedsVisualOverflowRecalc();
   }
 #if DCHECK_IS_ON()
-  InvalidateVisualOverflow();
+  InvalidateVisualOverflowForDCheck();
 #endif
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index ac06143..895312fc 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -2209,6 +2209,10 @@
 
   virtual RecalcLayoutOverflowResult RecalcLayoutOverflow();
 
+  // Invalidate visual overflow, using a method that varies based
+  // the object type and state of layout.
+  void InvalidateVisualOverflow();
+
   // Recalculates visual overflow for this object and non-self-painting
   // PaintLayer descendants.
   virtual void RecalcVisualOverflow();
@@ -2216,7 +2220,7 @@
 #if DCHECK_IS_ON()
   // Enables DCHECK to ensure that the visual overflow for |this| is computed.
   // The actual invalidation is maintained in |PaintLayer|.
-  void InvalidateVisualOverflow();
+  void InvalidateVisualOverflowForDCheck();
 #endif
 
   // Subclasses must reimplement this method to compute the size and position
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 15607229..0ccd165 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -1663,7 +1663,7 @@
         if (UNLIKELY(layout_box->HasSelfPaintingLayer()))
           layout_box->Layer()->SetNeedsVisualOverflowRecalc();
 #if DCHECK_IS_ON()
-        layout_box->InvalidateVisualOverflow();
+        layout_box->InvalidateVisualOverflowForDCheck();
 #endif
         continue;
       }
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
index 2f277a8..79f9b80 100644
--- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
+++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
@@ -32,6 +32,7 @@
 #include "third_party/blink/renderer/core/html/canvas/ukm_parameters.h"
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/workers/dedicated_worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/no_alloc_direct_call_host.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
@@ -330,6 +331,10 @@
       return nullptr;
     }
   } else {
+    // Tell the debugger about the attempt to create an offscreen
+    // canvas context even if it will fail, to ease debugging.
+    probe::DidCreateOffscreenCanvasContext(this);
+
     CanvasContextCreationAttributesCore recomputed_attributes = attributes;
     if (!allow_high_performance_power_preference_)
       recomputed_attributes.power_preference = "low-power";
diff --git a/third_party/blink/renderer/core/paint/box_border_painter.cc b/third_party/blink/renderer/core/paint/box_border_painter.cc
index 8545ca4..79c0411 100644
--- a/third_party/blink/renderer/core/paint/box_border_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_border_painter.cc
@@ -870,15 +870,20 @@
  private:
   void BuildOpacityGroups(const BoxBorderPainter& border_painter,
                           const Vector<BoxSide, 4>& sorted_sides) {
-    unsigned current_alpha = 0;
+    float current_alpha = 0.0f;
     for (BoxSide side : sorted_sides) {
       const BorderEdge& edge = border_painter.Edge(side);
-      const unsigned edge_alpha = edge.GetColor().AlphaAsInteger();
+      const float edge_alpha = edge.GetColor().Alpha();
 
-      DCHECK_GT(edge_alpha, 0u);
+      DCHECK_GT(edge_alpha, 0.0f);
       DCHECK_GE(edge_alpha, current_alpha);
+      // TODO(crbug.com/1434423): This float comparison looks very brittle. We
+      // need to deduce the original intention of the code here. Also, this path
+      // is clearly un-tested and caused some serious regressions when touched.
+      // See crbug.com/1445288
       if (edge_alpha != current_alpha) {
-        opacity_groups.push_back(OpacityGroup(edge_alpha));
+        opacity_groups.push_back(
+            OpacityGroup(edge.GetColor().AlphaAsInteger()));
         current_alpha = edge_alpha;
       }
 
@@ -1077,7 +1082,7 @@
       continue;
     }
 
-    DCHECK_GT(edge.GetColor().AlphaAsInteger(), 0);
+    DCHECK(!edge.GetColor().IsFullyTransparent());
 
     visible_edge_count_++;
     visible_edge_set_ |= EdgeFlagForSide(static_cast<BoxSide>(i));
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index a3a3b1d..144f7b65 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -698,7 +698,7 @@
 void PaintLayer::SetNeedsVisualOverflowRecalc() {
   DCHECK(IsSelfPaintingLayer());
 #if DCHECK_IS_ON()
-  GetLayoutObject().InvalidateVisualOverflow();
+  GetLayoutObject().InvalidateVisualOverflowForDCheck();
 #endif
   needs_visual_overflow_recalc_ = true;
   // |MarkAncestorChainForFlagsUpdate| will cause a paint property update which
diff --git a/third_party/blink/renderer/core/probe/core_probes.h b/third_party/blink/renderer/core/probe/core_probes.h
index 78d334d..d18cec6 100644
--- a/third_party/blink/renderer/core/probe/core_probes.h
+++ b/third_party/blink/renderer/core/probe/core_probes.h
@@ -36,6 +36,7 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/ad_tracker.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource.h"
 
 namespace network {
@@ -144,6 +145,12 @@
                       : nullptr;
 }
 
+inline CoreProbeSink* ToCoreProbeSink(OffscreenCanvas* offscreen_canvas) {
+  return offscreen_canvas
+             ? ToCoreProbeSink(offscreen_canvas->GetExecutionContext())
+             : nullptr;
+}
+
 CORE_EXPORT void AllAsyncTasksCanceled(ExecutionContext*);
 
 }  // namespace probe
diff --git a/third_party/blink/renderer/core/probe/core_probes.json5 b/third_party/blink/renderer/core/probe/core_probes.json5
index 5f78387..40ac2dfc1 100644
--- a/third_party/blink/renderer/core/probe/core_probes.json5
+++ b/third_party/blink/renderer/core/probe/core_probes.json5
@@ -100,6 +100,7 @@
         "BreakableLocation",
         "CharacterDataModified",
         "DidCreateCanvasContext",
+        "DidCreateOffscreenCanvasContext",
         "DidFireWebGLError",
         "DidFireWebGLErrorOrWarning",
         "DidFireWebGLWarning",
diff --git a/third_party/blink/renderer/core/probe/core_probes.pidl b/third_party/blink/renderer/core/probe/core_probes.pidl
index 8507d9d4..ec53a91 100644
--- a/third_party/blink/renderer/core/probe/core_probes.pidl
+++ b/third_party/blink/renderer/core/probe/core_probes.pidl
@@ -86,6 +86,7 @@
   void WillPopShadowRoot([Keep] Element* host, ShadowRoot*);
   void WillSendXMLHttpOrFetchNetworkRequest(ExecutionContext*, const String& url);
   void DidCreateCanvasContext(Document*);
+  void DidCreateOffscreenCanvasContext(OffscreenCanvas*);
   void DidFireWebGLError(Element*, const String& error_name);
   void DidFireWebGLWarning(Element*);
   void DidFireWebGLErrorOrWarning(Element*, const String& message);
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
index 949d4104..2191ba0 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
@@ -376,6 +376,8 @@
     element_data->visual_overflow_rect_in_layout_space =
         PhysicalRect::EnclosingRect(
             transition_state_element.overflow_rect_in_layout_space);
+    element_data->captured_rect_in_layout_space =
+        transition_state_element.captured_rect_in_layout_space;
 
     element_data->CacheGeometryState();
 
@@ -1505,9 +1507,10 @@
     element.snapshot_id = element_data->old_snapshot_id;
     element.paint_order = element_data->element_index;
     element.is_root = false;
+    element.captured_rect_in_layout_space =
+        element_data->captured_rect_in_layout_space;
 
     // TODO(khushalsagar): Also writing mode.
-    // TODO(vmpstr): Also captured_rect_in_layout_space.
 
     DCHECK(!old_root_data_ || element.paint_order > 0);
   }
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser.cc b/third_party/blink/renderer/modules/manifest/manifest_parser.cc
index b519150..8c4f48c8 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser.cc
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser.cc
@@ -452,12 +452,11 @@
   return description.has_value() ? *description : String();
 }
 
-String ManifestParser::ParseId(const JSONObject* object,
-                               const KURL& start_url) {
+KURL ManifestParser::ParseId(const JSONObject* object, const KURL& start_url) {
   if (!start_url.IsValid()) {
     ManifestUmaUtil::ParseIdResult(
         ManifestUmaUtil::ParseIdResultType::kInvalidStartUrl);
-    return String();
+    return KURL();
   }
   KURL start_url_origin = KURL(SecurityOrigin::Create(start_url)->ToString());
 
@@ -474,9 +473,7 @@
     id = start_url;
   }
   id.RemoveFragmentIdentifier();
-  // TODO(https://crbug.com/1231765): rename the field to relative_id to reflect
-  // the actual value.
-  return id.GetString().Substring(id.PathStart() + 1);
+  return id;
 }
 
 KURL ManifestParser::ParseStartURL(const JSONObject* object) {
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser.h b/third_party/blink/renderer/modules/manifest/manifest_parser.h
index 13615a8..7cfa1f49 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser.h
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser.h
@@ -155,7 +155,7 @@
   String ParseDescription(const JSONObject* object);
 
   // Parses the 'id' field of the manifest.
-  String ParseId(const JSONObject* object, const KURL& start_url);
+  KURL ParseId(const JSONObject* object, const KURL& start_url);
 
   // Parses the 'scope' field of the manifest, as defined in:
   // https://w3c.github.io/manifest/#scope-member. Returns the parsed KURL if
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
index 207a2fd..065b3eb 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/test/scoped_feature_list.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
@@ -17,6 +18,7 @@
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_uchar.h"
 
 namespace blink {
 
@@ -302,21 +304,21 @@
   {
     auto& manifest = ParseManifest(R"({"start_url": "/start?query=a" })");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("start?query=a", manifest->id);
+    EXPECT_EQ("http://foo.com/start?query=a", manifest->id);
   }
   // Invalid type.
   {
     auto& manifest =
         ParseManifest("{\"start_url\": \"/start?query=a\", \"id\": 1}");
     ASSERT_EQ(1u, GetErrorCount());
-    EXPECT_EQ("start?query=a", manifest->id);
+    EXPECT_EQ("http://foo.com/start?query=a", manifest->id);
   }
   // Empty string.
   {
     auto& manifest =
         ParseManifest(R"({ "start_url": "/start?query=a", "id": "" })");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("start?query=a", manifest->id);
+    EXPECT_EQ("http://foo.com/start?query=a", manifest->id);
   }
   // Full url.
   {
@@ -324,7 +326,7 @@
         "{ \"start_url\": \"/start?query=a\", \"id\": \"http://foo.com/foo\" "
         "}");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("foo", manifest->id);
+    EXPECT_EQ("http://foo.com/foo", manifest->id);
   }
   // Full url with different origin.
   {
@@ -332,35 +334,49 @@
         "{ \"start_url\": \"/start?query=a\", \"id\": "
         "\"http://another.com/foo\" }");
     ASSERT_EQ(1u, GetErrorCount());
-    EXPECT_EQ("start?query=a", manifest->id);
+    EXPECT_EQ("http://foo.com/start?query=a", manifest->id);
   }
   // Relative path
   {
     auto& manifest =
         ParseManifest("{ \"start_url\": \"/start?query=a\", \"id\": \".\" }");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("", manifest->id);
+    EXPECT_EQ("http://foo.com/", manifest->id);
   }
   // Absolute path
   {
     auto& manifest =
         ParseManifest("{ \"start_url\": \"/start?query=a\", \"id\": \"/\" }");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("", manifest->id);
+    EXPECT_EQ("http://foo.com/", manifest->id);
   }
   // url with fragment
   {
     auto& manifest = ParseManifest(
         "{ \"start_url\": \"/start?query=a\", \"id\": \"/#abc\" }");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("", manifest->id);
+    EXPECT_EQ("http://foo.com/", manifest->id);
   }
   // Smoke test.
   {
     auto& manifest =
         ParseManifest(R"({ "start_url": "/start?query=a", "id": "foo" })");
     ASSERT_EQ(0u, GetErrorCount());
-    EXPECT_EQ("foo", manifest->id);
+    EXPECT_EQ("http://foo.com/foo", manifest->id);
+  }
+  // Invalid UTF-8 character.
+  {
+    UChar invalid_utf8_chars[] = {0xD801, 0x0000};
+    String manifest_str =
+        String("{ \"start_url\": \"/start?query=a\", \"id\": \"") +
+        String(invalid_utf8_chars) + String("\" }");
+
+    ParseManifest(manifest_str);
+    ASSERT_EQ(1u, GetErrorCount());
+    EXPECT_THAT(
+        errors()[0].Utf8(),
+        testing::EndsWith("Unsupported encoding. JSON and all string literals "
+                          "must contain valid Unicode characters."));
   }
 }
 
diff --git a/third_party/blink/renderer/modules/subapps/sub_apps.cc b/third_party/blink/renderer/modules/subapps/sub_apps.cc
index 3447267..675016d 100644
--- a/third_party/blink/renderer/modules/subapps/sub_apps.cc
+++ b/third_party/blink/renderer/modules/subapps/sub_apps.cc
@@ -47,7 +47,7 @@
         add_result->result_code == SubAppsServiceResultCode::kSuccess
             ? V8SubAppsResultCode(V8SubAppsResultCode::Enum::kSuccess)
             : V8SubAppsResultCode(V8SubAppsResultCode::Enum::kFailure);
-    add_results_idl.emplace_back(add_result->unhashed_app_id_path, result_code);
+    add_results_idl.emplace_back(add_result->manifest_id_path, result_code);
   }
   return add_results_idl;
 }
@@ -60,7 +60,7 @@
         remove_result->result_code == SubAppsServiceResultCode::kSuccess
             ? V8SubAppsResultCode(V8SubAppsResultCode::Enum::kSuccess)
             : V8SubAppsResultCode(V8SubAppsResultCode::Enum::kFailure);
-    results.emplace_back(remove_result->unhashed_app_id_path, result_code);
+    results.emplace_back(remove_result->manifest_id_path, result_code);
   }
   return results;
 }
@@ -69,9 +69,9 @@
     HeapVector<std::pair<String, Member<SubAppsAddParams>>>
         sub_apps_to_add_idl) {
   Vector<SubAppsServiceAddParametersPtr> sub_apps_to_add_mojo;
-  for (auto& [unhashed_app_id_path, add_params] : sub_apps_to_add_idl) {
+  for (auto& [manifest_id_path, add_params] : sub_apps_to_add_idl) {
     sub_apps_to_add_mojo.emplace_back(SubAppsServiceAddParameters::New(
-        unhashed_app_id_path, add_params->installURL()));
+        manifest_id_path, add_params->installURL()));
   }
   return sub_apps_to_add_mojo;
 }
@@ -82,8 +82,8 @@
   for (auto& sub_app_entry : sub_apps_list_mojo) {
     SubAppsListResult* list_result = SubAppsListResult::Create();
     list_result->setAppName(std::move(sub_app_entry->app_name));
-    sub_apps_list_idl.emplace_back(
-        std::move(sub_app_entry->unhashed_app_id_path), list_result);
+    sub_apps_list_idl.emplace_back(std::move(sub_app_entry->manifest_id_path),
+                                   list_result);
   }
   return sub_apps_list_idl;
 }
@@ -168,8 +168,8 @@
   }
 
   // Check that the arguments are root-relative paths.
-  for (const auto& [unhashed_app_id_path, add_params] : sub_apps_to_add) {
-    if (KURL(unhashed_app_id_path).IsValid() ||
+  for (const auto& [manifest_id_path, add_params] : sub_apps_to_add) {
+    if (KURL(manifest_id_path).IsValid() ||
         KURL(add_params->installURL()).IsValid()) {
       exception_state.ThrowDOMException(
           DOMExceptionCode::kNotSupportedError,
@@ -221,15 +221,15 @@
 }
 
 ScriptPromise SubApps::remove(ScriptState* script_state,
-                              const Vector<String>& unhashed_app_id_paths,
+                              const Vector<String>& manifest_id_paths,
                               ExceptionState& exception_state) {
   if (!CheckPreconditionsMaybeThrow(exception_state)) {
     return ScriptPromise();
   }
 
   // Check that the arguments are root-relative paths.
-  for (const auto& unhashed_app_id_path : unhashed_app_id_paths) {
-    if (KURL(unhashed_app_id_path).IsValid()) {
+  for (const auto& manifest_id_path : manifest_id_paths) {
+    if (KURL(manifest_id_path).IsValid()) {
       exception_state.ThrowDOMException(
           DOMExceptionCode::kNotSupportedError,
           "Arguments must be root-relative paths.");
@@ -239,7 +239,7 @@
 
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   GetService()->Remove(
-      unhashed_app_id_paths,
+      manifest_id_paths,
       resolver->WrapCallbackInScriptScope(
           WTF::BindOnce([](ScriptPromiseResolver* resolver,
                            Vector<SubAppsServiceRemoveResultPtr> results_mojo) {
diff --git a/third_party/blink/renderer/modules/subapps/sub_apps.h b/third_party/blink/renderer/modules/subapps/sub_apps.h
index 1680dca..82fa0bd 100644
--- a/third_party/blink/renderer/modules/subapps/sub_apps.h
+++ b/third_party/blink/renderer/modules/subapps/sub_apps.h
@@ -42,7 +42,7 @@
       ExceptionState&);
   ScriptPromise list(ScriptState*, ExceptionState&);
   ScriptPromise remove(ScriptState*,
-                       const Vector<String>& unhashed_app_id_paths,
+                       const Vector<String>& manifest_id_paths,
                        ExceptionState&);
 
  private:
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 0978209..e7b78d51 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -469,7 +469,6 @@
     "bindings/v8_binding.cc",
     "bindings/v8_binding.h",
     "bindings/v8_binding_macros.h",
-    "bindings/v8_cross_origin_callback_info.h",
     "bindings/v8_cross_origin_property_support.cc",
     "bindings/v8_cross_origin_property_support.h",
     "bindings/v8_dom_activity_logger.cc",
diff --git a/third_party/blink/renderer/platform/bindings/script_state.h b/third_party/blink/renderer/platform/bindings/script_state.h
index fbe9cad..9f4a8614 100644
--- a/third_party/blink/renderer/platform/bindings/script_state.h
+++ b/third_party/blink/renderer/platform/bindings/script_state.h
@@ -11,7 +11,6 @@
 #include "gin/public/gin_embedders.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/renderer/platform/bindings/scoped_persistent.h"
-#include "third_party/blink/renderer/platform/bindings/v8_cross_origin_callback_info.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
@@ -149,25 +148,6 @@
     return From(info.GetIsolate()->GetCurrentContext());
   }
 
-  static ScriptState* ForRelevantRealm(
-      const v8::FunctionCallbackInfo<v8::Value>& info) {
-    return ForRelevantRealm(info.Holder());
-  }
-
-  static ScriptState* ForRelevantRealm(const V8CrossOriginCallbackInfo& info) {
-    return ForRelevantRealm(info.Holder());
-  }
-
-  static ScriptState* ForRelevantRealm(
-      const v8::PropertyCallbackInfo<v8::Value>& info) {
-    return ForRelevantRealm(info.Holder());
-  }
-
-  static ScriptState* ForRelevantRealm(
-      const v8::PropertyCallbackInfo<void>& info) {
-    return ForRelevantRealm(info.Holder());
-  }
-
   static ScriptState* ForRelevantRealm(v8::Local<v8::Object> object) {
     DCHECK(!object.IsEmpty());
     ScriptState* script_state = static_cast<ScriptState*>(
diff --git a/third_party/blink/renderer/platform/bindings/v8_cross_origin_callback_info.h b/third_party/blink/renderer/platform/bindings/v8_cross_origin_callback_info.h
deleted file mode 100644
index 59b4574..0000000
--- a/third_party/blink/renderer/platform/bindings/v8_cross_origin_callback_info.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_V8_CROSS_ORIGIN_CALLBACK_INFO_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_V8_CROSS_ORIGIN_CALLBACK_INFO_H_
-
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "v8/include/v8.h"
-
-namespace blink {
-
-// Simple adapter that is used in place of v8::PropertyCallbackInfo and
-// v8::FunctionCallbackInfo for getters and setters that are accessible
-// cross-origin. This is needed because a named access check interceptor takes
-// a v8::PropertyCallbackInfo<v8::Value> or v8::FunctionCallbackInfo<v8::Value>
-// argument, while a normal setter interceptor takes a
-// v8::PropertyCallbackInfo<void> or v8::FunctionCallbackInfo<v8::Value>
-// argument.
-//
-// Since the generated bindings only care about two fields (the isolate and the
-// holder), the generated bindings just substitutes this for the normal
-// v8::PropertyCallbackInfo and v8::FunctionCallbackInfo argument, so the same
-// generated function can be used to handle intercepted cross-origin sets and
-// normal sets.
-class V8CrossOriginCallbackInfo {
-  STACK_ALLOCATED();
-
- public:
-  explicit V8CrossOriginCallbackInfo(
-      const v8::PropertyCallbackInfo<v8::Value>& info)
-      : isolate_(info.GetIsolate()), holder_(info.Holder()) {}
-  explicit V8CrossOriginCallbackInfo(const v8::PropertyCallbackInfo<void>& info)
-      : isolate_(info.GetIsolate()), holder_(info.Holder()) {}
-  explicit V8CrossOriginCallbackInfo(
-      const v8::FunctionCallbackInfo<v8::Value>& info)
-      : isolate_(info.GetIsolate()), holder_(info.Holder()) {}
-
-  v8::Isolate* GetIsolate() const { return isolate_; }
-  v8::Local<v8::Object> Holder() const { return holder_; }
-
- private:
-  v8::Isolate* isolate_;
-  v8::Local<v8::Object> holder_;
-
-  V8CrossOriginCallbackInfo(const V8CrossOriginCallbackInfo&) = delete;
-  V8CrossOriginCallbackInfo& operator=(const V8CrossOriginCallbackInfo&) =
-      delete;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_V8_CROSS_ORIGIN_CALLBACK_INFO_H_
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 053fd22..0419a3c 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2342,7 +2342,7 @@
     },
     {
       name: "NewFlexboxSizing",
-      status: "stable",
+      status: "test",
     },
     {
       name: "NodeAsNSResolver",
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index a678f2f..84584dd 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -84,6 +84,7 @@
 crbug.com/1209223 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/preload/modulepreload-as.html [ Timeout Failure ]
 crbug.com/626703 external/wpt/html/semantics/popovers/popover-hide-crash.html [ Timeout ]
 crbug.com/626703 virtual/fenced-frame-mparch/wpt_internal/fenced_frame/unfenced-top.https.html [ Timeout ]
 crbug.com/626703 [ Release ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/can-load-api.https.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 9036445..fa9162d7 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -263,16 +263,6 @@
 crbug.com/1430357 [ Mac ] virtual/view-transition-wide-gamut/external/wpt/css/css-view-transitions/massive-element-* [ Failure Pass ]
 
 # View transition SPA failures with MPA serialization.
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-below-viewport-offscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-below-viewport-partially-onscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-left-of-viewport-offscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-left-of-viewport-partially-onscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-on-top-of-viewport-offscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-on-top-of-viewport-partially-onscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-right-of-viewport-offscreen-old.html [ Failure ]
-crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/massive-element-right-of-viewport-partially-onscreen-old.html [ Failure ]
 crbug.com/1443559 virtual/view-transition-mpa-serialization/inspector-protocol/css/css-get-styles-for-view-transition.js [ Failure ]
 crbug.com/1443559 virtual/view-transition-mpa-serialization/view-transition/capture-callback-exception.html [ Failure ]
 crbug.com/1443559 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/old-content-captures-clip-path.html [ Failure ]
@@ -2920,6 +2910,9 @@
 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 external/wpt/preload/modulepreload-as.html [ Timeout Failure ]
+crbug.com/626703 [ Win ] external/wpt/preload/modulepreload.html [ Timeout Failure ]
+crbug.com/626703 [ Mac11 ] external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/access-reporting-post-message.https.html [ Timeout ]
 crbug.com/626703 [ Mac13 ] external/wpt/html/semantics/links/hyperlink-auditing/headers.optional.html [ Timeout ]
 crbug.com/626703 [ Mac13 ] external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-video.html [ Failure ]
 crbug.com/626703 [ Mac13 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 9f3f191d..c889de08 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -272123,11 +272123,11 @@
   "support": {
    ".cache": {
     "gitignore2.json": [
-     "4fea38c7962c52d3aeae5024f9db13d74c9ac393",
+     "eab186c5a15be1168b7c96f5f55e1e5ea447887c",
      []
     ],
     "mtime.json": [
-     "a3869f3dfc0398d482690cb292101531fa12c673",
+     "dde16757bcee4e34fc657890539c48a97ab3db38",
      []
     ]
    },
@@ -287231,7 +287231,7 @@
        []
       ],
       "mix-blend-mode-blended-element-overflow-scroll.html.ini": [
-       "837e7f4c1224cbe84aea7fab59be97e3533bb470",
+       "053f4729ef2a7f1d42253a93d30aea86716f659e",
        []
       ],
       "mix-blend-mode-blended-with-3D-transform.html.ini": [
@@ -290919,6 +290919,10 @@
       "7f99f57a67957bfeb57717852b03eb707c6a52f7",
       []
      ],
+     "clipping-001.html.ini": [
+      "cf153598fae64dff9deae3979f7ab03a4e022ee8",
+      []
+     ],
      "contain-strict-with-opacity-and-oof-ref.html": [
       "a9c82c4f3e2d717469bd5bc9e9ff9cefb49a25d0",
       []
@@ -291372,11 +291376,23 @@
       []
      ],
      "table": {
+      "monolithic-overflow-002.tentative.html.ini": [
+       "4d131e8d4ab61f0fde48f6d838501b25dfc21523",
+       []
+      ],
+      "monolithic-overflow-005.html.ini": [
+       "4b6c3a6c54a75120ca948ad2cae42ad90ee076d5",
+       []
+      ],
       "repeated-section": {
        "abspos-uncontained-text-ref.html": [
         "a7c2eeb5b8e05397d606776fc8b470e451a12b04",
         []
        ],
+       "background-001.tentative.html.ini": [
+        "cd28eb99d88efd2d5c593291f7840f4c5d8ec930",
+        []
+       ],
        "balanced-inner-multicol-ref.html": [
         "ab2dcebc4158de315a8df204d110a63bbe93ea69",
         []
@@ -291531,6 +291547,10 @@
       "cad216fbc3e6385b2b89bc03358c15d5073913a5",
       []
      ],
+     "transform-020.html.ini": [
+      "b5abfae09383b894659cfc6d8d0f8f8633817d5a",
+      []
+     ],
      "underflow-from-next-page-print-ref.html": [
       "5c444b7220436690b42c6b22ff8be424df36680a",
       []
@@ -310350,7 +310370,7 @@
       []
      ],
      "list-style-type-string-005b.html.ini": [
-      "eca28755e9cd141c738a3c9466eb4d86949052b3",
+      "b1d35c1c6b13a0d86651d148501787bd70564717",
       []
      ],
      "list-style-type-string-006-ref.html": [
@@ -329132,6 +329152,10 @@
        "e8f2f52f31837aaa0dd3f388b6489c660af5ca87",
        []
       ],
+      "kind-of-widget-fallback-color-input-background-size-001.html.ini": [
+       "aa553d858fad44e93366cdfe9f41fa48f5521306",
+       []
+      ],
       "kind-of-widget-fallback-color-input-border-block-end-color-001.html.ini": [
        "ff428f0d2fb4cb85deecd123c54dcef65c487207",
        []
@@ -329380,6 +329404,10 @@
        "01b12488a5a9b1a3900b8a88af5aa28f09582a51",
        []
       ],
+      "kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini": [
+       "58815a13a5947defd3acedc17f7f9536d72d7f8d",
+       []
+      ],
       "kind-of-widget-fallback-input-search-border-bottom-color-001.html.ini": [
        "1300857824c48d4b0ebf80fcdf47724a83fab327",
        []
@@ -329489,7 +329517,7 @@
        []
       ],
       "kind-of-widget-fallback-input-search-text-border-block-end-color-001.html.ini": [
-       "114fa292144ff7fbb138a832d2851cb1b748ca37",
+       "f851b00fba21f8dbbdf73d632d3098b732266d2a",
        []
       ],
       "kind-of-widget-fallback-input-search-text-border-block-end-style-001.html.ini": [
@@ -329685,7 +329713,7 @@
        []
       ],
       "kind-of-widget-fallback-input-submit-border-left-color-001.html.ini": [
-       "2415b1279f48e51bb930d36e36c0f330f510a2d5",
+       "d54de29225439f93cb83507fd8a5e2c2668b7fc6",
        []
       ],
       "kind-of-widget-fallback-input-submit-border-left-style-001.html.ini": [
@@ -379480,6 +379508,14 @@
      "83670cd86e34c3cbf3b465428b8ff6848d51f534",
      []
     ],
+    "modulepreload-as.html.ini": [
+     "79e2df963b2f24376c68c427ef0514b44f203937",
+     []
+    ],
+    "modulepreload-expected.txt": [
+     "8ffab3e3609f62651e948b508b4584af487eadaa",
+     []
+    ],
     "modulepreload.html.ini": [
      "fe059ec2a207db9a8e068259a49e867599cb4e91",
      []
@@ -402389,7 +402425,7 @@
      []
     ],
     "shared-worker-parse-error-failure.html.ini": [
-     "54e37866569084c0947f11540ec3e45accc8c561",
+     "3d3a090a43fd893d067965cebc6f4c1d1d2b5ba0",
      []
     ],
     "support": {
@@ -443113,6 +443149,13 @@
         {}
        ]
       ],
+      "display-contents-dynamic-style-queries.html": [
+       "782cf566552423193669f9f0da034f802d265067",
+       [
+        null,
+        {}
+       ]
+      ],
       "display-contents.html": [
        "d96a46d06a85f024f0fce9d43e34dec6e271ea76",
        [
@@ -543608,7 +543651,7 @@
       ]
      ],
      "idlharness.https.html": [
-      "4e9e25fd5df7221b4c99fd49c8fe0bb3753c549c",
+      "7d693d3c0a273ff2df256850baaeeb9203e54ba2",
       [
        "html/dom/idlharness.https.html?exclude=(Document|Window|HTML.*)",
        {
@@ -543629,7 +543672,7 @@
       ]
      ],
      "idlharness.worker.js": [
-      "16f6e85ce7c619e454d5657ca2978067785b0cff",
+      "88942ddfea13a10a14c4ceed360c9a7a9963e62e",
       [
        "html/dom/idlharness.worker.html",
        {}
@@ -584842,8 +584885,22 @@
       {}
      ]
     ],
+    "modulepreload-as.html": [
+     "dd946e454a1fe1833dbe37164105f028795facba",
+     [
+      null,
+      {}
+     ]
+    ],
+    "modulepreload-sri.html": [
+     "ea32a6a302525240573deb75b6e3f16b96b5e1eb",
+     [
+      null,
+      {}
+     ]
+    ],
     "modulepreload.html": [
-     "0e4b6923e32e83ac3b8f3018537352aa120df6c5",
+     "4764b58261995cc617c4a8cca0daaf2ab78e66fb",
      [
       null,
       {}
diff --git a/third_party/blink/web_tests/external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-blended-element-overflow-scroll.html.ini b/third_party/blink/web_tests/external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-blended-element-overflow-scroll.html.ini
index 837e7f4..053f4729 100644
--- a/third_party/blink/web_tests/external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-blended-element-overflow-scroll.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/compositing/mix-blend-mode/mix-blend-mode-blended-element-overflow-scroll.html.ini
@@ -1,5 +1,6 @@
 [mix-blend-mode-blended-element-overflow-scroll.html]
   expected:
-    if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
-    if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
     if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+    if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): FAIL
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+    if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/clipping-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-break/clipping-001.html.ini
new file mode 100644
index 0000000..cf15359
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/clipping-001.html.ini
@@ -0,0 +1,3 @@
+[clipping-001.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-002.tentative.html.ini b/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-002.tentative.html.ini
new file mode 100644
index 0000000..4d131e8d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-002.tentative.html.ini
@@ -0,0 +1,3 @@
+[monolithic-overflow-002.tentative.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-005.html.ini b/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-005.html.ini
new file mode 100644
index 0000000..4b6c3a6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/monolithic-overflow-005.html.ini
@@ -0,0 +1,3 @@
+[monolithic-overflow-005.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/background-001.tentative.html.ini b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/background-001.tentative.html.ini
new file mode 100644
index 0000000..cd28eb9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/background-001.tentative.html.ini
@@ -0,0 +1,3 @@
+[background-001.tentative.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/transform-020.html.ini b/third_party/blink/web_tests/external/wpt/css/css-break/transform-020.html.ini
new file mode 100644
index 0000000..b5abfae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/transform-020.html.ini
@@ -0,0 +1,3 @@
+[transform-020.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-lists/list-style-type-string-005b.html.ini b/third_party/blink/web_tests/external/wpt/css/css-lists/list-style-type-string-005b.html.ini
index eca2875..b1d35c1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-lists/list-style-type-string-005b.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-lists/list-style-type-string-005b.html.ini
@@ -1,3 +1,4 @@
 [list-style-type-string-005b.html]
   expected:
+    if (product == "content_shell") and (os == "linux"): FAIL
     if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-color-input-background-size-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-color-input-background-size-001.html.ini
new file mode 100644
index 0000000..aa553d8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-color-input-background-size-001.html.ini
@@ -0,0 +1,3 @@
+[kind-of-widget-fallback-color-input-background-size-001.html]
+  expected:
+    if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini
new file mode 100644
index 0000000..58815a1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-border-block-start-width-001.html.ini
@@ -0,0 +1,3 @@
+[kind-of-widget-fallback-input-search-border-block-start-width-001.html]
+  expected:
+    if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-block-end-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-block-end-color-001.html.ini
index 114fa292..f851b00 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-block-end-color-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-search-text-border-block-end-color-001.html.ini
@@ -1,4 +1,5 @@
 [kind-of-widget-fallback-input-search-text-border-block-end-color-001.html]
   expected:
     if (product == "content_shell") and (os == "mac") and (port == "mac12"): FAIL
+    if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL
     if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-left-color-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-left-color-001.html.ini
index 2415b12..d54de29 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-left-color-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-input-submit-border-left-color-001.html.ini
@@ -1,3 +1,4 @@
 [kind-of-widget-fallback-input-submit-border-left-color-001.html]
   expected:
     if (product == "content_shell") and (os == "win") and (port == "win10.20h2"): FAIL
+    if (product == "content_shell") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html
new file mode 100644
index 0000000..9bc171a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<title>
+  Verify that we consider browsing context group reuse for COOP reporting.
+</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/cross-origin-opener-policy/reporting/resources/try-access.js"></script>
+<script src="/html/cross-origin-opener-policy/resources/common.js"></script>
+<script
+  src="/html/cross-origin-opener-policy/reporting/resources/reporting-common.js?pipe=sub&report_id=f1e361ab5854f2dcfe0224b19bc53199&report_only_id=b6fe666b74547291d52d72790adde05c"></script>
+<script>
+
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+  // To receive reports use the same hard-coded value as the one passed in the
+  // headers and to "reporting-common.js".
+  const report_token = "b6fe666b74547291d52d72790adde05c";
+  const reportTo = reportToHeaders(report_token);
+
+  // 1. Open a popup without any COOP. It should be in a
+  // different virtual browsing context group.
+  const opener_token = token(); // For this window.
+  const initial_openee_token = token();
+  const initial_openee_url = cross_origin + executor_path +
+      `&uuid=${initial_openee_token}`;
+  let openee = window.open(initial_openee_url);
+
+  // 2. Navigate the openee to a COOP-RO: restrict-properties page. If the
+  // policy was enforced, it would live in the same browsing context group as
+  // this page. The virtual browsing context group should similarly be equal.
+  // Note: We omit the reporting endpoint header, because it is not possible to
+  // easily escape it. Since it is not necessary in this test, we skip it.
+  const final_openee_token = token();
+  const final_openee_url = same_origin + executor_path +
+      reportTo.coopReportOnlyRestrictPropertiesHeader +
+      `&uuid=${final_openee_token}`;
+
+  send(initial_openee_token, `location.href = '${final_openee_url}';`);
+  test.add_cleanup(() => send(final_openee_token, "window.close()"));
+
+  // Wait for the final openee to load.
+  send(final_openee_token,
+    `send("${opener_token}", "Ready");
+  `);
+  assert_equals(await receive(opener_token), "Ready");
+
+  // 3. Try to access the openee from the opener. No report should be sent.
+  tryAccess(openee);
+
+  let report =
+    await receiveReport(report_token, "access-from-coop-page-to-openee")
+  assert_equals(report, "timeout");
+
+}, "access-reporting-browsing-context-group-reuse");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html.sub.headers b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html.sub.headers
new file mode 100644
index 0000000..33abadd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/reporting-bcg-reuse.https.html.sub.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy-Report-Only: restrict-properties; report-to="coop-report-only-endpoint"
+Reporting-Endpoints: coop-report-endpoint="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=f1e361ab5854f2dcfe0224b19bc53199", coop-report-only-endpoint="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=b6fe666b74547291d52d72790adde05c"
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https.html b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https.html
index 4e9e25f..7d693d3 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https.html
@@ -38,7 +38,7 @@
 
 idl_test(
   ['html'],
-  ['wai-aria', 'SVG', 'cssom', 'touch-events', 'uievents', 'dom', 'xhr', 'FileAPI', 'mediacapture-streams'],
+  ['wai-aria', 'SVG', 'cssom', 'touch-events', 'uievents', 'dom', 'xhr', 'FileAPI', 'mediacapture-streams', 'performance-timeline'],
   async idlArray => {
     self.documentWithHandlers = new Document();
     const handler = function(e) {};
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker.js b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker.js
index 16f6e85..88942ddf 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker.js
@@ -5,7 +5,7 @@
 
 idl_test(
   ["html"],
-  ["wai-aria", "dom", "cssom", "touch-events", "uievents"],
+  ["wai-aria", "dom", "cssom", "touch-events", "uievents", "performance-timeline"],
   idlArray => {
     idlArray.add_untested_idls('typedef Window WindowProxy;');
     idlArray.add_objects({
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html
new file mode 100644
index 0000000..dd946e4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="modulepreload" href="resources/module1.js?empty-string" as="" data-as="">
+<link rel="modulepreload" href="resources/module1.js?audio" as="audio" data-as="audio">
+<link rel="modulepreload" href="resources/module1.js?audioworklet" as="audioworklet" data-as="audioworklet">
+<link rel="modulepreload" href="resources/module1.js?document" as="document" data-as="document">
+<link rel="modulepreload" href="resources/module1.js?embed" as="embed" data-as="embed">
+<link rel="modulepreload" href="resources/module1.js?font" as="font" data-as="font">
+<link rel="modulepreload" href="resources/module1.js?frame" as="frame" data-as="frame">
+<link rel="modulepreload" href="resources/module1.js?iframe" as="iframe" data-as="iframe">
+<link rel="modulepreload" href="resources/module1.js?image" as="image" data-as="image">
+<link rel="modulepreload" href="resources/module1.js?manifest" as="manifest" data-as="manifest">
+<link rel="modulepreload" href="resources/module1.js?object" as="object" data-as="object">
+<link rel="modulepreload" href="resources/module1.js?paintworklet" as="paintworklet" data-as="paintworklet">
+<link rel="modulepreload" href="resources/module1.js?report" as="report" data-as="report">
+<link rel="modulepreload" href="resources/module1.js?script" as="script" data-as="script">
+<link rel="modulepreload" href="resources/module1.js?serviceworker" as="serviceworker" data-as="serviceworker">
+<link rel="modulepreload" href="resources/module1.js?sharedworker" as="sharedworker" data-as="sharedworker">
+<link rel="modulepreload" href="resources/module1.js?style" as="style" data-as="style">
+<link rel="modulepreload" href="resources/module1.js?track" as="track" data-as="track">
+<link rel="modulepreload" href="resources/module1.js?video" as="video" data-as="video">
+<link rel="modulepreload" href="resources/module1.js?webidentity" as="webidentity" data-as="webidentity">
+<link rel="modulepreload" href="resources/module1.js?worker" as="worker" data-as="worker">
+<link rel="modulepreload" href="resources/module1.js?xslt" as="xslt" data-as="xslt">
+<link rel="modulepreload" href="resources/module1.js?fetch" as="fetch" data-as="fetch">
+<link rel="modulepreload" href="resources/module1.js?invalid-dest" as="invalid-dest" data-as="invalid-dest">
+<link rel="modulepreload" href="resources/module1.js?iMaGe" as="iMaGe" data-as="iMaGe">
+<link rel="modulepreload" href="resources/module1.js?sCrIpT" as="sCrIpT" data-as="sCrIpT">
+<body>
+<script>
+  // compared to modulepreload.html, this tests behavior when elements are
+  // initially on an HTML page instead of being added by JS
+
+  const scriptLikes = [
+    'audioworklet',
+    'paintworklet',
+    'script',
+    'serviceworker',
+    'sharedworker',
+    'worker',
+  ];
+
+  const goodAsValues = ['', 'invalid-dest', 'sCrIpT', ...scriptLikes];
+
+  for (const link of document.querySelectorAll('link')) {
+    const asValue = link.dataset.as; // don't depend on "as" attribute reflection
+    const good = goodAsValues.includes(asValue);
+
+    // promise tests are queued sequentially, so create the promise here to
+    // ensure we don't miss the error event
+    const promise = new Promise((resolve, reject) => {
+      link.onload = good ? resolve : reject;
+      link.onerror = good ? reject : resolve;
+    });
+
+    promise_test(() => promise.then(() => {
+      const downloads = performance
+        .getEntriesByName(new URL(link.href, location.href))
+        .filter(entry => entry.transferSize > 0)
+        .length;
+      assert_equals(downloads, good ? 1 : 0);
+
+    }), `Modulepreload with as="${asValue}"`);
+  }
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini
new file mode 100644
index 0000000..79e2df96
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload-as.html.ini
@@ -0,0 +1,192 @@
+[modulepreload-as.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): ERROR
+    if (product == "content_shell") and (os == "mac") and (port == "mac12"): OK
+    if (product == "content_shell") and (os == "mac") and (port == "mac11"): [OK, ERROR, TIMEOUT]
+    if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [ERROR, OK]
+    if (product == "content_shell") and (os == "mac") and (port == "mac13"): [ERROR, OK, TIMEOUT]
+    if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [OK, TIMEOUT]
+    if (product == "content_shell") and (os == "win") and (port == "win11"): TIMEOUT
+    if product == "chrome": ERROR
+    [ERROR, TIMEOUT]
+  [Modulepreload with as="audio"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [FAIL, PASS]
+      FAIL
+
+  [Modulepreload with as="audioworklet"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="document"]
+    expected:
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "win"): [FAIL, PASS]
+      if product == "chrome": [FAIL, PASS]
+      FAIL
+
+  [Modulepreload with as="embed"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): FAIL
+      [FAIL, PASS]
+
+  [Modulepreload with as="fetch"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "linux"): PASS
+      if product == "chrome": PASS
+      [FAIL, PASS]
+
+  [Modulepreload with as="font"]
+    expected:
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "win"): [PASS, FAIL]
+      if product == "chrome": [PASS, FAIL]
+      FAIL
+
+  [Modulepreload with as="frame"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "win"): [PASS, FAIL]
+      if product == "chrome": [PASS, FAIL]
+      [FAIL, PASS]
+
+  [Modulepreload with as="iMaGe"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "linux"): PASS
+      [PASS, FAIL]
+
+  [Modulepreload with as="iframe"]
+    expected:
+      if (product == "content_shell") and (os == "linux") and (flag_specific == ""): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [FAIL, PASS]
+      [PASS, FAIL]
+
+  [Modulepreload with as="image"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "win"): [PASS, FAIL]
+
+  [Modulepreload with as="invalid-dest"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="manifest"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
+
+  [Modulepreload with as="object"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [PASS, FAIL]
+
+  [Modulepreload with as="paintworklet"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="report"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+
+  [Modulepreload with as="serviceworker"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="sharedworker"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="style"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
+      if (product == "content_shell") and (os == "linux") and (flag_specific == ""): [PASS, FAIL]
+      if product == "chrome": [PASS, FAIL]
+      [FAIL, PASS]
+
+  [Modulepreload with as="track"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "linux") and (flag_specific == ""): [PASS, FAIL]
+      if product == "chrome": [PASS, FAIL]
+      [FAIL, PASS]
+
+  [Modulepreload with as="video"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "win"): [FAIL, PASS]
+      [PASS, FAIL]
+
+  [Modulepreload with as="webidentity"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac10.15"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "mac") and (port == "mac11"): [FAIL, PASS]
+      if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-site-isolation-trials"): PASS
+      if (product == "content_shell") and (os == "win"): [FAIL, PASS]
+      [PASS, FAIL]
+
+  [Modulepreload with as="worker"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      FAIL
+
+  [Modulepreload with as="xslt"]
+    expected:
+      if (product == "content_shell") and (os == "mac") and (port == "mac13"): FAIL
+      if (product == "content_shell") and (os == "mac") and (port == "mac13-arm64"): [PASS, FAIL]
+      if (product == "content_shell") and (os == "mac") and (port == "mac12"): PASS
+      if (product == "content_shell") and (os == "linux"): PASS
+      if product == "chrome": [PASS, FAIL]
+      [FAIL, PASS]
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt b/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt
new file mode 100644
index 0000000..8ffab3e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload-expected.txt
@@ -0,0 +1,31 @@
+This is a testharness.js-based test.
+PASS link rel=modulepreload
+PASS same-origin link rel=modulepreload crossorigin=anonymous
+PASS same-origin link rel=modulepreload crossorigin=use-credentials
+PASS cross-origin link rel=modulepreload
+PASS cross-origin link rel=modulepreload crossorigin=anonymous
+PASS cross-origin link rel=modulepreload crossorigin=use-credentials
+PASS link rel=modulepreload with submodules
+PASS link rel=modulepreload for a module with syntax error
+PASS link rel=modulepreload for a module with network error
+PASS link rel=modulepreload with bad href attribute
+PASS link rel=modulepreload as=script
+PASS link rel=modulepreload with non-script-like as= value (image)
+PASS link rel=modulepreload with non-script-like as= value (xslt)
+PASS link rel=modulepreload with integrity match
+PASS link rel=modulepreload with integrity match2
+PASS link rel=modulepreload with integrity mismatch
+PASS link rel=modulepreload with integrity mismatch2
+FAIL link rel=modulepreload with integrity mismatch3 promise_test: Unhandled rejection with value: object "[object Event]"
+PASS multiple link rel=modulepreload with same href
+PASS multiple link rel=modulepreload with child module before parent
+PASS link rel=modulepreload with matching media
+PASS link rel=modulepreload with non-matching media
+PASS link rel=modulepreload with empty media
+PASS link rel=modulepreload with empty href
+PASS link rel=modulepreload with empty href and invalid as= value
+PASS link rel=modulepreload and script with non-matching crossorigin values
+PASS link rel=modulepreload and script with non-matching crossorigin values2
+PASS link rel=modulepreload and non-module script
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html
new file mode 100644
index 0000000..ea32a6a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload-sri.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="modulepreload" href="resources/module1.js" integrity="sha384-invalid">
+<script type="module" src="resources/module1.js" id="myscript"></script>
+<body>
+<script>
+  // compared to modulepreload.html, this tests behavior when elements are
+  // initially on an HTML page instead of being added by JS
+  promise_test(() => {
+    return new Promise((resolve, reject) => {
+      let myscript = document.querySelector('#myscript');
+      myscript.onerror = resolve;
+      myscript.onload = reject;
+    });
+  }, "Script should not be loaded if modulepreload's integrity is invalid");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/preload/modulepreload.html b/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
index 0e4b692..4764b58 100644
--- a/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
+++ b/third_party/blink/web_tests/external/wpt/preload/modulepreload.html
@@ -33,6 +33,15 @@
     });
 }
 
+function attachAndWaitForTimeout(element, t) {
+    return new Promise((resolve, reject) => {
+        element.onload = reject;
+        element.onerror = reject;
+        t.step_timeout(resolve, 1000);
+        document.body.appendChild(element);
+    });
+}
+
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
@@ -58,7 +67,7 @@
     document.cookie = 'same=1';
     var link = document.createElement('link');
     link.rel = 'modulepreload';
-    link.crossorigin = 'anonymous';
+    link.crossOrigin = 'anonymous';
     link.href = 'resources/dummy.js?sameOriginAnonymous';
     return attachAndWaitForLoad(link).then(() => {
         verifyNumberOfDownloads('resources/dummy.js?sameOriginAnonymous', 1);
@@ -66,7 +75,7 @@
         // Verify that <script> doesn't fetch the module again.
         var script = document.createElement('script');
         script.type = 'module';
-        script.crossorigin = 'anonymous';
+        script.crossOrigin = 'anonymous';
         script.src = 'resources/dummy.js?sameOriginAnonymous';
         return attachAndWaitForLoad(script);
     }).then(() => {
@@ -77,7 +86,7 @@
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
-    link.crossorigin = 'use-credentials';
+    link.crossOrigin = 'use-credentials';
     link.href = 'resources/dummy.js?sameOriginUseCredentials';
     return attachAndWaitForLoad(link).then(() => {
         verifyNumberOfDownloads('resources/dummy.js?sameOriginUseCredentials', 1);
@@ -85,7 +94,7 @@
         // Verify that <script> doesn't fetch the module again.
         var script = document.createElement('script');
         script.type = 'module';
-        script.crossorigin = 'use-credentials';
+        script.crossOrigin = 'use-credentials';
         script.src = 'resources/dummy.js?sameOriginUseCredentials';
         return attachAndWaitForLoad(script);
     }).then(() => {
@@ -122,7 +131,7 @@
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
-    link.crossorigin = 'anonymous';
+    link.crossOrigin = 'anonymous';
     link.href = `${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginAnonymous`;
     return attachAndWaitForLoad(link).then(() => {
         verifyNumberOfDownloads(`${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginAnonymous`, 1, true);
@@ -130,7 +139,7 @@
         // Verify that <script> doesn't fetch the module again.
         var script = document.createElement('script');
         script.type = 'module';
-        script.crossorigin = 'anonymous';
+        script.crossOrigin = 'anonymous';
         script.src = `${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginAnonymous`;
         return attachAndWaitForLoad(script);
     }).then(() => {
@@ -141,7 +150,7 @@
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
-    link.crossorigin = 'use-credentials';
+    link.crossOrigin = 'use-credentials';
     link.href = `${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginUseCredentials`;
     return attachAndWaitForLoad(link).then(() => {
         verifyNumberOfDownloads(`${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginUseCredentials`, 1, true);
@@ -149,7 +158,7 @@
         // Verify that <script> doesn't fetch the module again.
         var script = document.createElement('script');
         script.type = 'module';
-        script.crossorigin = 'use-credentials';
+        script.crossOrigin = 'use-credentials';
         script.src = `${host_info.HTTP_REMOTE_ORIGIN}/preload/resources/cross-origin-module.py?crossOriginUseCredentials`;
         return attachAndWaitForLoad(script);
     }).then(() => {
@@ -214,13 +223,21 @@
     link.href = 'resources/module1.js?as-image';
     link.as = 'image'
     return attachAndWaitForError(link);
-}, 'link rel=modulepreload with invalid as= value');
+}, 'link rel=modulepreload with non-script-like as= value (image)');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.js?as-xslt';
+    link.as = 'xslt'
+    return attachAndWaitForError(link);
+}, 'link rel=modulepreload with non-script-like as= value (xslt)');
 
 promise_test(function(t) {
     var link = document.createElement('link');
     link.rel = 'modulepreload';
     link.href = 'resources/module1.js?integrity-match';
-    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
+    link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
     return attachAndWaitForLoad(link);
 }, 'link rel=modulepreload with integrity match');
 
@@ -228,7 +245,7 @@
     var link = document.createElement('link');
     link.rel = 'modulepreload';
     link.href = 'resources/module1.mjs?integrity-match';
-    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
+    link.integrity = 'sha256-+Ks3iNIiTq2ujlWhvB056cmXobrCFpU9hd60xZ1WCaA='
     return attachAndWaitForLoad(link);
 }, 'link rel=modulepreload with integrity match2');
 
@@ -240,5 +257,127 @@
     return attachAndWaitForError(link);
 }, 'link rel=modulepreload with integrity mismatch');
 
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.mjs?integrity-doesnotmatch';
+    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc='
+    return attachAndWaitForError(link);
+}, 'link rel=modulepreload with integrity mismatch2');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.mjs?integrity-invalid';
+    link.integrity = 'sha256-dOxReWMnMSPfUvxEbBqIrjNh8ZN8n05j7h3JmhF8gQc=%'
+    return attachAndWaitForError(link);
+}, 'link rel=modulepreload with integrity mismatch3');
+
+promise_test(function(t) {
+    var link1 = document.createElement('link');
+    var link2 = document.createElement('link');
+    link1.rel = 'modulepreload';
+    link2.rel = 'modulepreload';
+    link1.href = 'resources/module1.js?same-url';
+    link2.href = 'resources/module1.js?same-url';
+    return Promise.all([
+        attachAndWaitForLoad(link1),
+        attachAndWaitForLoad(link2),
+    ]);
+}, 'multiple link rel=modulepreload with same href');
+
+promise_test(function(t) {
+    var link1 = document.createElement('link');
+    var link2 = document.createElement('link');
+    link1.rel = 'modulepreload';
+    link2.rel = 'modulepreload';
+    link1.href = 'resources/module2.js?child-before';
+    link2.href = 'resources/module1.js?child-before';
+    return attachAndWaitForLoad(link1)
+        .then(() => attachAndWaitForLoad(link2))
+        .then(() => new Promise(r => t.step_timeout(r, 1000)))
+        .then(() => {
+            verifyNumberOfDownloads('resources/module2.js?child-before', 1);
+        });
+
+}, 'multiple link rel=modulepreload with child module before parent');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.mjs?matching-media';
+    link.media = 'all';
+    return attachAndWaitForLoad(link);
+}, 'link rel=modulepreload with matching media');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.mjs?non-matching-media';
+    link.media = 'not all';
+    return attachAndWaitForTimeout(link, t);
+}, 'link rel=modulepreload with non-matching media');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = 'resources/module1.mjs?empty-media';
+    link.media = '';
+    return attachAndWaitForLoad(link);
+}, 'link rel=modulepreload with empty media');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = '';
+    return attachAndWaitForTimeout(link, t);
+}, 'link rel=modulepreload with empty href');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    link.rel = 'modulepreload';
+    link.href = '';
+    link.as = 'fetch';
+    return attachAndWaitForTimeout(link, t);
+}, 'link rel=modulepreload with empty href and invalid as= value');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    var script = document.createElement('script');
+    link.rel = 'modulepreload';
+    script.type = 'module';
+    link.href = 'resources/module1.mjs?non-matching-crossorigin';
+    script.src = link.href;
+    script.crossOrigin = 'anonymous';
+    document.body.append(link);
+    return attachAndWaitForLoad(script);
+}, 'link rel=modulepreload and script with non-matching crossorigin values');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    var script = document.createElement('script');
+    link.rel = 'modulepreload';
+    script.type = 'module';
+    link.href = 'resources/module1.mjs?non-matching-crossorigin';
+    script.src = link.href;
+    link.crossOrigin = 'anonymous';
+    script.crossOrigin = 'use-credentials';
+    document.body.append(link);
+    return attachAndWaitForLoad(script);
+}, 'link rel=modulepreload and script with non-matching crossorigin values2');
+
+promise_test(function(t) {
+    var link = document.createElement('link');
+    var moduleScript = document.createElement('script');
+    var classicScript = document.createElement('script');
+    link.rel = 'modulepreload';
+    moduleScript.type = 'module';
+    link.href = 'resources/dummy.js?non-module script';
+    classicScript.src = link.href;
+    moduleScript.src = link.href;
+    document.body.append(link);
+    document.body.append(classicScript);
+    return attachAndWaitForLoad(moduleScript);
+}, 'link rel=modulepreload and non-module script');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/subapps/add-error.tentative.https.html b/third_party/blink/web_tests/external/wpt/subapps/add-error.tentative.https.html
index 2cbf5083..defe4743 100644
--- a/third_party/blink/web_tests/external/wpt/subapps/add-error.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/subapps/add-error.tentative.https.html
@@ -82,8 +82,8 @@
   };
 
   let mocked_response = [
-    { "unhashedAppIdPath": url_1, "resultCode": Status.FAILURE },
-    { "unhashedAppIdPath": url_2, "resultCode": Status.FAILURE }
+    { "manifestIdPath": url_1, "resultCode": Status.FAILURE },
+    { "manifestIdPath": url_2, "resultCode": Status.FAILURE }
   ];
 
   let expected_results = {
@@ -106,8 +106,8 @@
   };
 
   let mocked_response = [
-    { "unhashedAppIdPath": url_1, "resultCode": Status.SUCCESS },
-    { "unhashedAppIdPath": url_2, "resultCode": Status.FAILURE }
+    { "manifestIdPath": url_1, "resultCode": Status.SUCCESS },
+    { "manifestIdPath": url_2, "resultCode": Status.FAILURE }
   ];
 
   let expected_results = {
diff --git a/third_party/blink/web_tests/external/wpt/subapps/add-success.tentative.https.html b/third_party/blink/web_tests/external/wpt/subapps/add-success.tentative.https.html
index 2453fdb..a9a439b 100644
--- a/third_party/blink/web_tests/external/wpt/subapps/add-success.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/subapps/add-success.tentative.https.html
@@ -16,7 +16,7 @@
   };
 
   let mocked_response = [
-    { "unhashedAppIdPath": install_url, "resultCode": Status.SUCCESS }
+    { "manifestIdPath": install_url, "resultCode": Status.SUCCESS }
   ];
 
   let expected_results = {
@@ -38,8 +38,8 @@
   };
 
   let mocked_response = [
-    { "unhashedAppIdPath": url_1, "resultCode": Status.SUCCESS },
-    { "unhashedAppIdPath": url_2, "resultCode": Status.SUCCESS }
+    { "manifestIdPath": url_1, "resultCode": Status.SUCCESS },
+    { "manifestIdPath": url_2, "resultCode": Status.SUCCESS }
   ];
 
   let expected_results = {
diff --git a/third_party/blink/web_tests/external/wpt/subapps/list-success.tentative.https.html b/third_party/blink/web_tests/external/wpt/subapps/list-success.tentative.https.html
index c1d281a..ea4f961 100644
--- a/third_party/blink/web_tests/external/wpt/subapps/list-success.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/subapps/list-success.tentative.https.html
@@ -15,8 +15,8 @@
   const url_2 = '/sub-app-2';
 
   const mocked_response = [
-    { "unhashedAppIdPath": url_1, "appName": "App 1" },
-    { "unhashedAppIdPath": url_2, "appName": "App 2" },
+    { "manifestIdPath": url_1, "appName": "App 1" },
+    { "manifestIdPath": url_2, "appName": "App 2" },
   ];
 
   let expected_results = {
diff --git a/third_party/blink/web_tests/external/wpt/subapps/remove-error.tentative.https.html b/third_party/blink/web_tests/external/wpt/subapps/remove-error.tentative.https.html
index 917c3b4..a727190 100644
--- a/third_party/blink/web_tests/external/wpt/subapps/remove-error.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/subapps/remove-error.tentative.https.html
@@ -48,9 +48,9 @@
   let remove_call_params = [url_1, url_2, url_3];
 
   let mocked_response = [
-    { "unhashedAppIdPath": url_1, "resultCode": Status.FAILURE },
-    { "unhashedAppIdPath": url_2, "resultCode": Status.FAILURE },
-    { "unhashedAppIdPath": url_3, "resultCode": Status.FAILURE }
+    { "manifestIdPath": url_1, "resultCode": Status.FAILURE },
+    { "manifestIdPath": url_2, "resultCode": Status.FAILURE },
+    { "manifestIdPath": url_3, "resultCode": Status.FAILURE }
   ];
 
   let expected_results = {
@@ -70,9 +70,9 @@
   let remove_call_params = [url_1, url_2, url_3];
 
   let mocked_response = [
-    { "unhashedAppIdPath": url_1, "resultCode": Status.SUCCESS },
-    { "unhashedAppIdPath": url_2, "resultCode": Status.SUCCESS },
-    { "unhashedAppIdPath": url_3, "resultCode": Status.FAILURE }
+    { "manifestIdPath": url_1, "resultCode": Status.SUCCESS },
+    { "manifestIdPath": url_2, "resultCode": Status.SUCCESS },
+    { "manifestIdPath": url_3, "resultCode": Status.FAILURE }
   ];
 
   let expected_results = {
diff --git a/third_party/blink/web_tests/external/wpt/subapps/remove-success.tentative.https.html b/third_party/blink/web_tests/external/wpt/subapps/remove-success.tentative.https.html
index 6fd4305b..ad60d6398 100644
--- a/third_party/blink/web_tests/external/wpt/subapps/remove-success.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/subapps/remove-success.tentative.https.html
@@ -9,7 +9,7 @@
 
 const url = '/sub-app';
 let remove_call_params = [url];
-let mocked_response = [{ "unhashedAppIdPath": url, "resultCode": Status.SUCCESS }];
+let mocked_response = [{ "manifestIdPath": url, "resultCode": Status.SUCCESS }];
 let expected_results = {[url]: "success"};
 
 await subapps_remove_expect_success_with_result(t, remove_call_params, mocked_response, expected_results);
@@ -24,9 +24,9 @@
 let remove_call_params = [url_1, url_2, url_3];
 
 let mocked_response = [
-  { "unhashedAppIdPath": url_1, "resultCode": Status.SUCCESS },
-  { "unhashedAppIdPath": url_2, "resultCode": Status.SUCCESS },
-  { "unhashedAppIdPath": url_3, "resultCode": Status.SUCCESS }
+  { "manifestIdPath": url_1, "resultCode": Status.SUCCESS },
+  { "manifestIdPath": url_2, "resultCode": Status.SUCCESS },
+  { "manifestIdPath": url_3, "resultCode": Status.SUCCESS }
 ];
 
 let expected_results = {
diff --git a/third_party/blink/web_tests/external/wpt/workers/shared-worker-parse-error-failure.html.ini b/third_party/blink/web_tests/external/wpt/workers/shared-worker-parse-error-failure.html.ini
new file mode 100644
index 0000000..3d3a090
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/shared-worker-parse-error-failure.html.ini
@@ -0,0 +1,11 @@
+[shared-worker-parse-error-failure.html]
+  expected:
+    if (product == "content_shell") and (os == "mac") and (port == "mac12"): OK
+    TIMEOUT
+  [Classic shared worker construction for script with syntax error should dispatch an event named error.]
+    expected:
+      if product == "chrome": TIMEOUT
+
+  [Static import on classic shared worker should dispatch an event named error.]
+    expected:
+      if product == "chrome": NOTRUN
diff --git a/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/external/wpt/preload/modulepreload-as-expected.txt b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/external/wpt/preload/modulepreload-as-expected.txt
new file mode 100644
index 0000000..9bd09b53
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-site-isolation-trials/external/wpt/preload/modulepreload-as-expected.txt
@@ -0,0 +1,30 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Unhandled rejection
+PASS Modulepreload with as=""
+FAIL Modulepreload with as="audio" assert_equals: expected 0 but got 1
+FAIL Modulepreload with as="audioworklet" promise_test: Unhandled rejection with value: object "[object Event]"
+FAIL Modulepreload with as="document" assert_equals: expected 0 but got 1
+PASS Modulepreload with as="embed"
+PASS Modulepreload with as="font"
+PASS Modulepreload with as="frame"
+PASS Modulepreload with as="iframe"
+PASS Modulepreload with as="image"
+PASS Modulepreload with as="manifest"
+PASS Modulepreload with as="object"
+FAIL Modulepreload with as="paintworklet" promise_test: Unhandled rejection with value: object "[object Event]"
+PASS Modulepreload with as="report"
+PASS Modulepreload with as="script"
+FAIL Modulepreload with as="serviceworker" promise_test: Unhandled rejection with value: object "[object Event]"
+FAIL Modulepreload with as="sharedworker" promise_test: Unhandled rejection with value: object "[object Event]"
+PASS Modulepreload with as="style"
+PASS Modulepreload with as="track"
+PASS Modulepreload with as="video"
+PASS Modulepreload with as="webidentity"
+FAIL Modulepreload with as="worker" promise_test: Unhandled rejection with value: object "[object Event]"
+PASS Modulepreload with as="xslt"
+PASS Modulepreload with as="fetch"
+FAIL Modulepreload with as="invalid-dest" promise_test: Unhandled rejection with value: object "[object Event]"
+PASS Modulepreload with as="iMaGe"
+PASS Modulepreload with as="sCrIpT"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/linux/svg/clip-path/clip-path-with-text-clipped-expected.png b/third_party/blink/web_tests/platform/linux/svg/clip-path/clip-path-with-text-clipped-expected.png
index a226c2f..41c04dea 100644
--- a/third_party/blink/web_tests/platform/linux/svg/clip-path/clip-path-with-text-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/clip-path/clip-path-with-text-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/clip-path/clip-path-childs-clipped-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
index f51f93d..dab5ea160 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/clip-path/clip-path-childs-clipped-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
index f51f93d..dab5ea160 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/svg/clip-path/clip-path-childs-clipped-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
index f51f93d..dab5ea160 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/svg/clip-path/clip-path-childs-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/clip-path/clip-path-with-text-clipped-expected.png b/third_party/blink/web_tests/platform/mac/svg/clip-path/clip-path-with-text-clipped-expected.png
index 3125766..90bfe28 100644
--- a/third_party/blink/web_tests/platform/mac/svg/clip-path/clip-path-with-text-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/clip-path/clip-path-with-text-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/clip-path/clip-path-with-text-clipped-expected.png b/third_party/blink/web_tests/platform/win/svg/clip-path/clip-path-with-text-clipped-expected.png
index 110298a..183ab123 100644
--- a/third_party/blink/web_tests/platform/win/svg/clip-path/clip-path-with-text-clipped-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/clip-path/clip-path-with-text-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/clip-path/clip-path-childs-clipped-expected.png b/third_party/blink/web_tests/svg/clip-path/clip-path-childs-clipped-expected.png
index 86239d4..c71b038 100644
--- a/third_party/blink/web_tests/svg/clip-path/clip-path-childs-clipped-expected.png
+++ b/third_party/blink/web_tests/svg/clip-path/clip-path-childs-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/clip-path/clip-path-clipped-evenodd-twice-expected.png b/third_party/blink/web_tests/svg/clip-path/clip-path-clipped-evenodd-twice-expected.png
index 3f7a64da..fce6237 100644
--- a/third_party/blink/web_tests/svg/clip-path/clip-path-clipped-evenodd-twice-expected.png
+++ b/third_party/blink/web_tests/svg/clip-path/clip-path-clipped-evenodd-twice-expected.png
Binary files differ
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 5120b04..e553140 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-13-0-136-ge1a4e081a
-Revision: e1a4e081aa57b3e044c7f30c3118cb6015e397d6
+Version: VER-2-13-0-138-g416d4c25f
+Revision: 416d4c25f1e15d2494d373982a511928f635e705
 CPEPrefix: cpe:/a:freetype:freetype:2.12.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 5bee9e2..35d95a5e 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: ff412edde1370a77b5109b1ca899ab63c94a6639
+Version: b698c00b5db7fc1bf8e15c24ae5a640251952084
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/openscreen/OWNERS b/third_party/openscreen/OWNERS
index 4b7fbf8..0cf7d2d6 100644
--- a/third_party/openscreen/OWNERS
+++ b/third_party/openscreen/OWNERS
@@ -1,3 +1,2 @@
 mfoltz@chromium.org
 jophba@chromium.org
-rwkeane@google.com
diff --git a/third_party/openxr/BUILD.gn b/third_party/openxr/BUILD.gn
index c142882..ed731bd 100644
--- a/third_party/openxr/BUILD.gn
+++ b/third_party/openxr/BUILD.gn
@@ -26,6 +26,50 @@
 
     public_configs = [ ":config" ]
   }
+
+  if (is_android) {
+    # On Android, the loader comes with a couple more external dependencies.
+
+    source_set("jnipp") {
+      sources = [
+        "src/src/external/jnipp/jnipp.cpp",
+        "src/src/external/jnipp/jnipp.h",
+      ]
+
+      cflags_cc = [ "-Wno-shadow" ]
+
+      # JNIPP uses exceptions to communicate failures:
+      configs += [ "//build/config/compiler:exceptions" ]
+    }
+
+    config("android-jni-wrappers-config") {
+      include_dirs = [ "src/src/external/jnipp" ]
+    }
+
+    source_set("android-jni-wrappers") {
+      sources = [
+        "src/src/external/android-jni-wrappers/wrap/ObjectWrapperBase.h",
+        "src/src/external/android-jni-wrappers/wrap/android.content.cpp",
+        "src/src/external/android-jni-wrappers/wrap/android.content.h",
+        "src/src/external/android-jni-wrappers/wrap/android.content.impl.h",
+        "src/src/external/android-jni-wrappers/wrap/android.database.cpp",
+        "src/src/external/android-jni-wrappers/wrap/android.database.h",
+        "src/src/external/android-jni-wrappers/wrap/android.database.impl.h",
+        "src/src/external/android-jni-wrappers/wrap/android.net.cpp",
+        "src/src/external/android-jni-wrappers/wrap/android.net.h",
+        "src/src/external/android-jni-wrappers/wrap/android.net.impl.h",
+      ]
+
+      # android-jni-wrappers assume jnipp headers are in the include path and
+      # they also do so in the headers that are intended to be included by the
+      # consumer of this target, so let's add them to public_configs to
+      # propagate the include dirs to our consumers:
+      public_configs = [ ":android-jni-wrappers-config" ]
+
+      public_deps = [ ":jnipp" ]
+    }
+  }
+
   source_set("openxr") {
     # This visibility is done to help enforce the dependency that
     # openxr_platform.h requires platform headers to be included before it.
@@ -93,6 +137,23 @@
       "-Wno-unused-function",
       "-Wno-extra-semi",
     ]
+
+    if (is_android) {
+      sources += [
+        "src/src/loader/android_utilities.cpp",
+        "src/src/loader/android_utilities.h",
+      ]
+
+      deps += [ ":android-jni-wrappers" ]
+
+      # OpenXR loader on Android assumes that android-jni-wrappers are in the
+      # include path:
+      include_dirs += [ "src/src/external/android-jni-wrappers" ]
+
+      # OpenXR loader on Android has a bug where there is unconditional try-catch block
+      # (in android_utilities.cpp) even when built without exceptions support.
+      configs += [ "//build/config/compiler:exceptions" ]
+    }
   }
 
   config("config") {
diff --git a/third_party/openxr/README.chromium b/third_party/openxr/README.chromium
index 47823c0..ee4af1a2 100644
--- a/third_party/openxr/README.chromium
+++ b/third_party/openxr/README.chromium
@@ -1,8 +1,8 @@
 Name: OpenXR SDK
 Short Name: OpenXR
 URL: https://github.com/KhronosGroup/OpenXR-SDK
-Version: 1.0.17sd
-Revision: bf21ccb1007bb531b45d9978919a56ea5059c245
+Version: 1.0.27
+Revision: 58a00cf85c39ad5ec4dc43a769624e420c06179a
 License: Apache 2.0
 License File: src/LICENSE
 Security Critical: yes
@@ -14,4 +14,52 @@
 
 Local Modifications:
 No modifications to upstream files. BUILD.gn contains all of the configurations
-needed to build the OpenXR loader in Chromium.
+needed to build the OpenXR loader in Chromium, along with its dependencies. The
+readme was expanded with information about transitive dependencies that are
+copied directly into the OpenXR SDK repository.
+
+
+
+Name: JNIPP
+Short Name: JNIPP
+URL: https://github.com/mitchdowd/jnipp
+Version: N/A
+Revision: unknown
+License: MIT
+License File: src/src/external/jnipp/LICENSE
+Security Critical: yes
+
+Description:
+JNIPP is just a C++ wrapper for the standard Java Native Interface (JNI).It
+tries to take some of the long-winded annoyance out of integrating your Java
+and C++ code.
+
+Local Modifications:
+No modifications to upstream files. BUILD.gn contains all of the configurations
+needed to build the library in Chromium. Since it is a transitive dependency
+that was directly included in OpenXR SDK repository, the exact revision is
+unknown. The library also does not have any versioned releases.
+
+
+
+Name: android-jni-wrappers
+Short Name: android-jni-wrappers
+URL: https://gitlab.freedesktop.org/monado/utilities/android-jni-wrappers
+Version: N/A
+Revision: unknown
+License: Apache 2.0
+License File: https://gitlab.freedesktop.org/monado/utilities/android-jni-wrappers/-/blob/a3ca2c505a338cdffcca2bf91021ee72b010122f/LICENSES/Apache-2.0.txt
+Security Critical: yes
+
+Description:
+Python tool to generate C++ wrappers for (mostly Android-related) JNI/Java
+objects. Generated files are typically slightly hand-modified.
+
+Local Modifications:
+No modifications to upstream files. BUILD.gn contains all of the configurations
+needed to build the library in Chromium, along with its dependencies. Since it
+is a transitive dependency that was directly included in OpenXR SDK repository,
+the exact revision is unknown. The library also does not have any versioned
+releases. The library contains auto-generated files with unknown hand-made
+modifications. The library is triple-licensed, and the copy from OpenXR SDK
+repository does not include a LICENSE file.
diff --git a/third_party/rust/cxx/chromium_integration/rust_cxx.gni b/third_party/rust/cxx/chromium_integration/rust_cxx.gni
index 4636b2c..99ef394 100644
--- a/third_party/rust/cxx/chromium_integration/rust_cxx.gni
+++ b/third_party/rust/cxx/chromium_integration/rust_cxx.gni
@@ -89,14 +89,13 @@
     output_h = "{{source_gen_dir}}/{{source_file_part}}.h"
     output_cc = "{{source_gen_dir}}/{{source_file_part}}.cc"
 
-    # Below we use $host_toolchain_no_sanitizers rather than $host_toolchain.
-    # Usually this is the same thing, but in sanitizer builds,
-    # host_toolchain_no_sanitizers won't have the sanitizers.
-    # In this case, it's fine either way, so we can choose whichever
-    # is the quicker to build, based on sharing C++ dependencies with
-    # $host_toolchain, or sharing Rust dependencies with proc macros
-    # using $host_toolchain_no_sanitizers.
-    cxxbridge_target = "//third_party/rust/cxxbridge_cmd/v1:cxxbridge($host_toolchain_no_sanitizers)"
+    # Below we use $rust_macro_toolchain rather than $host_toolchain since we
+    # are building a standalone Rust target that is not part of the Chromium
+    # production build, and this unblocks it from building while the Chromium
+    # stdlib is still compiling, further freeing up other Rust proc-macro
+    # targets (if they used cxxbridge for some reason).
+    cxxbridge_target =
+        "//third_party/rust/cxxbridge_cmd/v1:cxxbridge($rust_macro_toolchain)"
 
     cxxbridge_out_dir = get_label_info(cxxbridge_target, "root_out_dir")
     cxxbridge_executable = "${cxxbridge_out_dir}/cxxbridge"
diff --git a/tools/android/modularization/convenience/lookup_dep.py b/tools/android/modularization/convenience/lookup_dep.py
index 61cfdd3..47b9841 100755
--- a/tools/android/modularization/convenience/lookup_dep.py
+++ b/tools/android/modularization/convenience/lookup_dep.py
@@ -190,11 +190,9 @@
     return matches
 
   def _entries_for(self, class_name) -> List[ClassEntry]:
-    class_entries = self._class_index.get(class_name)
-    assert class_entries is not None
-    return sorted(class_entries)
+    return sorted(self._class_index[class_name])
 
-  def _index_root(self) -> Dict[str, List[ClassEntry]]:
+  def _index_root(self) -> Dict[str, Set[ClassEntry]]:
     """Create the class to target index."""
     logging.debug('Running list_java_targets.py...')
     list_java_targets_command = [
@@ -263,10 +261,10 @@
           build_config.full_class_names.update(
               dep_build_config.full_class_names)
 
-    class_index = collections.defaultdict(list)
+    class_index = collections.defaultdict(set)
     for build_config in path_to_build_config.values():
       for full_class_name in build_config.full_class_names:
-        class_index[full_class_name].append(
+        class_index[full_class_name].add(
             ClassEntry(full_class_name=full_class_name,
                        target=build_config.target_name,
                        preferred_dep=build_config.preferred_dep))
diff --git a/tools/cast3p/OWNERS b/tools/cast3p/OWNERS
index c2828b8f..3bec82c 100644
--- a/tools/cast3p/OWNERS
+++ b/tools/cast3p/OWNERS
@@ -1,6 +1,5 @@
 chonggu@google.com
 riazantsevv@google.com
-rwkeane@google.com
 
 per-file cast_core.version=chromium-internal-autoroll@skia-corp.google.com.iam.gserviceaccount.com
-per-file runtime.version=chromium-internal-autoroll@skia-corp.google.com.iam.gserviceaccount.com
\ No newline at end of file
+per-file runtime.version=chromium-internal-autoroll@skia-corp.google.com.iam.gserviceaccount.com
diff --git a/tools/cast3p/runtime.version b/tools/cast3p/runtime.version
index 9a4e193c..7b18005 100644
--- a/tools/cast3p/runtime.version
+++ b/tools/cast3p/runtime.version
@@ -1 +1 @@
-359935
+360859
diff --git a/tools/clang/scripts/upload_revision.py b/tools/clang/scripts/upload_revision.py
index 4d64230..fee94442 100755
--- a/tools/clang/scripts/upload_revision.py
+++ b/tools/clang/scripts/upload_revision.py
@@ -44,11 +44,13 @@
 HEAD_SHA_REGEX = b'"sha":"([^"]+)"'
 
 # Bots where we build Clang + Rust.
-BOTS = [
+BUILD_CLANG_BOTS = [
     'linux_upload_clang',
     'mac_upload_clang',
     'mac_upload_clang_arm',
     'win_upload_clang',
+]
+BUILD_RUST_BOTS = [
     'linux_upload_rust',
     'mac_upload_rust',
     'mac_upload_rust_arm',
@@ -300,28 +302,33 @@
     print('Cannot set both --skip-clang and --skip-rust.')
     sys.exit(1)
 
-  if args.clang_git_hash:
-    clang_git_hash = args.clang_git_hash
+  if not args.skip_clang:
+    if args.clang_git_hash:
+      clang_git_hash = args.clang_git_hash
+    else:
+      clang_git_hash = GetLatestGitHash(CLANG_URL)
+    # To `GetCommitDescription()`, we need a checkout. On success, the
+    # CheckoutLLVM() makes `LLVM_DIR` be the current working directory, so that
+    # we can GetCommitDescription() without changing directory.
+    CheckoutGitRepo("LLVM", LLVM_GIT_URL, clang_git_hash, LLVM_DIR)
+    clang_version = ClangVersion(GetCommitDescription(clang_git_hash),
+                                 args.clang_sub_revision)
+    os.chdir(CHROMIUM_DIR)
   else:
-    clang_git_hash = GetLatestGitHash(CLANG_URL)
+    clang_version = '-skipped-'
 
-  # To `GetCommitDescription()`, we need a checkout. On success, the
-  # CheckoutLLVM() makes `LLVM_DIR` be the current working directory, so that
-  # we can GetCommitDescription() without changing directory.
-  CheckoutGitRepo("LLVM", LLVM_GIT_URL, clang_git_hash, LLVM_DIR)
-  clang_version = ClangVersion(GetCommitDescription(clang_git_hash),
-                               args.clang_sub_revision)
-  os.chdir(CHROMIUM_DIR)
-
-  if args.rust_git_hash:
-    rust_git_hash = args.rust_git_hash
+  if not args.skip_rust:
+    if args.rust_git_hash:
+      rust_git_hash = args.rust_git_hash
+    else:
+      rust_git_hash = GetLatestGitHash(RUST_URL)
+    CheckoutGitRepo("Rust", RUST_GIT_URL, rust_git_hash, RUST_SRC_DIR)
+    rust_version = RustVersion(rust_git_hash, args.rust_sub_revision)
+    os.chdir(CHROMIUM_DIR)
   else:
-    rust_git_hash = GetLatestGitHash(RUST_URL)
-  CheckoutGitRepo("Rust", RUST_GIT_URL, rust_git_hash, RUST_SRC_DIR)
-  rust_version = RustVersion(rust_git_hash, args.rust_sub_revision)
-  os.chdir(CHROMIUM_DIR)
+    rust_version = '-skipped-'
 
-  print((f'Making a patch for Clang {clang_version} and Rust {rust_version}'))
+  print(f'Making a patch for Clang {clang_version} and Rust {rust_version}')
 
   branch_name = f'clang-{clang_version}_rust-{rust_version}'
   Git('checkout', 'origin/main', '-b', branch_name, no_run=args.no_git)
@@ -379,12 +386,20 @@
       no_run=args.no_git)
   Git('commit', '-m', commit_message, no_run=args.no_git)
   Git('cl', 'upload', '-f', '--bypass-hooks', no_run=args.no_git)
-  Git('cl',
-      'try',
-      '-B',
-      "chromium/try",
-      *itertools.chain(*[['-b', bot] for bot in BOTS]),
-      no_run=args.no_git)
+  if not args.skip_clang:
+    Git('cl',
+        'try',
+        '-B',
+        "chromium/try",
+        *itertools.chain(*[['-b', bot] for bot in BUILD_CLANG_BOTS]),
+        no_run=args.no_git)
+  if not args.skip_rust:
+    Git('cl',
+        'try',
+        '-B',
+        "chromium/try",
+        *itertools.chain(*[['-b', bot] for bot in BUILD_RUST_BOTS]),
+        no_run=args.no_git)
 
   print('Please, wait until the try bots succeeded '
         'and then push the binaries to goma.')
diff --git a/tools/crates/gnrt/lib/config.rs b/tools/crates/gnrt/lib/config.rs
index dedb2ecf..dd76635 100644
--- a/tools/crates/gnrt/lib/config.rs
+++ b/tools/crates/gnrt/lib/config.rs
@@ -35,6 +35,9 @@
     pub rustflags: Vec<String>,
     /// Sets GN output_dir variable.
     pub output_dir: Option<String>,
+    /// Adds the specified default library configs in the target.
+    #[serde(default)]
+    pub add_library_configs: Vec<String>,
     /// Removes the specified default library configs in the target.
     #[serde(default)]
     pub remove_library_configs: Vec<String>,
diff --git a/tools/crates/gnrt/lib/gn.rs b/tools/crates/gnrt/lib/gn.rs
index f1c561ec..e4090ec1 100644
--- a/tools/crates/gnrt/lib/gn.rs
+++ b/tools/crates/gnrt/lib/gn.rs
@@ -219,6 +219,7 @@
 
     let rustc_metadata = config_field!(rustc_metadata).next().cloned();
 
+    let add_library_configs: Vec<String> = config_field!(add_library_configs).cloned().collect();
     let remove_library_configs: Vec<String> =
         config_field!(remove_library_configs).cloned().collect();
 
@@ -247,6 +248,7 @@
     };
 
     apply_default_configs(&mut rule);
+    rule.add_library_configs.extend(add_library_configs);
     rule.remove_library_configs.extend(remove_library_configs);
 
     rule.features = dep
diff --git a/tools/json_schema_compiler/test/generated_schemas_unittest.cc b/tools/json_schema_compiler/test/generated_schemas_unittest.cc
index 3e9c2225..259a7198 100644
--- a/tools/json_schema_compiler/test/generated_schemas_unittest.cc
+++ b/tools/json_schema_compiler/test/generated_schemas_unittest.cc
@@ -18,12 +18,10 @@
   ASSERT_TRUE(GeneratedSchemas::IsGenerated(kApiName));
 
   // The schema string must be in json format.
-  absl::optional<base::Value> json_schema =
-      base::JSONReader::Read(GeneratedSchemas::Get(kApiName));
+  absl::optional<base::Value::Dict> json_schema =
+      base::JSONReader::ReadDict(GeneratedSchemas::Get(kApiName));
   ASSERT_TRUE(json_schema);
-  ASSERT_TRUE(json_schema->is_dict());
-
-  EXPECT_FALSE(json_schema->FindPath("manifest_keys"));
+  EXPECT_FALSE(json_schema->Find("manifest_keys"));
 }
 
 }  // namespace
diff --git a/tools/mac/download_symbols.py b/tools/mac/download_symbols.py
index 337f7ab..a6efc93 100755
--- a/tools/mac/download_symbols.py
+++ b/tools/mac/download_symbols.py
@@ -30,9 +30,10 @@
 
     Args:
         version: The version to download symbols for.
-        channel: The release channel to download symbols for. If None, attempts
-                 to guess the channel.
-        arch: The CPU architecture to download symbols for.
+        channel: The release channel (stable, beta, dev, canary) to download
+                 symbols for. If None, attempts to guess the channel.
+        arch: The CPU architecture (x86_64, arm64 / aarch64) to download
+              symbols for.
         dest_dir: The location to download symbols to. The dSYMs will be
                   extracted to a subdirectory of this directory.
 
@@ -168,13 +169,15 @@
     parser.add_argument(
         '--channel',
         '-c',
-        help='Chrome release channel for the version. The channel will be ' \
+        choices=['stable', 'beta', 'dev', 'canary'],
+        help='Chrome release channel for the version The channel will be ' \
              'guessed if not specified.'
     )
     parser.add_argument(
         '--arch',
         '-a',
-        help='CPU architecture to download, defaults to that of the current OS.'
+        choices=['aarch64', 'arm64', 'x86_64'],
+        help='CPU architecture to download. Defaults to that of the current OS.'
     )
     parser.add_argument('--out',
                         '-o',
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 6d4f3fb1..05c15e6a 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -30991,6 +30991,14 @@
   </description>
 </action>
 
+<action name="SidePanel.ShoppingInsights.Shown">
+  <owner>zhiyuancai@chromium.org</owner>
+  <owner>chrome-shopping@google.com</owner>
+  <description>
+    Recorded when the shopping insights side panel entry is shown.
+  </description>
+</action>
+
 <action name="SidePanel.Show">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9639d41..1bd8ed9 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -15877,6 +15877,7 @@
   <int value="5" label="User selected the Lens Region Search lab"/>
   <int value="6" label="User selected the WebUI Tab Strip lab"/>
   <int value="7" label="User selected the Tab Search Media Tabs lab"/>
+  <int value="8" label="User selected the Chrome Refresh lab"/>
 </enum>
 
 <enum name="ChromeMLServiceDecisionTreePredictionResult">
@@ -17568,6 +17569,7 @@
   <int value="17" label="APK download"/>
   <int value="19" label="Blocked Ad redirect"/>
   <int value="20" label="Blocked Ad popup"/>
+  <int value="21" label="Hash-prefix real-time experiment"/>
 </enum>
 
 <enum name="ClientSidePhishingResult">
@@ -45037,6 +45039,12 @@
   <int value="1" label="Success"/>
 </enum>
 
+<enum name="FormatPixmapSupport">
+  <int value="0" label="No format with Native Pixmaps"/>
+  <int value="1" label="NV12 format with Native Pixmaps"/>
+  <int value="2" label="YV12 format with Native Pixmaps"/>
+</enum>
+
 <enum name="FormDataDeserializationStatus">
   <int value="0" label="Login database success"/>
   <int value="1" label="Login database failure"/>
@@ -66120,6 +66128,7 @@
   <int value="1913263516" label="OculusVR:enabled"/>
   <int value="1913298816" label="OverlayScrollbar:enabled"/>
   <int value="1913926782" label="ChromeModernAlternateCardLayout:disabled"/>
+  <int value="1914131031" label="AndroidSurfaceControlMagnifier:enabled"/>
   <int value="1914347357" label="AutofillEnableVirtualCard:disabled"/>
   <int value="1915028326" label="BuiltInModuleKvStorage:disabled"/>
   <int value="1915178511" label="disable-blink-features"/>
@@ -66309,6 +66318,7 @@
   <int value="2004604350" label="AutofillGetPaymentsIdentityFromSync:enabled"/>
   <int value="2004651603" label="NativeFileSystemAPI:disabled"/>
   <int value="2004829262" label="enable-webgl-draft-extensions"/>
+  <int value="2004933261" label="AndroidSurfaceControlMagnifier:disabled"/>
   <int value="2005009211" label="OmniboxAdaptiveSuggestionsCount:disabled"/>
   <int value="2005012791" label="AblateSendPendingAccessibilityEvents:enabled"/>
   <int value="2005245012" label="SecurityInterstitialsDarkMode:disabled"/>
@@ -78426,6 +78436,18 @@
   <int value="4" label="Internal error"/>
 </enum>
 
+<enum name="OutputFormat">
+  <int value="0" label="Unset format state"/>
+  <int value="1" label="I420, 3 R8 GMBs"/>
+  <int value="2" label="Single NV12 GMB"/>
+  <int value="3" label="Dual NV12, one R8 and one RG88 GMBs"/>
+  <int value="4" label="XR30, 10:10:10:2 BGRX in one GMB"/>
+  <int value="5" label="XB30, 10:10:10:2 RGBX in one GMB"/>
+  <int value="6" label="RGBA, 8:8:8:8 RGBA in one GMB"/>
+  <int value="7" label="BGRA, 8:8:8:8 BGRA in one GMB"/>
+  <int value="8" label="P010, one P010 GMB"/>
+</enum>
+
 <enum name="OverlayFormat">
   <int value="0" label="BGRA"/>
   <int value="1" label="YUY2"/>
@@ -82609,6 +82631,7 @@
   <int value="13" label="Volume labels presented in the 'blkid' tool"/>
   <int value="14" label="Extensible Authentication Protocol (EAP) properties"/>
   <int value="15" label="Credit card number"/>
+  <int value="16" label="International Bank Account Number"/>
 </enum>
 
 <enum name="PinAutosubmitBackfillEvent">
@@ -99124,6 +99147,25 @@
   <int value="7" label="Reused previous decision (made by user)"/>
 </enum>
 
+<enum name="StorageAccessInputState">
+  <int value="0" label="OptInWithMatchingGrant">
+    The frame-level opt-in bool was provided, and there is a matching permission
+    grant.
+  </int>
+  <int value="1" label="OptInWithoutGrant">
+    The frame-level opt-in bool was provided, but there is no matching
+    permission grant.
+  </int>
+  <int value="2" label="MatchingGrantWithoutOptIn">
+    There is a matching permission grant, but no frame-level opt-in bool was
+    provided.
+  </int>
+  <int value="3" label="NoOptInNoGrant">
+    No frame-level opt-in bool was provided, and there is no matching permission
+    grant.
+  </int>
+</enum>
+
 <enum name="StorageAccessResult">
   <int value="0" label="Storage access blocked"/>
   <int value="1" label="Storage access allowed"/>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 36b0c1f..596a64e 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -34,6 +34,7 @@
   <variant name="Lens"/>
   <variant name="ReadAnything"/>
   <variant name="ReadingList"/>
+  <variant name="ShoppingInsights"/>
   <variant name="SideSearch"/>
   <variant name="UserNotes"/>
   <variant name="WebView"/>
diff --git a/tools/metrics/histograms/metadata/chrome/histograms.xml b/tools/metrics/histograms/metadata/chrome/histograms.xml
index 07b0356..438c475 100644
--- a/tools/metrics/histograms/metadata/chrome/histograms.xml
+++ b/tools/metrics/histograms/metadata/chrome/histograms.xml
@@ -168,7 +168,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.RemoteProcessInteractionResult"
-    enum="RemoteProcessInteractionResult" expires_after="2023-05-31">
+    enum="RemoteProcessInteractionResult" expires_after="2023-11-30">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
@@ -178,7 +178,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TerminateProcessErrorCode.Posix"
-    enum="PopularOSErrno" expires_after="2023-05-31">
+    enum="PopularOSErrno" expires_after="2023-11-30">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 09befcf..ccaec89 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1365,6 +1365,19 @@
   </summary>
 </histogram>
 
+<histogram name="GPU.SharedImage.FormatPixmapSupport"
+    enum="FormatPixmapSupport" expires_after="2023-12-01">
+  <owner>vasilyt@chromium.org</owner>
+  <owner>hitawala@chromium.org</owner>
+  <summary>
+    Tracks which formats can be supported for creating SharedImages with real
+    GpuMemoryBuffers on Ozone based platforms. Result is an enum that represents
+    multiplanar buffer formats in order of possible support eg. NV12, YV12 and
+    whether they can be used on platform that supports native pixmaps. This is
+    logged once per gpu process launch.
+  </summary>
+</histogram>
+
 <histogram name="GPU.SoftwareRendering" enum="BooleanSoftwareRendering"
     expires_after="2023-09-10">
   <owner>zmo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index eff7f318..fb089a8 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -2108,6 +2108,52 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.SandboxMetrics.DocumentsSize" units="MB"
+    expires_after="2023-10-01">
+  <owner>michaeldo@chromium.org</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <summary>
+    The total amount of storage used by the application Documents directory.
+    Chrome uses this directory to store files downloaded by the user. Logged at
+    application startup, maximum once every 2 weeks, if the
+    `kLogApplicationStorageSizeMetrics` flag is enabled.
+  </summary>
+</histogram>
+
+<histogram name="IOS.SandboxMetrics.LibrarySize" units="MB"
+    expires_after="2023-10-01">
+  <owner>michaeldo@chromium.org</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <summary>
+    The total amount of storage used by the application Library directory.
+    Chrome uses this directory to store application data and caches. Logged at
+    application startup, maximum once every 2 weeks, if the
+    `kLogApplicationStorageSizeMetrics` flag is enabled.
+  </summary>
+</histogram>
+
+<histogram name="IOS.SandboxMetrics.OptimizationGuideModelDownloadedItems"
+    units="items" expires_after="2023-10-01">
+  <owner>michaeldo@chromium.org</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <summary>
+    The number of download directories used by the optimization guide model
+    downloads. Logged at application startup, maximum once every 2 weeks, if the
+    `kLogApplicationStorageSizeMetrics` flag is enabled.
+  </summary>
+</histogram>
+
+<histogram name="IOS.SandboxMetrics.OptimizationGuideModelDownloadsSize"
+    units="KB" expires_after="2023-10-01">
+  <owner>michaeldo@chromium.org</owner>
+  <owner>rohitrao@chromium.org</owner>
+  <summary>
+    The total amount of storage used by the optimization guide model downloads.
+    Logged at application startup, maximum once every 2 weeks, if the
+    `kLogApplicationStorageSizeMetrics` flag is enabled.
+  </summary>
+</histogram>
+
 <histogram name="IOS.SandboxMetrics.TotalCapacity" enum="IOSStorageCapacity"
     expires_after="2023-10-01">
   <owner>michaeldo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 63d9935..db99c62 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3091,6 +3091,18 @@
   </summary>
 </histogram>
 
+<histogram name="Media.GPU.OutputFormat" enum="OutputFormat"
+    expires_after="2023-12-01">
+  <owner>hitawala@chromium.org</owner>
+  <owner>vasilyt@chromium.org</owner>
+  <owner>shared-image-team@google.com</owner>
+  <summary>
+    Records the format used for creating video frames with GpuMemoryBuffers.
+    Result is an enum that represents OutputFormat in
+    GpuVideoAcceleratorFactoriesImpl. Recorded once per VideoFrame creation.
+  </summary>
+</histogram>
+
 <histogram name="Media.GPU.VideoDecoderType" enum="VideoDecoderType"
     expires_after="2023-10-01">
   <owner>dalecurtis@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index e472085a..521384b 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -7753,11 +7753,12 @@
 
 <histogram name="LoadingPredictor.OptimizationHintsReceiveStatus"
     enum="LoadingPredictorOptimizationHintsReceiveStatus"
-    expires_after="2023-06-25">
+    expires_after="2023-10-27">
   <owner>sophiechang@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     The status of when the optimization hints were received by the Loading
     Predictor. This will be recorded on navigations for which predictions from
@@ -7769,6 +7770,7 @@
     expires_after="2023-08-27">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     The number of origins that were preconnected for a page load. It includes
     preconnect attempts that don't result in new opened connection. Logged after
@@ -7780,6 +7782,7 @@
     expires_after="2023-09-17">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     The percentage of origins that were preconnected and requested by a page
     load to the total number of origins that were preconnected for a page load.
@@ -7792,6 +7795,7 @@
     units="origins" expires_after="2023-07-16">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     When the loading predictor has origins in the local database for a given
     navigation to preconnect and preresolve, the count of predicted origins.
@@ -7802,6 +7806,7 @@
     units="%" expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     When the loading predictor has origins in the local database for a given
     navigation to preconnect and preresolve, the precision of the predictions in
@@ -7814,6 +7819,7 @@
     units="%" expires_after="2023-09-17">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     When the loading predictor has origins in the local database for a given
     navigation to preconnect and preresolve, the recall of the predictions, in
@@ -7826,6 +7832,7 @@
     enum="ResourcePrefetchPredictorRedirectStatus" expires_after="2023-06-25">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     When the prefetch predictor has origins in the local database for a given
     navigation to preconnect and preresolve, records stats about whether
@@ -7837,6 +7844,7 @@
     expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     The number of hosts that were preresolved for a page load. It includes only
     successful DNS lookups. Logged after the preconnect manager completes all
@@ -7848,6 +7856,7 @@
     expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
+  <component>Internals&gt;Preload</component>
   <summary>
     The percentage of hosts that were preresolved and requested by a page load
     to the total number of hosts that were preresolved for a page load. Logged
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index 2cef2bd..079597f 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -2354,16 +2354,6 @@
   </token>
 </histogram>
 
-<histogram name="PlatformFile.UnknownErrors.Windows" enum="WinGetLastError"
-    expires_after="M95">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <summary>
-    Errors returned by CreateFile on Windows that PlatformFileError doesn't yet
-    support.
-  </summary>
-</histogram>
-
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index 4bc137ef..085fa7d 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -92,6 +92,24 @@
   </summary>
 </histogram>
 
+<histogram name="API.StorageAccess.InputState" enum="StorageAccessInputState"
+    expires_after="2024-05-12">
+  <owner>cfredric@chromium.org</owner>
+  <owner>kaustubhag@chromium.org</owner>
+  <summary>
+    Records the signals used to decide whether to unblock cookie access (in the
+    network service) via the Storage Access API. This metric is recorded each
+    time CookieSettings are queried and the Storage Access API is relevant (i.e.
+    third-party cookies are blocked by user setting, and there's no
+    site-specific override that takes precedence). This is recorded regardless
+    of whether the Storage Access API is enabled in the browser.
+
+    This metric is only recorded in the network service, not in the browser's
+    CookieSettings object, since the browser's accesses supply hardcoded
+    override signals and therefore are not interesting to analyze.
+  </summary>
+</histogram>
+
 <histogram name="API.StorageAccess.RequestOutcome"
     enum="StorageAccessAPIRequestOutcome" expires_after="2023-08-31">
   <owner>cfredric@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml
index 27b689d..fb8a0a5 100644
--- a/tools/metrics/histograms/metadata/uma/histograms.xml
+++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -372,6 +372,20 @@
   </summary>
 </histogram>
 
+<histogram name="UMA.LocalHistogram" enum="Boolean" expires_after="never">
+<!-- expires-never: Used to monitor that local histograms are not reported. -->
+
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    A local histogram that should never be reported to the servers. If this
+    histogram appears on the serverside, it is possibly an indication that local
+    histograms are making it into UMA logs. Emitted in the constructor and
+    destructor of MetricsService, and in child processes after setting up the
+    shared memory.
+  </summary>
+</histogram>
+
 <histogram name="UMA.LogSize.OnSuccess" units="KB" expires_after="2023-10-01">
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 70eb5ad3..7cb60de 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -708,7 +708,7 @@
                               pinpoint_only=True)
 ANDROID_GO_WEBVIEW = PerfPlatform('android-go_webview-perf',
                                   'Android OPM1.171019.021 (gobo)',
-                                  _ANDROID_GO_WEBVIEW_BENCHMARK_CONFIGS, 10,
+                                  _ANDROID_GO_WEBVIEW_BENCHMARK_CONFIGS, 8,
                                   'android')
 ANDROID_PIXEL2 = PerfPlatform('android-pixel2-perf',
                               'Android OPM1.171019.021',
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 6f8ff68d..981496d0 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/v34.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "79436072ceab4698c704d1c35523a7a90b92170d",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/8962065c2d161b04046d8bb1af2f68b2cb155487/trace_processor_shell.exe"
+            "hash": "a6ef59f506e789a2a8f4e3ff8367e24ad48be433",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d9d0730f824491aeae712e41d43cf348272eb55e/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "336a42cb9ec3c417e13a97816271fec10cdf67e5",
             "full_remote_path": "perfetto-luci-artifacts/v34.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "ce67e881f8fc52d4df2eced8ae970cc6eb053732",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/8962065c2d161b04046d8bb1af2f68b2cb155487/trace_processor_shell"
+            "hash": "3553f5a4f72c25a9adeecd00e3c821fefa6bd9bd",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/d9d0730f824491aeae712e41d43cf348272eb55e/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c32364e05e22cdf82ee0866aedd11c0e2050809c",
             "full_remote_path": "perfetto-luci-artifacts/v34.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a998317f2d0bbcf5544aab4181872e5066e4418e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/d9d0730f824491aeae712e41d43cf348272eb55e/trace_processor_shell"
+            "hash": "ab9efa3edd117f26568756e670183fdfb87141b2",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/22a5641b28a7f061f700ea1c486e634943569f72/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/android-go_webview-perf_map.json b/tools/perf/core/shard_maps/android-go_webview-perf_map.json
index 101e78e..3b5d466 100644
--- a/tools/perf/core/shard_maps/android-go_webview-perf_map.json
+++ b/tools/perf/core/shard_maps/android-go_webview-perf_map.json
@@ -11,7 +11,7 @@
                 "abridged": false
             },
             "system_health.common_mobile": {
-                "end": 19,
+                "end": 26,
                 "abridged": false
             }
         }
@@ -19,8 +19,8 @@
     "1": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 19,
-                "end": 65,
+                "begin": 26,
+                "end": 74,
                 "abridged": false
             }
         }
@@ -28,11 +28,11 @@
     "2": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 65,
+                "begin": 74,
                 "abridged": false
             },
             "system_health.memory_mobile": {
-                "end": 5,
+                "end": 10,
                 "abridged": false
             }
         }
@@ -40,8 +40,8 @@
     "3": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 5,
-                "end": 12,
+                "begin": 10,
+                "end": 21,
                 "abridged": false
             }
         }
@@ -49,8 +49,8 @@
     "4": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 12,
-                "end": 21,
+                "begin": 21,
+                "end": 35,
                 "abridged": false
             }
         }
@@ -58,8 +58,8 @@
     "5": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 21,
-                "end": 30,
+                "begin": 35,
+                "end": 53,
                 "abridged": false
             }
         }
@@ -67,8 +67,8 @@
     "6": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 30,
-                "end": 46,
+                "begin": 53,
+                "end": 68,
                 "abridged": false
             }
         }
@@ -76,25 +76,7 @@
     "7": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 46,
-                "end": 59,
-                "abridged": false
-            }
-        }
-    },
-    "8": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 59,
-                "end": 69,
-                "abridged": false
-            }
-        }
-    },
-    "9": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 69,
+                "begin": 68,
                 "abridged": false
             },
             "system_health.webview_startup": {
@@ -107,19 +89,17 @@
     },
     "extra_infos": {
         "num_stories": 188,
-        "predicted_min_shard_time": 1047.0,
-        "predicted_min_shard_index": 9,
-        "predicted_max_shard_time": 1323.0,
-        "predicted_max_shard_index": 8,
-        "shard #0": 1224.0,
-        "shard #1": 1161.0,
-        "shard #2": 1270.0,
-        "shard #3": 1086.0,
-        "shard #4": 1188.0,
-        "shard #5": 1149.0,
-        "shard #6": 1209.0,
-        "shard #7": 1218.0,
-        "shard #8": 1323.0,
-        "shard #9": 1047.0
+        "predicted_min_shard_time": 1351.0,
+        "predicted_min_shard_index": 7,
+        "predicted_max_shard_time": 1497.0,
+        "predicted_max_shard_index": 6,
+        "shard #0": 1436.0,
+        "shard #1": 1479.0,
+        "shard #2": 1427.0,
+        "shard #3": 1431.0,
+        "shard #4": 1410.0,
+        "shard #5": 1485.0,
+        "shard #6": 1497.0,
+        "shard #7": 1351.0
     }
 }
\ No newline at end of file
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
index 831ea2a7..3098db1 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win.cc
@@ -169,7 +169,10 @@
         current_line_start->text_offset(), current_line_end->text_offset(),
         AXCoordinateSystem::kFrame, AXClippingBehavior::kUnclipped);
 
-    if (frame_rect.Contains(current_rect)) {
+    // There are scenarios where the text range bounds might be slightly outside
+    // the container bounds, so we check if the bounding rects intersect rather
+    // than if it is only contained within.
+    if (frame_rect.Intersects(current_rect)) {
       Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider =
           AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
               current_line_start->Clone(), current_line_end->Clone());
diff --git a/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
index 8d9b073..a38be10 100644
--- a/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
@@ -785,64 +785,6 @@
   EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
 }
 
-TEST_F(AXPlatformNodeTextProviderTest,
-       ITextProviderWinGetVisibleRangesInContentEditable) {
-  TestAXTreeUpdate update(std::string(R"HTML(
-    ++1 kRootWebArea
-    ++++2 kGenericContainer states=kRichlyEditable,kEditable boolAttribute=kNonAtomicTextFieldRoot,true
-    ++++++3 kParagraph
-    ++++++++4 kStaticText name="hello"
-    ++++++++++5 kInlineTextBox name="hello"
-  )HTML"));
-
-  Init(update);
-
-  AXNode* div_node = GetRoot()->children()[0];
-
-  ComPtr<IRawElementProviderSimple> div_com =
-      QueryInterfaceFromNode<IRawElementProviderSimple>(div_node);
-
-  ComPtr<ITextProvider> text_provider;
-  EXPECT_HRESULT_SUCCEEDED(
-      div_com->GetPatternProvider(UIA_TextPatternId, &text_provider));
-
-  base::win::ScopedSafearray text_provider_ranges;
-  EXPECT_HRESULT_SUCCEEDED(
-      text_provider->GetVisibleRanges(text_provider_ranges.Receive()));
-
-  ITextRangeProvider** array_data;
-  ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
-      text_provider_ranges.Get(), reinterpret_cast<void**>(&array_data)));
-
-  ComPtr<AXPlatformNodeTextProviderWin> platform_node_text_provider;
-  text_provider->QueryInterface(IID_PPV_ARGS(&platform_node_text_provider));
-
-  AXPlatformNodeWin* owner = GetOwner(platform_node_text_provider.Get());
-
-  ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
-      AXEmbeddedObjectBehavior::kSuppressCharacter);
-  AXNodePosition::AXPositionInstance expected_start =
-      owner->GetDelegate()->CreateTextPositionAt(0);
-  AXNodePosition::AXPositionInstance expected_end =
-      expected_start->CreatePositionAtEndOfAnchor();
-
-  ComPtr<AXPlatformNodeTextRangeProviderWin> actual_range;
-  array_data[0]->QueryInterface(IID_PPV_ARGS(&actual_range));
-
-  EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
-  EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
-
-  EXPECT_EQ(expected_start->text_offset(), 0);
-  EXPECT_EQ(GetStart(actual_range.Get())->text_offset(),
-            expected_start->text_offset());
-  EXPECT_EQ(expected_end->text_offset(), 5);
-  EXPECT_EQ(GetEnd(actual_range.Get())->text_offset(),
-            expected_end->text_offset());
-
-  ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(text_provider_ranges.Get()));
-  text_provider_ranges.Reset();
-}
-
 TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetConversionTarget) {
   TestAXTreeUpdate update(std::string(R"HTML(
     ++1 kRootWebArea name="Document"
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 0e841f6..8403530 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -110,7 +110,7 @@
 // Enabling this fixes b/265853952.
 BASE_FEATURE(kAlwaysConfirmComposition,
              "AlwaysConfirmComposition",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Update of the virtual keyboard settings UI as described in
@@ -436,12 +436,12 @@
 // Fixes b/267944900.
 BASE_FEATURE(kWaylandKeepSelectionFix,
              "WaylandKeepSelectionFix",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Fixes b/267944900.
 BASE_FEATURE(kWaylandCancelComposition,
              "WaylandCancelComposition",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kWaylandScreenCoordinatesEnabled,
              "WaylandScreenCoordinatesEnabled",
diff --git a/ui/events/blink/blink_features.cc b/ui/events/blink/blink_features.cc
index 17a8814b..5336097 100644
--- a/ui/events/blink/blink_features.cc
+++ b/ui/events/blink/blink_features.cc
@@ -14,8 +14,4 @@
              "DontSendKeyEventsToJavascript",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kReduceHorizontalFlingVelocity,
-             "ReduceHorizontalFlingVelocity",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 }  // namespace features
diff --git a/ui/events/blink/blink_features.h b/ui/events/blink/blink_features.h
index c0c5b0c..41bf687e 100644
--- a/ui/events/blink/blink_features.h
+++ b/ui/events/blink/blink_features.h
@@ -20,12 +20,6 @@
 // is on installed PWA.
 COMPONENT_EXPORT(BLINK_FEATURES)
 BASE_DECLARE_FEATURE(kDontSendKeyEventsToJavascript);
-
-// Reduces the velocity of horizontal flings. This is an experiment to test
-// a simple means of making flings in mandatory scroll snap areas feel more
-// natural. See https://crbug.com/1189696 for details.
-COMPONENT_EXPORT(BLINK_FEATURES)
-BASE_DECLARE_FEATURE(kReduceHorizontalFlingVelocity);
 }
 
 #endif  // UI_EVENTS_BLINK_BLINK_FEATURES_H_
diff --git a/ui/events/blink/fling_booster.cc b/ui/events/blink/fling_booster.cc
index 67956c1..4811bda 100644
--- a/ui/events/blink/fling_booster.cc
+++ b/ui/events/blink/fling_booster.cc
@@ -5,7 +5,6 @@
 #include "ui/events/blink/fling_booster.h"
 
 #include "base/trace_event/trace_event.h"
-#include "ui/events/blink/blink_features.h"
 
 using blink::WebGestureEvent;
 using blink::WebInputEvent;
@@ -35,12 +34,6 @@
   gfx::Vector2dF velocity(fling_start.data.fling_start.velocity_x,
                           fling_start.data.fling_start.velocity_y);
 
-  static const bool reduce_horizontal_fling_velocity =
-      base::FeatureList::IsEnabled(features::kReduceHorizontalFlingVelocity);
-  if (reduce_horizontal_fling_velocity) {
-    // Reduce the horizontal velocity of flings.
-    velocity.Scale(0.2f, 1.0f);
-  }
   TRACE_EVENT2("input", "FlingBooster::GetVelocityForFlingStart", "vx",
                velocity.x(), "vy", velocity.y());
 
diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc
index a74fd23..e7f5779 100644
--- a/ui/gfx/render_text_harfbuzz.cc
+++ b/ui/gfx/render_text_harfbuzz.cc
@@ -1807,6 +1807,15 @@
       if (IsNewlineSegment(display_text, segment))
         continue;
 
+      const size_t crash_report_size = 256;
+      DEBUG_ALIAS_FOR_U16CSTR(alias_display_text, display_text.c_str(),
+                              crash_report_size);
+      DEBUG_ALIAS_FOR_U16CSTR(alias_text, text().c_str(), crash_report_size);
+      const size_t run_list_size = run_list->runs().size();
+      base::debug::Alias(&run_list_size);
+      const size_t segment_run_size = segment.run;
+      base::debug::Alias(&segment_run_size);
+
       const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
       renderer->SetTypeface(run.font_params.skia_face);
       renderer->SetTextSize(SkIntToScalar(run.font_params.font_size));
@@ -1844,10 +1853,6 @@
         const int pos_size = positions.size();
         base::debug::Alias(&colored_pos);
         base::debug::Alias(&pos_size);
-        const size_t crash_report_size = 256;
-        DEBUG_ALIAS_FOR_U16CSTR(alias_display_text, display_text.c_str(),
-                                crash_report_size);
-        DEBUG_ALIAS_FOR_U16CSTR(alias_text, text().c_str(), crash_report_size);
 
         renderer->SetForegroundColor(it->second);
         renderer->DrawPosText(
diff --git a/ui/ozone/platform/cast/surface_factory_cast.cc b/ui/ozone/platform/cast/surface_factory_cast.cc
index 03fc4f0..bd6536e 100644
--- a/ui/ozone/platform/cast/surface_factory_cast.cc
+++ b/ui/ozone/platform/cast/surface_factory_cast.cc
@@ -32,8 +32,7 @@
   SkCanvas* GetCanvas() override { return surface_->getCanvas(); }
 
   void ResizeCanvas(const gfx::Size& viewport_size, float scale) override {
-    surface_ =
-        SkSurface::MakeNull(viewport_size.width(), viewport_size.height());
+    surface_ = SkSurfaces::Null(viewport_size.width(), viewport_size.height());
   }
 
   void PresentCanvas(const gfx::Rect& damage) override {}
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
index dca21f43..dc65762 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
@@ -344,8 +344,16 @@
 
 gfx::NativeViewAccessible
 NativeWidgetMacNSWindowHost::GetNativeViewAccessibleForNSWindow() const {
-  if (in_process_ns_window_bridge_)
-    return in_process_ns_window_bridge_->ns_window();
+  if (in_process_ns_window_bridge_) {
+    // AppKit requires the return of the NSWindow that contains `ns_view()`,
+    // Failure to do so will result in VoiceOver not announcing focus changes.
+    // Typically, this would be `ns_window()`, but in fullscreen mode,
+    // the overlay window's contentView is moved to NSToolbarFullScreenWindow.
+    // Regardless of the mode (fullscreen or not), `[ns_view() window]` would
+    // always yield the correct NSWindow that contains `ns_view()`.
+    return [in_process_ns_window_bridge_->ns_view() window];
+  }
+
   return remote_window_accessible_.get();
 }
 
diff --git a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html
index 3872608..96e6200 100644
--- a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html
+++ b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html
@@ -187,11 +187,18 @@
 
   .folder {
     --cr-icon-color: currentColor;
+    --cr-icon-size: 16px;
     height: 16px;
     margin: 0;
     width: 16px;
   }
 
+  :host-context([chrome-refresh-2023]) .folder {
+    --cr-icon-size: 20px;
+    height: 20px;
+    width: 20px;
+  }
+
   .count {
     --cr-url-list-item-count-border-radius: 4px;
     display: none;
diff --git a/ui/webui/resources/js/OWNERS b/ui/webui/resources/js/OWNERS
index f85b6c1..c1876b8f 100644
--- a/ui/webui/resources/js/OWNERS
+++ b/ui/webui/resources/js/OWNERS
@@ -1,4 +1,2 @@
-per-file post_message_api_* = danan@chromium.org
 per-file post_message_api_* = yilkal@chromium.org
-per-file webview_manager* = danan@chromium.org
 per-file webview_manager* = yilkal@chromium.org
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 97428a9..84be4c3 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -41,6 +41,7 @@
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/webapps/browser/installable/installable_manager.h"
+#include "components/webapps/browser/installable/ml_installability_promoter.h"
 #include "components/webrtc/media_stream_devices_controller.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -421,9 +422,10 @@
   PrerenderTabHelper::CreateForWebContents(web_contents_.get());
 
   webapps::InstallableManager::CreateForWebContents(web_contents_.get());
+  webapps::MLInstallabilityPromoter::CreateForWebContents(web_contents_.get());
 
 #if BUILDFLAG(IS_ANDROID)
-  // Must be created after InstallableManager.
+  // Must be created after InstallableManager and MLInstallabilityPromoter.
   WebLayerAppBannerManagerAndroid::CreateForWebContents(web_contents_.get());
 #endif
 }